Attribute and type defs in ODS don’t provide a declarative way to describe parsers and printers in the same way that ops do. This RFC proposes an assemblyFormat field for TypeDefs and AttrDefs to generate parsers and printers.
Defining a Custom Assembly Format
Attributes and type defs that define an assemblyFormat must specify mnemonic. Then, the assembly format is specified in a manner very similar to op assembly formats:
def MyAttr : AttrDef<MyDialect, "MyAttr"> {
let parameters = (ins "int32_t":$value);
let mnemonic = "my_attr";
let assemblyFormat = "`<` $value `>`";
}
The above attribute will have an assembly format #my_dialect.my_attr<42>, since it meets the requirements for pretty-printing format.
let assemblyFormat = "`(` $value `)`";
The above format will result in the ugly form: #my_dialect<"my_attr(42)">
Special Directives
The only available directive is struct. The directive struct($one, $two, $three, ...) matches a comma-separated list of key-value pairs for the parameters one, two, and three in any order (but all must be present).
def MyStructType : TypeDef<MyDialect, "MyStructType"> {
let parameters = (ins "int":$one, "int":$two, "int":$three);
let mnemonic = "my_struct_type";
let assemblyFormat = "`<` struct($two, $three) `,` $one `>`";
}
The above attribute will have the format !my_dialect.my_struct_type<two = 24, three = 16, 42>.
Using struct(*) will capture all the parameters in an attribute or type.
Parameter Parsing and Printing
The default parser for any parameter is
MyParamT myParam;
::mlir::ParseResult result = ::mlir::parseField(parser, myParam);
The default printer is
printer << getMyParam();
Both parseField and the stream operator can be overloaded to provide parsers and printers for custom types. One goal is to provide enough of these in MLIR core that must common types (integers, strings, etc.) won’t require work on the user’s part.
Just as non-POD parameters require defining an AttrParameter or TypeParameter to provide a custom allocator, they must also specify a cppStorageType; for StringRef it is std::string and for ArrayRef<T> it is SmallVector<T>.
For more complex parsing or printing behaviour, define parser and/or printer to override the default parser and printer.
def MyParam : AttrParameter<"MyParam"> {
let parser = [{ parseMyParam($_parser, $_self, $_type) }];
let printer = [{ printMyParam($_printer, $_self) }];
}
For attribute parameters, $_type refers to the expected type of the attribute.
Limitations
Parameter types whose cppStorageType does not have a default constructor cannot be parsed as part of an assembly format, because they are declared at the top of the function – notably, APFloat.
Example: Ditching StructAttr
StructAttr is convenient and concise, but it can be slow (attributes inside DictAttr).
def SlowAttr : StructAttr<"SlowAttr", MyDialect, [
StructFieldAttr<"count", I32Attr>,
StructFieldAttr<"dims", I32ArrayAttr>,
StructFieldAttr<"limits", I32ArrayAttr>,
StructFieldAttr<"map", AffineMapAttr>]>;
The above example can be replaced with an AttrDef now as simply:
def FasterAttr : AttrDef<"FasterAttr", MyDialect> {
let parameters = (ins
"int32_t":$count,
ArrayRefParameter<"int32_t">:$dims,
ArrayRefParameter<"int32_t">:$limits,
"::mlir::AffineMap":$map
);
let mnemonic = "faster";
let assemblyFormat = "`<` struct(*) `>`";
}