1 # Defining Dialect Attributes and Types
3 This document describes how to define dialect
4 [attributes](../LangRef.md/#attributes) and [types](../LangRef.md/#type-system).
10 Before diving into how to define these constructs, below is a quick refresher
11 from the [MLIR LangRef](../LangRef.md).
15 Attributes are the mechanism for specifying constant data on operations in
16 places where a variable is never allowed - e.g. the comparison predicate of a
17 [`arith.cmpi` operation](../Dialects/ArithOps.md/#arithcmpi-arithcmpiop), or
18 the underlying value of a [`arith.constant` operation](../Dialects/ArithOps.md/#arithconstant-arithconstantop).
19 Each operation has an attribute dictionary, which associates a set of attribute
20 names to attribute values.
24 Every SSA value, such as operation results or block arguments, in MLIR has a type
25 defined by the type system. MLIR has an open type system with no fixed list of types,
26 and there are no restrictions on the abstractions they represent. For example, take
27 the following [Arithmetic AddI operation](../Dialects/ArithOps.md/#arithaddi-arithaddiop):
30 %result = arith.addi %lhs, %rhs : i64
33 It takes two input SSA values (`%lhs` and `%rhs`), and returns a single SSA
34 value (`%result`). The inputs and outputs of this operation are of type `i64`,
35 which is an instance of the [Builtin IntegerType](../Dialects/Builtin.md/#integertype).
37 ## Attributes and Types
39 The C++ Attribute and Type classes in MLIR (like Ops, and many other things) are
40 value-typed. This means that instances of `Attribute` or `Type` are passed
41 around by-value, as opposed to by-pointer or by-reference. The `Attribute` and
42 `Type` classes act as wrappers around internal storage objects that are uniqued
43 within an instance of an `MLIRContext`.
45 The structure for defining Attributes and Types is nearly identical, with only a
46 few differences depending on the context. As such, a majority of this document
47 describes the process for defining both Attributes and Types side-by-side with
48 examples for both. If necessary, a section will explicitly call out any
51 One difference is that generating C++ classes from declarative TableGen
52 definitions will require adding additional targets to your `CMakeLists.txt`.
53 This is not necessary for custom types. The details are outlined further below.
55 ### Adding a new Attribute or Type definition
57 As described above, C++ Attribute and Type objects in MLIR are value-typed and
58 essentially function as helpful wrappers around an internal storage object that
59 holds the actual data for the type. Similarly to Operations, Attributes and Types
60 are defined declaratively via [TableGen](https://llvm.org/docs/TableGen/index.html);
61 a generic language with tooling to maintain records of domain-specific information.
62 It is highly recommended that users review the
63 [TableGen Programmer's Reference](https://llvm.org/docs/TableGen/ProgRef.html)
64 for an introduction to its syntax and constructs.
66 Starting the definition of a new attribute or type simply requires adding a
67 specialization for either the `AttrDef` or `TypeDef` class respectively. Instances
68 of the classes correspond to unqiue Attribute or Type classes.
70 Below show cases an example Attribute and Type definition. We generally recommend
71 defining Attribute and Type classes in different `.td` files to better encapsulate
72 the different constructs, and define a proper layering between them. This
73 recommendation extends to all of the MLIR constructs, including [Interfaces](../Interfaces.md),
77 // Include the definition of the necessary tablegen constructs for defining
79 include "mlir/IR/AttrTypeBase.td"
81 // It's common to define a base classes for types in the same dialect. This
82 // removes the need to pass in the dialect for each type, and can also be used
83 // to define a few fields ahead of time.
84 class MyDialect_Type<string name, string typeMnemonic, list<Trait> traits = []>
85 : TypeDef<My_Dialect, name, traits> {
86 let mnemonic = typeMnemonic;
89 // Here is a simple definition of an "integer" type, with a width parameter.
90 def My_IntegerType : MyDialect_Type<"Integer", "int"> {
91 let summary = "Integer type with arbitrary precision up to a fixed limit";
93 Integer types have a designated bit width.
95 /// Here we defined a single parameter for the type, which is the bitwidth.
96 let parameters = (ins "unsigned":$width);
98 /// Here we define the textual format of the type declaratively, which will
99 /// automatically generate parser and printer logic. This will allow for
100 /// instances of the type to be output as, for example:
102 /// !my.int<10> // a 10-bit integer.
104 let assemblyFormat = "`<` $width `>`";
106 /// Indicate that our type will add additional verification to the parameters.
107 let genVerifyDecl = 1;
111 Below is an example of an Attribute:
114 // Include the definition of the necessary tablegen constructs for defining
116 include "mlir/IR/AttrTypeBase.td"
118 // It's common to define a base classes for attributes in the same dialect. This
119 // removes the need to pass in the dialect for each attribute, and can also be used
120 // to define a few fields ahead of time.
121 class MyDialect_Attr<string name, string attrMnemonic, list<Trait> traits = []>
122 : AttrDef<My_Dialect, name, traits> {
123 let mnemonic = attrMnemonic;
126 // Here is a simple definition of an "integer" attribute, with a type and value parameter.
127 def My_IntegerAttr : MyDialect_Attr<"Integer", "int"> {
128 let summary = "An Attribute containing a integer value";
130 An integer attribute is a literal attribute that represents an integral
131 value of the specified integer type.
133 /// Here we've defined two parameters, one is a "self" type parameter, and the
134 /// other is the integer value of the attribute. The self type parameter is
135 /// specially handled by the assembly format.
136 let parameters = (ins AttributeSelfTypeParameter<"">:$type, "APInt":$value);
138 /// Here we've defined a custom builder for the type, that removes the need to pass
139 /// in an MLIRContext instance; as it can be infered from the `type`.
141 AttrBuilderWithInferredContext<(ins "Type":$type,
142 "const APInt &":$value), [{
143 return $_get(type.getContext(), type, value);
147 /// Here we define the textual format of the attribute declaratively, which will
148 /// automatically generate parser and printer logic. This will allow for
149 /// instances of the attribute to be output as, for example:
151 /// #my.int<50> : !my.int<32> // a 32-bit integer of value 50.
153 /// Note that the self type parameter is not included in the assembly format.
154 /// Its value is derived from the optional trailing type on all attributes.
155 let assemblyFormat = "`<` $value `>`";
157 /// Indicate that our attribute will add additional verification to the parameters.
158 let genVerifyDecl = 1;
160 /// Indicate to the ODS generator that we do not want the default builders,
161 /// as we have defined our own simpler ones.
162 let skipDefaultBuilders = 1;
168 The name of the C++ class which gets generated defaults to
169 `<classParamName>Attr` or `<classParamName>Type` for attributes and types
170 respectively. In the examples above, this was the `name` template parameter that
171 was provided to `MyDialect_Attr` and `MyDialect_Type`. For the definitions we
172 added above, we would get C++ classes named `IntegerType` and `IntegerAttr`
173 respectively. This can be explicitly overridden via the `cppClassName` field.
177 If you added your dialect using `add_mlir_dialect()` in your `CMakeLists.txt`,
178 the above mentioned classes will automatically get generated for custom
179 _types_. They will be output in a file named `<Your Dialect>Types.h.inc`.
181 To also generate the classes for custom _attributes_, you will need to add
182 two additional TableGen targets to your `CMakeLists.txt`:
185 mlir_tablegen(<Your Dialect>AttrDefs.h.inc -gen-attrdef-decls
186 -attrdefs-dialect=<Your Dialect>)
187 mlir_tablegen(<Your Dialect>AttrDefs.cpp.inc -gen-attrdef-defs
188 -attrdefs-dialect=<Your Dialect>)
189 add_public_tablegen_target(<Your Dialect>AttrDefsIncGen)
192 The generated `<Your Dialect>AttrDefs.h.inc` will need to be included whereever
193 you are referencing the custom attribute types.
197 The `summary` and `description` fields allow for providing user documentation
198 for the attribute or type. The `summary` field expects a simple single-line
199 string, with the `description` field used for long and extensive documentation.
200 This documentation can be used to generate markdown documentation for the
201 dialect and is used by upstream
202 [MLIR dialects](https://mlir.llvm.org/docs/Dialects/).
206 The `mnemonic` field, i.e. the template parameters `attrMnemonic` and
207 `typeMnemonic` we specified above, are used to specify a name for use during
208 parsing. This allows for more easily dispatching to the current attribute or
209 type class when parsing IR. This field is generally optional, and custom
210 parsing/printing logic can be added without defining it, though most classes
211 will want to take advantage of the convenience it provides. This is why we
212 added it as a template parameter in the examples above.
216 The `parameters` field is a variable length list containing the attribute or
217 type's parameters. If no parameters are specified (the default), this type is
218 considered a singleton type (meaning there is only one possible instance).
219 Parameters in this list take the form: `"c++Type":$paramName`. Parameter types
220 with a C++ type that requires allocation when constructing the storage instance
221 in the context require one of the following:
223 - Utilize the `AttrParameter` or `TypeParameter` classes instead of the raw
224 "c++Type" string. This allows for providing custom allocation code when using
225 that parameter. `StringRefParameter` and `ArrayRefParameter` are examples of
226 common parameter types that require allocation.
227 - Set the `genAccessors` field to 1 (the default) to generate accessor methods
228 for each parameter (e.g. `int getWidth() const` in the Type example above).
229 - Set the `hasCustomStorageConstructor` field to `1` to generate a storage class
230 that only declares the constructor, allowing for you to specialize it with
231 whatever allocation code necessary.
233 #### AttrParameter, TypeParameter, and AttrOrTypeParameter
235 As hinted at above, these classes allow for specifying parameter types with
236 additional functionality. This is generally useful for complex parameters, or those
237 with additional invariants that prevent using the raw C++ class. Examples
238 include documentation (e.g. the `summary` and `syntax` field), the C++ type, a
239 custom allocator to use in the storage constructor method, a custom comparator
240 to decide if two instances of the parameter type are equal, etc. As the names
241 may suggest, `AttrParameter` is intended for parameters on Attributes,
242 `TypeParameter` for Type parameters, and `AttrOrTypeParameters` for either.
244 Below is an easy parameter pitfall, and highlights when to use these parameter
248 let parameters = (ins "ArrayRef<int>":$dims);
251 The above seems innocuous, but it is often a bug! The default storage
252 constructor blindly copies parameters by value. It does not know anything about
253 the types, meaning that the data of this ArrayRef will be copied as-is and is
254 likely to lead to use-after-free errors when using the created Attribute or
255 Type if the underlying does not have a lifetime exceeding that of the MLIRContext.
256 If the lifetime of the data can't be guaranteed, the `ArrayRef<int>` requires
257 allocation to ensure that its elements reside within the MLIRContext, e.g. with
258 `dims = allocator.copyInto(dims)`.
260 Here is a simple example for the exact situation above:
263 def ArrayRefIntParam : TypeParameter<"::llvm::ArrayRef<int>", "Array of int"> {
264 let allocator = "$_dst = $_allocator.copyInto($_self);";
267 The parameter can then be used as so:
270 let parameters = (ins ArrayRefIntParam:$dims);
273 Below contains descriptions for other various available fields:
275 The `allocator` code block has the following substitutions:
277 - `$_allocator` is the TypeStorageAllocator in which to allocate objects.
278 - `$_dst` is the variable in which to place the allocated data.
280 The `comparator` code block has the following substitutions:
282 - `$_lhs` is an instance of the parameter type.
283 - `$_rhs` is an instance of the parameter type.
285 MLIR includes several specialized classes for common situations:
287 - `APFloatParameter` for APFloats.
289 - `StringRefParameter<descriptionOfParam>` for StringRefs.
291 - `ArrayRefParameter<arrayOf, descriptionOfParam>` for ArrayRefs of value types.
293 - `SelfAllocationParameter<descriptionOfParam>` for C++ classes which contain a
294 method called `allocateInto(StorageAllocator &allocator)` to allocate itself
297 - `ArrayRefOfSelfAllocationParameter<arrayOf, descriptionOfParam>` for arrays of
298 objects which self-allocate as per the last specialization.
300 - `AttributeSelfTypeParameter` is a special `AttrParameter` that represents
301 parameters derived from the optional trailing type on attributes.
305 Similarly to operations, Attribute and Type classes may attach `Traits` that
306 provide additional mixin methods and other data. `Trait`s may be attached via
307 the trailing template argument, i.e. the `traits` list parameter in the example
308 above. See the main [`Trait`](../Traits) documentation for more information
309 on defining and using traits.
313 Attribute and Type classes may attach `Interfaces` to provide an virtual
314 interface into the Attribute or Type. `Interfaces` are added in the same way as
315 [Traits](#Traits), by using the `traits` list template parameter of the
316 `AttrDef` or `TypeDef`. See the main [`Interface`](../Interfaces.md)
317 documentation for more information on defining and using interfaces.
321 For each attribute or type, there are a few builders(`get`/`getChecked`)
322 automatically generated based on the parameters of the type. These are used to
323 construct instances of the corresponding attribute or type. For example, given
324 the following definition:
327 def MyAttrOrType : ... {
328 let parameters = (ins "int":$intParam);
332 The following builders are generated:
335 // Builders are named `get`, and return a new instance for a given set of parameters.
336 static MyAttrOrType get(MLIRContext *context, int intParam);
338 // If `genVerifyDecl` is set to 1, the following method is also generated. This method
339 // is similar to `get`, but is failable and on error will return nullptr.
340 static MyAttrOrType getChecked(function_ref<InFlightDiagnostic()> emitError,
341 MLIRContext *context, int intParam);
344 If these autogenerated methods are not desired, such as when they conflict with
345 a custom builder method, the `skipDefaultBuilders` field may be set to 1 to
346 signal that the default builders should not be generated.
348 #### Custom builder methods
350 The default builder methods may cover a majority of the simple cases related to
351 construction, but when they cannot satisfy all of an attribute or type's needs,
352 additional builders may be defined via the `builders` field. The `builders`
353 field is a list of custom builders, either using `TypeBuilder` for types or
354 `AttrBuilder` for attributes, that are added to the attribute or type class. The
355 following will showcase several examples for defining builders for a custom type
356 `MyType`, the process is the same for attributes except that attributes use
357 `AttrBuilder` instead of `TypeBuilder`.
361 let parameters = (ins "int":$intParam);
364 TypeBuilder<(ins "int":$intParam)>,
365 TypeBuilder<(ins CArg<"int", "0">:$intParam)>,
366 TypeBuilder<(ins CArg<"int", "0">:$intParam), [{
367 // Write the body of the `get` builder inline here.
368 return Base::get($_ctxt, intParam);
370 TypeBuilderWithInferredContext<(ins "Type":$typeParam), [{
371 // This builder states that it can infer an MLIRContext instance from
373 return Base::get(typeParam.getContext(), ...);
375 TypeBuilder<(ins "int":$intParam), [{}], "IntegerType">,
380 In this example, we provide several different convenience builders that are
381 useful in different scenarios. The `ins` prefix is common to many function
382 declarations in ODS, which use a TableGen [`dag`](#tablegen-syntax). What
383 follows is a comma-separated list of types (quoted string or `CArg`) and names
384 prefixed with the `$` sign. The use of `CArg` allows for providing a default
385 value to that argument. Let's take a look at each of these builders individually
387 The first builder will generate the declaration of a builder method that looks
392 TypeBuilder<(ins "int":$intParam)>,
397 class MyType : /*...*/ {
399 static MyType get(::mlir::MLIRContext *context, int intParam);
403 This builder is identical to the one that will be automatically generated for
404 `MyType`. The `context` parameter is implicitly added by the generator, and is
405 used when building the Type instance (with `Base::get`). The distinction here is
406 that we can provide the implementation of this `get` method. With this style of
407 builder definition only the declaration is generated, the implementor of
408 `MyType` will need to provide a definition of `MyType::get`.
410 The second builder will generate the declaration of a builder method that looks
415 TypeBuilder<(ins CArg<"int", "0">:$intParam)>,
420 class MyType : /*...*/ {
422 static MyType get(::mlir::MLIRContext *context, int intParam = 0);
426 The constraints here are identical to the first builder example except for the
427 fact that `intParam` now has a default value attached.
429 The third builder will generate the declaration of a builder method that looks
434 TypeBuilder<(ins CArg<"int", "0">:$intParam), [{
435 // Write the body of the `get` builder inline here.
436 return Base::get($_ctxt, intParam);
442 class MyType : /*...*/ {
444 static MyType get(::mlir::MLIRContext *context, int intParam = 0);
447 MyType MyType::get(::mlir::MLIRContext *context, int intParam) {
448 // Write the body of the `get` builder inline here.
449 return Base::get(context, intParam);
453 This is identical to the second builder example. The difference is that now, a
454 definition for the builder method will be generated automatically using the
455 provided code block as the body. When specifying the body inline, `$_ctxt` may
456 be used to access the `MLIRContext *` parameter.
458 The fourth builder will generate the declaration of a builder method that looks
463 TypeBuilderWithInferredContext<(ins "Type":$typeParam), [{
464 // This builder states that it can infer an MLIRContext instance from
466 return Base::get(typeParam.getContext(), ...);
472 class MyType : /*...*/ {
474 static MyType get(Type typeParam);
477 MyType MyType::get(Type typeParam) {
478 // This builder states that it can infer an MLIRContext instance from its
480 return Base::get(typeParam.getContext(), ...);
484 In this builder example, the main difference from the third builder example
485 there is that the `MLIRContext` parameter is no longer added. This is because
486 the builder used `TypeBuilderWithInferredContext` implies that the context
487 parameter is not necessary as it can be inferred from the arguments to the
490 The fifth builder will generate the declaration of a builder method with a
491 custom return type, like:
495 TypeBuilder<(ins "int":$intParam), [{}], "IntegerType">,
500 class MyType : /*...*/ {
502 static IntegerType get(::mlir::MLIRContext *context, int intParam);
507 This generates a builder declaration the same as the first three examples, but
508 the return type of the builder is user-specified instead of the attribute or
509 type class. This is useful for defining builders of attributes and types that
510 may fold or canonicalize on construction.
512 ### Parsing and Printing
514 If a mnemonic was specified, the `hasCustomAssemblyFormat` and `assemblyFormat`
515 fields may be used to specify the assembly format of an attribute or type. Attributes
516 and Types with no parameters need not use either of these fields, in which case
517 the syntax for the Attribute or Type is simply the mnemonic.
519 For each dialect, two "dispatch" functions will be created: one for parsing and
520 one for printing. These static functions placed alongside the class definitions
521 and have the following function signatures:
524 static ParseResult generatedAttributeParser(DialectAsmParser& parser, StringRef *mnemonic, Type attrType, Attribute &result);
525 static LogicalResult generatedAttributePrinter(Attribute attr, DialectAsmPrinter& printer);
527 static ParseResult generatedTypeParser(DialectAsmParser& parser, StringRef *mnemonic, Type &result);
528 static LogicalResult generatedTypePrinter(Type type, DialectAsmPrinter& printer);
531 The above functions should be added to the respective in your
532 `Dialect::printType` and `Dialect::parseType` methods, or consider using the
533 `useDefaultAttributePrinterParser` and `useDefaultTypePrinterParser` ODS Dialect
534 options if all attributes or types define a mnemonic.
536 The mnemonic, hasCustomAssemblyFormat, and assemblyFormat fields are optional.
537 If none are defined, the generated code will not include any parsing or printing
538 code and omit the attribute or type from the dispatch functions above. In this
539 case, the dialect author is responsible for parsing/printing in the respective
540 `Dialect::parseAttribute`/`Dialect::printAttribute` and
541 `Dialect::parseType`/`Dialect::printType` methods.
543 #### Using `hasCustomAssemblyFormat`
545 Attributes and types defined in ODS with a mnemonic can define an
546 `hasCustomAssemblyFormat` to specify custom parsers and printers defined in C++.
547 When set to `1` a corresponding `parse` and `print` method will be declared on
548 the Attribute or Type class to be defined by the user.
550 For Types, these methods will have the form:
552 - `static Type MyType::parse(AsmParser &parser)`
554 - `void MyType::print(AsmPrinter &p) const`
556 For Attributes, these methods will have the form:
558 - `static Attribute MyAttr::parse(AsmParser &parser, Type attrType)`
560 - `void MyAttr::print(AsmPrinter &p) const`
562 #### Using `assemblyFormat`
564 Attributes and types defined in ODS with a mnemonic can define an
565 `assemblyFormat` to declaratively describe custom parsers and printers. The
566 assembly format consists of literals, variables, and directives.
568 - A literal is a keyword or valid punctuation enclosed in backticks, e.g.
569 `` `keyword` `` or `` `<` ``.
570 - A variable is a parameter name preceded by a dollar sign, e.g. `$param0`,
571 which captures one attribute or type parameter.
572 - A directive is a keyword followed by an optional argument list that defines
573 special parser and printer behaviour.
576 // An example type with an assembly format.
577 def MyType : TypeDef<My_Dialect, "MyType"> {
578 // Define a mnemonic to allow the dialect's parser hook to call into the
580 let mnemonic = "my_type";
582 // Define two parameters whose C++ types are indicated in string literals.
583 let parameters = (ins "int":$count, "AffineMap":$map);
585 // Define the assembly format. Surround the format with less `<` and greater
586 // `>` so that MLIR's printer uses the pretty format.
587 let assemblyFormat = "`<` $count `,` `map` `=` $map `>`";
591 The declarative assembly format for `MyType` results in the following format in
595 !my_dialect.my_type<42, map = affine_map<(i, j) -> (j, i)>>
598 ##### Parameter Parsing and Printing
600 For many basic parameter types, no additional work is needed to define how these
601 parameters are parsed or printed.
603 - The default printer for any parameter is `$_printer << $_self`, where `$_self`
604 is the C++ value of the parameter and `$_printer` is an `AsmPrinter`.
605 - The default parser for a parameter is
606 `FieldParser<$cppClass>::parse($_parser)`, where `$cppClass` is the C++ type
607 of the parameter and `$_parser` is an `AsmParser`.
609 Printing and parsing behaviour can be added to additional C++ types by
610 overloading these functions or by defining a `parser` and `printer` in an ODS
613 Example of overloading:
616 using MyParameter = std::pair<int, int>;
618 AsmPrinter &operator<<(AsmPrinter &printer, MyParameter param) {
619 printer << param.first << " * " << param.second;
622 template <> struct FieldParser<MyParameter> {
623 static FailureOr<MyParameter> parse(AsmParser &parser) {
625 if (parser.parseInteger(a) || parser.parseStar() ||
626 parser.parseInteger(b))
628 return MyParameter(a, b);
633 Example of using ODS parameter classes:
636 def MyParameter : TypeParameter<"std::pair<int, int>", "pair of ints"> {
637 let printer = [{ $_printer << $_self.first << " * " << $_self.second }];
638 let parser = [{ [&] -> FailureOr<std::pair<int, int>> {
640 if ($_parser.parseInteger(a) || $_parser.parseStar() ||
641 $_parser.parseInteger(b))
643 return std::make_pair(a, b);
648 A type using this parameter with the assembly format `` `<` $myParam `>` `` will
649 look as follows in the IR:
652 !my_dialect.my_type<42 * 24>
655 ###### Non-POD Parameters
657 Parameters that aren't plain-old-data (e.g. references) may need to define a
658 `cppStorageType` to contain the data until it is copied into the allocator. For
659 example, `StringRefParameter` uses `std::string` as its storage type, whereas
660 `ArrayRefParameter` uses `SmallVector` as its storage type. The parsers for
661 these parameters are expected to return `FailureOr<$cppStorageType>`.
663 To add a custom conversion between the `cppStorageType` and the C++ type of the
664 parameter, parameters can override `convertFromStorage`, which by default is
665 `"$_self"` (i.e., it attempts an implicit conversion from `cppStorageType`).
667 ###### Optional and Default-Valued Parameters
669 An optional parameter can be omitted from the assembly format of an attribute or
670 a type. An optional parameter is omitted when it is equal to its default value.
671 Optional parameters in the assembly format can be indicated by setting
672 `defaultValue`, a string of the C++ default value. If a value for the parameter
673 was not encountered during parsing, it is set to this default value. If a
674 parameter is equal to its default value, it is not printed. The `comparator`
675 field of the parameter is used, but if one is not specified, the equality
678 When using `OptionalParameter`, the default value is set to the C++
679 default-constructed value for the C++ storage type. For example, `Optional<int>`
680 will be set to `std::nullopt` and `Attribute` will be set to `nullptr`. The
681 presence of these parameters is tested by comparing them to their "null" values.
683 An optional group is a set of elements optionally printed based on the presence
684 of an anchor. Only optional parameters or directives that only capture optional
685 parameters can be used in optional groups. The group in which the anchor is
686 placed is printed if it is present, otherwise the other one is printed. If a
687 directive that captures more than one optional parameter is used as the anchor,
688 the optional group is printed if any of the captured parameters is present. For
689 example, a `custom` directive may only be used as an optional group anchor if it
690 captures at least one optional parameter.
692 Suppose parameter `a` is an `IntegerAttr`.
695 ( `(` $a^ `)` ) : (`x`)?
698 In the above assembly format, if `a` is present (non-null), then it will be
699 printed as `(5 : i32)`. If it is not present, it will be `x`. Directives that
700 are used inside optional groups are allowed only if all captured parameters are
703 An optional parameter can also be specified with `DefaultValuedParameter`, which
704 specifies that a parameter should be omitted when it is equal to some given
708 let parameters = (ins DefaultValuedParameter<"Optional<int>", "5">:$a)
709 let mnemonic = "default_valued";
710 let assemblyFormat = "(`<` $a^ `>`)?";
713 Which will look like:
716 !test.default_valued // a = 5
717 !test.default_valued<10> // a = 10
720 For optional `Attribute` or `Type` parameters, the current MLIR context is
721 available through `$_ctxt`. E.g.
724 DefaultValuedParameter<"IntegerType", "IntegerType::get($_ctxt, 32)">
727 The value of parameters that appear __before__ the default-valued parameter in
728 the parameter declaration list are available as substitutions. E.g.
731 let parameters = (ins
732 "IntegerAttr":$value,
733 DefaultValuedParameter<"Type", "$value.getType()">:$type
737 ###### Attribute Self Type Parameter
739 An attribute optionally has a trailing type after the assembly format of the
740 attribute value itself. MLIR parses over the attribute value and optionally
741 parses a colon-type before passing the `Type` into the dialect parser hook.
744 dialect-attribute ::= `#` dialect-namespace `<` attr-data `>`
746 | `#` alias-name pretty-dialect-sym-body? (`:` type)?
749 `AttributeSelfTypeParameter` is an attribute parameter specially handled by the
750 assembly format generator. Only one such parameter can be specified, and its
751 value is derived from the trailing type. This parameter's default value is
752 `NoneType::get($_ctxt)`.
754 In order for the type to be printed by
755 MLIR, however, the attribute must implement `TypedAttrInterface`. For example,
758 // This attribute has only a self type parameter.
759 def MyExternAttr : AttrDef<MyDialect, "MyExtern", [TypedAttrInterface]> {
760 let parameters = (AttributeSelfTypeParameter<"">:$type);
761 let mnemonic = "extern";
762 let assemblyFormat = "";
766 This attribute can look like:
769 #my_dialect.extern // none
770 #my_dialect.extern : i32
771 #my_dialect.extern : tensor<4xi32>
772 #my_dialect.extern : !my_dialect.my_type
775 ##### Assembly Format Directives
777 Attribute and type assembly formats have the following directives:
779 - `params`: capture all parameters of an attribute or type.
780 - `qualified`: mark a parameter to be printed with its leading dialect and
782 - `struct`: generate a "struct-like" parser and printer for a list of key-value
784 - `custom`: dispatch a call to user-define parser and printer functions
785 - `ref`: in a custom directive, references a previously bound variable
787 ###### `params` Directive
789 This directive is used to refer to all parameters of an attribute or type, except
790 for the attribute self type (which is handled separately from normal parameters).
791 When used as a top-level directive, `params` generates a parser and printer for a
792 comma-separated list of the parameters. For example:
795 def MyPairType : TypeDef<My_Dialect, "MyPairType"> {
796 let parameters = (ins "int":$a, "int":$b);
797 let mnemonic = "pair";
798 let assemblyFormat = "`<` params `>`";
802 In the IR, this type will appear as:
805 !my_dialect.pair<42, 24>
808 The `params` directive can also be passed to other directives, such as `struct`,
809 as an argument that refers to all parameters in place of explicitly listing all
810 parameters as variables.
812 ###### `qualified` Directive
814 This directive can be used to wrap attribute or type parameters such that they
815 are printed in a fully qualified form, i.e., they include the dialect name and
821 def OuterType : TypeDef<My_Dialect, "MyOuterType"> {
822 let parameters = (ins MyPairType:$inner);
823 let mnemonic = "outer";
824 let assemblyFormat = "`<` pair `:` $inner `>`";
826 def OuterQualifiedType : TypeDef<My_Dialect, "MyOuterQualifiedType"> {
827 let parameters = (ins MyPairType:$inner);
828 let mnemonic = "outer_qual";
829 let assemblyFormat = "`<` pair `:` qualified($inner) `>`";
833 In the IR, the types will appear as:
836 !my_dialect.outer<pair : <42, 24>>
837 !my_dialect.outer_qual<pair : !mydialect.pair<42, 24>>
840 If optional parameters are present, they are not printed in the parameter list
841 if they are not present.
843 ###### `struct` Directive
845 The `struct` directive accepts a list of variables to capture and will generate
846 a parser and printer for a comma-separated list of key-value pairs. If an
847 optional parameter is included in the `struct`, it can be elided. The variables
848 are printed in the order they are specified in the argument list **but can be
849 parsed in any order**. For example:
852 def MyStructType : TypeDef<My_Dialect, "MyStructType"> {
853 let parameters = (ins StringRefParameter<>:$sym_name,
854 "int":$a, "int":$b, "int":$c);
855 let mnemonic = "struct";
856 let assemblyFormat = "`<` $sym_name `->` struct($a, $b, $c) `>`";
860 In the IR, this type can appear with any permutation of the order of the
861 parameters captured in the directive.
864 !my_dialect.struct<"foo" -> a = 1, b = 2, c = 3>
865 !my_dialect.struct<"foo" -> b = 2, c = 3, a = 1>
868 Passing `params` as the only argument to `struct` makes the directive capture
869 all the parameters of the attribute or type. For the same type above, an
870 assembly format of `` `<` struct(params) `>` `` will result in:
873 !my_dialect.struct<b = 2, sym_name = "foo", c = 3, a = 1>
876 The order in which the parameters are printed is the order in which they are
877 declared in the attribute's or type's `parameter` list.
879 ###### `custom` and `ref` directive
881 The `custom` directive is used to dispatch calls to user-defined printer and
882 parser functions. For example, suppose we had the following type:
885 let parameters = (ins "int":$foo, "int":$bar);
886 let assemblyFormat = "custom<Foo>($foo) custom<Bar>($bar, ref($foo))";
889 The `custom` directive `custom<Foo>($foo)` will in the parser and printer
890 respectively generate calls to:
893 ParseResult parseFoo(AsmParser &parser, int &foo);
894 void printFoo(AsmPrinter &printer, int foo);
897 As you can see, by default parameters are passed into the parse function by
898 reference. This is only possible if the C++ type is default constructible.
899 If the C++ type is not default constructible, the parameter is wrapped in a
900 `FailureOr`. Therefore, given the following definition:
903 let parameters = (ins "NotDefaultConstructible":$foobar);
904 let assemblyFormat = "custom<Fizz>($foobar)";
907 It will generate calls expecting the following signature for `parseFizz`:
910 ParseResult parseFizz(AsmParser &parser, FailureOr<NotDefaultConstructible> &foobar);
913 A previously bound variable can be passed as a parameter to a `custom` directive
914 by wrapping it in a `ref` directive. In the previous example, `$foo` is bound by
915 the first directive. The second directive references it and expects the
916 following printer and parser signatures:
919 ParseResult parseBar(AsmParser &parser, int &bar, int foo);
920 void printBar(AsmPrinter &printer, int bar, int foo);
923 More complex C++ types can be used with the `custom` directive. The only caveat
924 is that the parameter for the parser must use the storage type of the parameter.
925 For example, `StringRefParameter` expects the parser and printer signatures as:
928 ParseResult parseStringParam(AsmParser &parser, std::string &value);
929 void printStringParam(AsmPrinter &printer, StringRef value);
932 The custom parser is considered to have failed if it returns failure or if any
933 bound parameters have failure values afterwards.
935 A string of C++ code can be used as a `custom` directive argument. When
936 generating the custom parser and printer call, the string is pasted as a
937 function argument. For example, `parseBar` and `printBar` can be re-used with
941 let parameters = (ins "int":$bar);
942 let assemblyFormat = [{ custom<Bar>($foo, "1") }];
945 The string is pasted verbatim but with substitutions for `$_builder` and
946 `$_ctxt`. String literals can be used to parameterize custom directives.
950 If the `genVerifyDecl` field is set, additional verification methods are
951 generated on the class.
953 - `static LogicalResult verify(function_ref<InFlightDiagnostic()> emitError, parameters...)`
955 These methods are used to verify the parameters provided to the attribute or
956 type class on construction, and emit any necessary diagnostics. This method is
957 automatically invoked from the builders of the attribute or type class.
959 - `AttrOrType getChecked(function_ref<InFlightDiagnostic()> emitError, parameters...)`
961 As noted in the [Builders](#Builders) section, these methods are companions to
962 `get` builders that are failable. If the `verify` invocation fails when these
963 methods are called, they return nullptr instead of asserting.
967 Somewhat alluded to in the sections above is the concept of a "storage class"
968 (often abbreviated to "storage"). Storage classes contain all of the data
969 necessary to construct and unique a attribute or type instance. These classes
970 are the "immortal" objects that get uniqued within an MLIRContext and get
971 wrapped by the `Attribute` and `Type` classes. Every Attribute or Type class has
972 a corresponding storage class, that can be accessed via the protected
975 In most cases the storage class is auto generated, but if necessary it can be
976 manually defined by setting the `genStorageClass` field to 0. The name and
977 namespace (defaults to `detail`) can additionally be controlled via the The
978 `storageClass` and `storageNamespace` fields.
980 #### Defining a storage class
982 User defined storage classes must adhere to the following:
984 - Inherit from the base type storage class of `AttributeStorage` or
985 `TypeStorage` respectively.
986 - Define a type alias, `KeyTy`, that maps to a type that uniquely identifies an
987 instance of the derived type. For example, this could be a `std::tuple` of all
988 of the storage parameters.
989 - Provide a construction method that is used to allocate a new instance of the
991 - `static Storage *construct(StorageAllocator &allocator, const KeyTy &key)`
992 - Provide a comparison method between an instance of the storage and the
994 - `bool operator==(const KeyTy &) const`
995 - Provide a method to generate the `KeyTy` from a list of arguments passed to
996 the uniquer when building an Attribute or Type. (Note: This is only necessary
997 if the `KeyTy` cannot be default constructed from these arguments).
998 - `static KeyTy getKey(Args...&& args)`
999 - Provide a method to hash an instance of the `KeyTy`. (Note: This is not
1000 necessary if an `llvm::DenseMapInfo<KeyTy>` specialization exists)
1001 - `static llvm::hash_code hashKey(const KeyTy &)`
1002 - Provide a method to generate the `KeyTy` from an instance of the storage class.
1003 - `static KeyTy getAsKey()`
1005 Let's look at an example:
1008 /// Here we define a storage class for a ComplexType, that holds a non-zero
1009 /// integer and an integer type.
1010 struct ComplexTypeStorage : public TypeStorage {
1011 ComplexTypeStorage(unsigned nonZeroParam, Type integerType)
1012 : nonZeroParam(nonZeroParam), integerType(integerType) {}
1014 /// The hash key for this storage is a pair of the integer and type params.
1015 using KeyTy = std::pair<unsigned, Type>;
1017 /// Define the comparison function for the key type.
1018 bool operator==(const KeyTy &key) const {
1019 return key == KeyTy(nonZeroParam, integerType);
1022 /// Define a hash function for the key type.
1023 /// Note: This isn't necessary because std::pair, unsigned, and Type all have
1024 /// hash functions already available.
1025 static llvm::hash_code hashKey(const KeyTy &key) {
1026 return llvm::hash_combine(key.first, key.second);
1029 /// Define a construction function for the key type.
1030 /// Note: This isn't necessary because KeyTy can be directly constructed with
1031 /// the given parameters.
1032 static KeyTy getKey(unsigned nonZeroParam, Type integerType) {
1033 return KeyTy(nonZeroParam, integerType);
1036 /// Define a construction method for creating a new instance of this storage.
1037 static ComplexTypeStorage *construct(StorageAllocator &allocator, const KeyTy &key) {
1038 return new (allocator.allocate<ComplexTypeStorage>())
1039 ComplexTypeStorage(key.first, key.second);
1042 /// Construct an instance of the key from this storage class.
1043 KeyTy getAsKey() const {
1044 return KeyTy(nonZeroParam, integerType);
1047 /// The parametric data held by the storage class.
1048 unsigned nonZeroParam;
1053 ### Mutable attributes and types
1055 Attributes and Types are immutable objects uniqued within an MLIRContext. That
1056 being said, some parameters may be treated as "mutable" and modified after
1057 construction. Mutable parameters should be reserved for parameters that can not
1058 be reasonably initialized during construction time. Given the mutable component,
1059 these parameters do not take part in the uniquing of the Attribute or Type.
1061 TODO: Mutable parameters are currently not supported in the declarative
1062 specification of attributes and types, and thus requires defining the Attribute
1063 or Type class in C++.
1065 #### Defining a mutable storage
1067 In addition to the base requirements for a storage class, instances with a
1068 mutable component must additionally adhere to the following:
1070 - The mutable component must not participate in the storage `KeyTy`.
1071 - Provide a mutation method that is used to modify an existing instance of the
1072 storage. This method modifies the mutable component based on arguments, using
1073 `allocator` for any newly dynamically-allocated storage, and indicates whether
1074 the modification was successful.
1075 - `LogicalResult mutate(StorageAllocator &allocator, Args ...&& args)`
1077 Let's define a simple storage for recursive types, where a type is identified by
1078 its name and may contain another type including itself.
1081 /// Here we define a storage class for a RecursiveType that is identified by its
1082 /// name and contains another type.
1083 struct RecursiveTypeStorage : public TypeStorage {
1084 /// The type is uniquely identified by its name. Note that the contained type
1085 /// is _not_ a part of the key.
1086 using KeyTy = StringRef;
1088 /// Construct the storage from the type name. Explicitly initialize the
1089 /// containedType to nullptr, which is used as marker for the mutable
1090 /// component being not yet initialized.
1091 RecursiveTypeStorage(StringRef name) : name(name), containedType(nullptr) {}
1093 /// Define the comparison function.
1094 bool operator==(const KeyTy &key) const { return key == name; }
1096 /// Define a construction method for creating a new instance of the storage.
1097 static RecursiveTypeStorage *construct(StorageAllocator &allocator,
1099 // Note that the key string is copied into the allocator to ensure it
1100 // remains live as long as the storage itself.
1101 return new (allocator.allocate<RecursiveTypeStorage>())
1102 RecursiveTypeStorage(allocator.copyInto(key));
1105 /// Define a mutation method for changing the type after it is created. In
1106 /// many cases, we only want to set the mutable component once and reject
1107 /// any further modification, which can be achieved by returning failure from
1109 LogicalResult mutate(StorageAllocator &, Type body) {
1110 // If the contained type has been initialized already, and the call tries
1111 // to change it, reject the change.
1112 if (containedType && containedType != body)
1115 // Change the body successfully.
1116 containedType = body;
1125 #### Type class definition
1127 Having defined the storage class, we can define the type class itself.
1128 `Type::TypeBase` provides a `mutate` method that forwards its arguments to the
1129 `mutate` method of the storage and ensures the mutation happens safely.
1132 class RecursiveType : public Type::TypeBase<RecursiveType, Type,
1133 RecursiveTypeStorage> {
1135 /// Inherit parent constructors.
1138 /// Creates an instance of the Recursive type. This only takes the type name
1139 /// and returns the type with uninitialized body.
1140 static RecursiveType get(MLIRContext *ctx, StringRef name) {
1141 // Call into the base to get a uniqued instance of this type. The parameter
1142 // (name) is passed after the context.
1143 return Base::get(ctx, name);
1146 /// Now we can change the mutable component of the type. This is an instance
1147 /// method callable on an already existing RecursiveType.
1148 void setBody(Type body) {
1149 // Call into the base to mutate the type.
1150 LogicalResult result = Base::mutate(body);
1152 // Most types expect the mutation to always succeed, but types can implement
1153 // custom logic for handling mutation failures.
1154 assert(succeeded(result) &&
1155 "attempting to change the body of an already-initialized type");
1157 // Avoid unused-variable warning when building without assertions.
1161 /// Returns the contained type, which may be null if it has not been
1162 /// initialized yet.
1163 Type getBody() { return getImpl()->containedType; }
1165 /// Returns the name.
1166 StringRef getName() { return getImpl()->name; }
1170 ### Extra declarations
1172 The declarative Attribute and Type definitions try to auto-generate as much
1173 logic and methods as possible. With that said, there will always be long-tail
1174 cases that won't be covered. For such cases, `extraClassDeclaration` and
1175 `extraClassDefinition` can be used. Code within the `extraClassDeclaration`
1176 field will be copied literally to the generated C++ Attribute or Type class.
1177 Code within `extraClassDefinition` will be added to the generated source file
1178 inside the class's C++ namespace. The substitution `$cppClass` will be replaced
1179 by the Attribute or Type's C++ class name.
1181 Note that these are mechanisms intended for long-tail cases by power users; for
1182 not-yet-implemented widely-applicable cases, improving the infrastructure is
1185 ### Registering with the Dialect
1187 Once the attributes and types have been defined, they must then be registered
1188 with the parent `Dialect`. This is done via the `addAttributes` and `addTypes`
1189 methods. Note that when registering, the full definition of the storage classes
1193 void MyDialect::initialize() {
1194 /// Add the defined attributes to the dialect.
1196 #define GET_ATTRDEF_LIST
1197 #include "MyDialect/Attributes.cpp.inc"
1200 /// Add the defined types to the dialect.
1202 #define GET_TYPEDEF_LIST
1203 #include "MyDialect/Types.cpp.inc"