# 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` and `#my_dialect.attr` 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(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(attr)) {
os << "my_dialect_attr";
return AliasResult::FinalAlias;
}
return AliasResult::NoAlias;
}
};
void MyDialect::initialize() {
// register the interface to the dialect
addInterface();
}
```
* 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]> {
...
}
```
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`.
## 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()
}
```