| # Customizing Assembly Behavior |
| |
| [TOC] |
| |
| ## Generating Aliases |
| |
| To reduce verbosity in the resulting assembly, `AsmPrinter` can generate aliases for frequently used types and attributes. |
| |
| 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`, simplifying further references. |
| |
| To enable this, the owning dialect of these types/attributes can define an interface to hook into the `AsmPrinter`. This is effective only when the assembly is not printed in generic form. |
| |
| ```cpp |
| // OpAsmDialectInterface is defined in |
| // https://github.com/llvm/llvm-project/blob/91ab10e8d6c256d841da1a1a1b47c334e08d95b9/mlir/include/mlir/IR/OpImplementation.h#L1738 |
| 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"; |
| // Could return OverridableAlias when |
| // allowing other dialect to override the alias. |
| // |
| // Other dialects are allowed to provide alias for |
| // type/attribute not owned by them |
| // but the final result would depend on the registration order |
| // of these dialects in the MLIRContext |
| 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>(); |
| } |
| ``` |
| |
| * If `getAlias` provides an alias with a trailing digit, `AsmPrinter` appends an underscore to avoid conflicts with autogenerated IDs. |
| * If multiple types/attributes have the same alias from `getAlias`, a number is appended to the alias to avoid conflicts. |
| |
| ## 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`. |
| |
| ## 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() |
| } |
| ``` |