# 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` and `#my_dialect.attr` 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(type)) { os << "my_dialect_type"; return AliasResult::FinalAlias; } return AliasResult::NoAlias; } AliasResult getAlias(Attribute attr, raw_ostream& os) const override { if (mlir::isa(attr)) { os << "my_dialect_attr"; return AliasResult::FinalAlias; } return AliasResult::NoAlias; } }; void MyDialect::initialize() { // register the interface to the dialect addInterface(); } ``` ### `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]> { ... } ``` 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 setNameFn) { auto type = getType(); if (auto intCst = llvm::dyn_cast(getValue())) { auto intType = llvm::dyn_cast(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() } ```