1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
# Customizing Assembly Behavior
[TOC]
## Generating Aliases
`AsmPrinter` can generate aliases for frequently used types and attributes when not printing them in generic form. For example, `!my_dialect.type<a=3,b=4,c=5,d=tuple,e=another_type>` and `#my_dialect.attr<a=3>` can be aliased to `!my_dialect_type` and `#my_dialect_attr`.
There are mainly two ways to hook into the `AsmPrinter`. One is the attribute/type interface and the other is the dialect interface.
The attribute/type interface is the first hook to check. If no such hook is found, or the hook returns `OverridableAlias` (see definition below), then dialect interfaces are involved.
The dialect interface for one specific dialect could generate alias for all types/attributes, even when it does not "own" them. The `AsmPrinter` checks all dialect interfaces based on their order of registration. For example, the default alias `map` for `builtin` attribute `AffineMapAttr` could be overriden by the dialect interface for `my_dialect` as custom dialect is often registered after the `builtin` dialect.
```cpp
/// Holds the result of `OpAsm{Dialect,Attr,Type}Interface::getAlias` hook call.
enum class OpAsmAliasResult {
/// The object (type or attribute) is not supported by the hook
/// and an alias was not provided.
NoAlias,
/// An alias was provided, but it might be overriden by other hook.
OverridableAlias,
/// An alias was provided and it should be used
/// (no other hooks will be checked).
FinalAlias
};
```
If multiple types/attributes have the same alias from `getAlias` hooks, a number is appended to the alias to avoid conflicts.
### `OpAsmDialectInterface`
```cpp
#include "mlir/IR/OpImplementation.h"
struct MyDialectOpAsmDialectInterface : public OpAsmDialectInterface {
public:
using OpAsmDialectInterface::OpAsmDialectInterface;
AliasResult getAlias(Type type, raw_ostream& os) const override {
if (mlir::isa<MyType>(type)) {
os << "my_dialect_type";
return AliasResult::FinalAlias;
}
return AliasResult::NoAlias;
}
AliasResult getAlias(Attribute attr, raw_ostream& os) const override {
if (mlir::isa<MyAttribute>(attr)) {
os << "my_dialect_attr";
return AliasResult::FinalAlias;
}
return AliasResult::NoAlias;
}
};
void MyDialect::initialize() {
// register the interface to the dialect
addInterface<MyDialectOpAsmDialectInterface>();
}
```
### `OpAsmAttrInterface` and `OpAsmTypeInterface`
The easiest way to use these interfaces is toggling `genMnemonicAlias` in the tablegen file of the attribute/alias. It directly uses the mnemonic as alias. See [Defining Dialect Attributes and Types](/docs/DefiningDialects/AttributesAndTypes) for details.
If a more custom behavior is wanted, the following modification to the attribute/type should be made
1. Add `OpAsmAttrInterface` or `OpAsmTypeInterface` into its trait list.
2. Implement the `getAlias` method, either in tablegen or its cpp file.
```tablegen
include "mlir/IR/OpAsmInterface.td"
// Add OpAsmAttrInterface trait
def MyAttr : MyDialect_Attr<"MyAttr",
[ OpAsmAttrInterface ] > {
// This method could be put in the cpp file.
let extraClassDeclaration = [{
::mlir::OpAsmAliasResult getAlias(::llvm::raw_ostream &os) const {
os << "alias_name";
return ::mlir::OpAsmAliasResult::OverridableAlias;
}
}];
}
```
## Suggesting SSA/Block Names
An `Operation` can suggest the SSA name prefix using `OpAsmOpInterface`.
For example, `arith.constant` will suggest a name like `%c42_i32` for its result:
```tablegen
include "mlir/IR/OpAsmInterface.td"
def Arith_ConstantOp : Op<Arith_Dialect, "constant",
[ConstantLike, Pure,
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>]> {
...
}
```
And the corresponding method:
```cpp
// from https://github.com/llvm/llvm-project/blob/5ce271ef74dd3325993c827f496e460ced41af11/mlir/lib/Dialect/Arith/IR/ArithOps.cpp#L184
void arith::ConstantOp::getAsmResultNames(
function_ref<void(Value, StringRef)> setNameFn) {
auto type = getType();
if (auto intCst = llvm::dyn_cast<IntegerAttr>(getValue())) {
auto intType = llvm::dyn_cast<IntegerType>(type);
// Sugar i1 constants with 'true' and 'false'.
if (intType && intType.getWidth() == 1)
return setNameFn(getResult(), (intCst.getInt() ? "true" : "false"));
// Otherwise, build a complex name with the value and type.
SmallString<32> specialNameBuffer;
llvm::raw_svector_ostream specialName(specialNameBuffer);
specialName << 'c' << intCst.getValue();
if (intType)
specialName << '_' << type;
setNameFn(getResult(), specialName.str());
} else {
setNameFn(getResult(), "cst");
}
}
```
Similarly, an `Operation` can suggest the name for its block arguments using `getAsmBlockArgumentNames` method in `OpAsmOpInterface`.
For custom block names, `OpAsmOpInterface` has a method `getAsmBlockNames` so that
the operation can suggest a custom prefix instead of a generic `^bb0`.
Alternatively, `OpAsmTypeInterface` provides a `getAsmName` method for scenarios where the name could be inferred from its type.
## Defining Default Dialect
An `Operation` can indicate that the nested region in it has a default dialect prefix, and the operations in the region could elide the dialect prefix.
For example, in a `func.func` op all `func` prefix could be omitted:
```tablegen
include "mlir/IR/OpAsmInterface.td"
def FuncOp : Func_Op<"func", [
OpAsmOpInterface
...
]> {
let extraClassDeclaration = [{
/// Allow the dialect prefix to be omitted.
static StringRef getDefaultDialect() { return "func"; }
}];
}
```
```mlir
func.func @main() {
// actually func.call
call @another()
}
```
|