MLIR

Multi-Level IR Compiler Framework

Customizing Assembly Behavior

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.

// 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:

include "mlir/IR/OpAsmInterface.td"

def Arith_ConstantOp : Op<Arith_Dialect, "constant",
    [ConstantLike, Pure,
     DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>]> {
...
}

And the corresponding method:

// 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:

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"; }
  }];
}
func.func @main() {
  // actually func.call
  call @another()
}