
After the introduction of OpAsmAttr/TypeInterface in #121187 #124721, the documentation for them could be updated along side the doc for OpAsmDialectInterface. #127993 changed the trailing digit behavior for alias name generation.
165 lines
5.6 KiB
Markdown
165 lines
5.6 KiB
Markdown
# 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()
|
|
}
|
|
```
|