1# Defining Dialect Attributes and Types 2 3This document describes how to define dialect 4[attributes](LangRef.md/#attributes) and [types](LangRef.md/#type-system). 5 6[TOC] 7 8## LangRef Refresher 9 10Before diving into how to define these constructs, below is a quick refresher 11from the [MLIR LangRef](LangRef.md). 12 13### Attributes 14 15Attributes are the mechanism for specifying constant data on operations in 16places where a variable is never allowed - e.g. the comparison predicate of a 17[`arith.cmpi` operation](Dialects/ArithmeticOps.md#arithcmpi-mlirarithcmpiop), or 18the underlying value of a [`arith.constant` operation](Dialects/ArithmeticOps.md#arithconstant-mlirarithconstantop). 19Each operation has an attribute dictionary, which associates a set of attribute 20names to attribute values. 21 22### Types 23 24Every SSA value, such as operation results or block arguments, in MLIR has a type 25defined by the type system. MLIR has an open type system with no fixed list of types, 26and there are no restrictions on the abstractions they represent. For example, take 27the following [Arithmetic AddI operation](Dialects/ArithmeticOps.md#arithaddi-mlirarithaddiop): 28 29```mlir 30 %result = arith.addi %lhs, %rhs : i64 31``` 32 33It takes two input SSA values (`%lhs` and `%rhs`), and returns a single SSA 34value (`%result`). The inputs and outputs of this operation are of type `i64`, 35which is an instance of the [Builtin IntegerType](Dialects/Builtin.md#integertype). 36 37## Attributes and Types 38 39The C++ Attribute and Type classes in MLIR (like Ops, and many other things) are 40value-typed. This means that instances of `Attribute` or `Type` are passed 41around 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 43within an instance of an `MLIRContext`. 44 45The structure for defining Attributes and Types is nearly identical, with only a 46few differences depending on the context. As such, a majority of this document 47describes the process for defining both Attributes and Types side-by-side with 48examples for both. If necessary, a section will explicitly call out any 49distinct differences. 50 51### Adding a new Attribute or Type definition 52 53As described above, C++ Attribute and Type objects in MLIR are value-typed and 54essentially function as helpful wrappers around an internal storage object that 55holds the actual data for the type. Similarly to Operations, Attributes and Types 56are defined declaratively via [TableGen](https://llvm.org/docs/TableGen/index.html); 57a generic language with tooling to maintain records of domain-specific information. 58It is highly recommended that users review the 59[TableGen Programmer's Reference](https://llvm.org/docs/TableGen/ProgRef.html) 60for an introduction to its syntax and constructs. 61 62Starting the definition of a new attribute or type simply requires adding a 63specialization for either the `AttrDef` or `TypeDef` class respectively. Instances 64of the classes correspond to unqiue Attribute or Type classes. 65 66Below show cases an example Attribute and Type definition. We generally recommend 67defining Attribute and Type classes in different `.td` files to better encapsulate 68the different constructs, and define a proper layering between them. This 69recommendation extends to all of the MLIR constructs, including [Interfaces](Interfaces.md), 70Operations, etc. 71 72```tablegen 73// Include the definition of the necessary tablegen constructs for defining 74// our types. 75include "mlir/IR/AttrTypeBase.td" 76 77// It's common to define a base classes for types in the same dialect. This 78// removes the need to pass in the dialect for each type, and can also be used 79// to define a few fields ahead of time. 80class MyDialect_Type<string name, string typeMnemonic, list<Trait> traits = []> 81 : TypeDef<My_Dialect, name, traits> { 82 let mnemonic = typeMnemonic; 83} 84 85// Here is a simple definition of an "integer" type, with a width parameter. 86def My_IntegerType : MyDialect_Type<"Integer", "int"> { 87 let summary = "Integer type with arbitrary precision up to a fixed limit"; 88 let description = [{ 89 Integer types have a designated bit width. 90 }]; 91 /// Here we defined a single parameter for the type, which is the bitwidth. 92 let parameters = (ins "unsigned":$width); 93 94 /// Here we define the textual format of the type declaratively, which will 95 /// automatically generate parser and printer logic. This will allow for 96 /// instances of the type to be output as, for example: 97 /// 98 /// !my.int<10> // a 10-bit integer. 99 /// 100 let assemblyFormat = "`<` $width `>`"; 101 102 /// Indicate that our type will add additional verification to the parameters. 103 let genVerifyDecl = 1; 104} 105``` 106 107Below is an example of an Attribute: 108 109```tablegen 110// Include the definition of the necessary tablegen constructs for defining 111// our attributes. 112include "mlir/IR/AttrTypeBase.td" 113 114// It's common to define a base classes for attributes in the same dialect. This 115// removes the need to pass in the dialect for each attribute, and can also be used 116// to define a few fields ahead of time. 117class MyDialect_Attr<string name, string attrMnemonic, list<Trait> traits = []> 118 : AttrDef<My_Dialect, name, traits> { 119 let mnemonic = attrMnemonic; 120} 121 122// Here is a simple definition of an "integer" attribute, with a type and value parameter. 123def My_IntegerAttr : MyDialect_Attr<"Integer", "int"> { 124 let summary = "An Attribute containing a integer value"; 125 let description = [{ 126 An integer attribute is a literal attribute that represents an integral 127 value of the specified integer type. 128 }]; 129 /// Here we've defined two parameters, one is the `self` type of the attribute 130 /// (i.e. the type of the Attribute itself), and the other is the integer value 131 /// of the attribute. 132 let parameters = (ins AttributeSelfTypeParameter<"">:$type, "APInt":$value); 133 134 /// Here we've defined a custom builder for the type, that removes the need to pass 135 /// in an MLIRContext instance; as it can be infered from the `type`. 136 let builders = [ 137 AttrBuilderWithInferredContext<(ins "Type":$type, 138 "const APInt &":$value), [{ 139 return $_get(type.getContext(), type, value); 140 }]> 141 ]; 142 143 /// Here we define the textual format of the attribute declaratively, which will 144 /// automatically generate parser and printer logic. This will allow for 145 /// instances of the attribute to be output as, for example: 146 /// 147 /// #my.int<50> : !my.int<32> // a 32-bit integer of value 50. 148 /// 149 let assemblyFormat = "`<` $value `>`"; 150 151 /// Indicate that our attribute will add additional verification to the parameters. 152 let genVerifyDecl = 1; 153 154 /// Indicate to the ODS generator that we do not want the default builders, 155 /// as we have defined our own simpler ones. 156 let skipDefaultBuilders = 1; 157} 158``` 159 160### Class Name 161 162The name of the C++ class which gets generated defaults to 163`<classParamName>Attr` or `<classParamName>Type` for attributes and types 164respectively. In the examples above, this was the `name` template parameter that 165was provided to `MyDialect_Attr` and `MyDialect_Type`. For the definitions we 166added above, we would get C++ classes named `IntegerType` and `IntegerAttr` 167respectively. This can be explicitly overridden via the `cppClassName` field. 168 169### Documentation 170 171The `summary` and `description` fields allow for providing user documentation 172for the attribute or type. The `summary` field expects a simple single-line 173string, with the `description` field used for long and extensive documentation. 174This documentation can be used to generate markdown documentation for the 175dialect and is used by upstream 176[MLIR dialects](https://mlir.llvm.org/docs/Dialects/). 177 178### Mnemonic 179 180The `mnemonic` field, i.e. the template parameters `attrMnemonic` and 181`typeMnemonic` we specified above, are used to specify a name for use during 182parsing. This allows for more easily dispatching to the current attribute or 183type class when parsing IR. This field is generally optional, and custom 184parsing/printing logic can be added without defining it, though most classes 185will want to take advantage of the convenience it provides. This is why we 186added it as a template parameter in the examples above. 187 188### Parameters 189 190The `parameters` field is a variable length list containing the attribute or 191type's parameters. If no parameters are specified (the default), this type is 192considered a singleton type (meaning there is only one possible instance). 193Parameters in this list take the form: `"c++Type":$paramName`. Parameter types 194with a C++ type that requires allocation when constructing the storage instance 195in the context require one of the following: 196 197- Utilize the `AttrParameter` or `TypeParameter` classes instead of the raw 198 "c++Type" string. This allows for providing custom allocation code when using 199 that parameter. `StringRefParameter` and `ArrayRefParameter` are examples of 200 common parameter types that require allocation. 201- Set the `genAccessors` field to 1 (the default) to generate accessor methods 202 for each parameter (e.g. `int getWidth() const` in the Type example above). 203- Set the `hasCustomStorageConstructor` field to `1` to generate a storage class 204 that only declares the constructor, allowing for you to specialize it with 205 whatever allocation code necessary. 206 207#### AttrParameter, TypeParameter, and AttrOrTypeParameter 208 209As hinted at above, these classes allow for specifying parameter types with 210additional functionality. This is generally useful for complex parameters, or those 211with additional invariants that prevent using the raw C++ class. Examples 212include documentation (e.g. the `summary` and `syntax` field), the C++ type, a 213custom allocator to use in the storage constructor method, a custom comparator 214to decide if two instances of the parameter type are equal, etc. As the names 215may suggest, `AttrParameter` is intended for parameters on Attributes, 216`TypeParameter` for Type parameters, and `AttrOrTypeParameters` for either. 217 218Below is an easy parameter pitfall, and highlights when to use these parameter 219classes. 220 221```tablegen 222let parameters = (ins "ArrayRef<int>":$dims); 223``` 224 225The above seems innocuous, but it is often a bug! The default storage 226constructor blindly copies parameters by value. It does not know anything about 227the types, meaning that the data of this ArrayRef will be copied as-is and is 228likely to lead to use-after-free errors when using the created Attribute or 229Type if the underlying does not have a lifetime exceeding that of the MLIRContext. 230If the lifetime of the data can't be guaranteed, the `ArrayRef<int>` requires 231allocation to ensure that its elements reside within the MLIRContext, e.g. with 232`dims = allocator.copyInto(dims)`. 233 234Here is a simple example for the exact situation above: 235 236```tablegen 237def ArrayRefIntParam : TypeParameter<"::llvm::ArrayRef<int>", "Array of int"> { 238 let allocator = "$_dst = $_allocator.copyInto($_self);"; 239} 240 241The parameter can then be used as so: 242 243... 244let parameters = (ins ArrayRefIntParam:$dims); 245``` 246 247Below contains descriptions for other various available fields: 248 249The `allocator` code block has the following substitutions: 250 251- `$_allocator` is the TypeStorageAllocator in which to allocate objects. 252- `$_dst` is the variable in which to place the allocated data. 253 254The `comparator` code block has the following substitutions: 255 256- `$_lhs` is an instance of the parameter type. 257- `$_rhs` is an instance of the parameter type. 258 259MLIR includes several specialized classes for common situations: 260 261- `APFloatParameter` for APFloats. 262 263- `StringRefParameter<descriptionOfParam>` for StringRefs. 264 265- `ArrayRefParameter<arrayOf, descriptionOfParam>` for ArrayRefs of value types. 266 267- `SelfAllocationParameter<descriptionOfParam>` for C++ classes which contain a 268 method called `allocateInto(StorageAllocator &allocator)` to allocate itself 269 into `allocator`. 270 271- `ArrayRefOfSelfAllocationParameter<arrayOf, descriptionOfParam>` for arrays of 272 objects which self-allocate as per the last specialization. 273 274- `AttributeSelfTypeParameter` is a special AttrParameter that corresponds to 275 the `Type` of the attribute. Only one parameter of the attribute may be of 276 this parameter type. 277 278### Traits 279 280Similarly to operations, Attribute and Type classes may attach `Traits` that 281provide additional mixin methods and other data. `Trait`s may be attached via 282the trailing template argument, i.e. the `traits` list parameter in the example 283above. See the main [`Trait`](Traits.md) documentation for more information 284on defining and using traits. 285 286### Interfaces 287 288Attribute and Type classes may attach `Interfaces` to provide an virtual 289interface into the Attribute or Type. `Interfaces` are added in the same way as 290[Traits](#Traits), by using the `traits` list template parameter of the 291`AttrDef` or `TypeDef`. See the main [`Interface`](Interfaces.md) 292documentation for more information on defining and using interfaces. 293 294### Builders 295 296For each attribute or type, there are a few builders(`get`/`getChecked`) 297automatically generated based on the parameters of the type. These are used to 298construct instances of the corresponding attribute or type. For example, given 299the following definition: 300 301```tablegen 302def MyAttrOrType : ... { 303 let parameters = (ins "int":$intParam); 304} 305``` 306 307The following builders are generated: 308 309```c++ 310// Builders are named `get`, and return a new instance for a given set of parameters. 311static MyAttrOrType get(MLIRContext *context, int intParam); 312 313// If `genVerifyDecl` is set to 1, the following method is also generated. This method 314// is similar to `get`, but is failable and on error will return nullptr. 315static MyAttrOrType getChecked(function_ref<InFlightDiagnostic()> emitError, 316 MLIRContext *context, int intParam); 317``` 318 319If these autogenerated methods are not desired, such as when they conflict with 320a custom builder method, the `skipDefaultBuilders` field may be set to 1 to 321signal that the default builders should not be generated. 322 323#### Custom builder methods 324 325The default builder methods may cover a majority of the simple cases related to 326construction, but when they cannot satisfy all of an attribute or type's needs, 327additional builders may be defined via the `builders` field. The `builders` 328field is a list of custom builders, either using `TypeBuilder` for types or 329`AttrBuilder` for attributes, that are added to the attribute or type class. The 330following will showcase several examples for defining builders for a custom type 331`MyType`, the process is the same for attributes except that attributes use 332`AttrBuilder` instead of `TypeBuilder`. 333 334```tablegen 335def MyType : ... { 336 let parameters = (ins "int":$intParam); 337 338 let builders = [ 339 TypeBuilder<(ins "int":$intParam)>, 340 TypeBuilder<(ins CArg<"int", "0">:$intParam)>, 341 TypeBuilder<(ins CArg<"int", "0">:$intParam), [{ 342 // Write the body of the `get` builder inline here. 343 return Base::get($_ctxt, intParam); 344 }]>, 345 TypeBuilderWithInferredContext<(ins "Type":$typeParam), [{ 346 // This builder states that it can infer an MLIRContext instance from 347 // its arguments. 348 return Base::get(typeParam.getContext(), ...); 349 }]>, 350 TypeBuilder<(ins "int":$intParam), [{}], "IntegerType">, 351 ]; 352} 353``` 354 355In this example, we provide several different convenience builders that are 356useful in different scenarios. The `ins` prefix is common to many function 357declarations in ODS, which use a TableGen [`dag`](#tablegen-syntax). What 358follows is a comma-separated list of types (quoted string or `CArg`) and names 359prefixed with the `$` sign. The use of `CArg` allows for providing a default 360value to that argument. Let's take a look at each of these builders individually 361 362The first builder will generate the declaration of a builder method that looks 363like: 364 365```tablegen 366 let builders = [ 367 TypeBuilder<(ins "int":$intParam)>, 368 ]; 369``` 370 371```c++ 372class MyType : /*...*/ { 373 /*...*/ 374 static MyType get(::mlir::MLIRContext *context, int intParam); 375}; 376``` 377 378This builder is identical to the one that will be automatically generated for 379`MyType`. The `context` parameter is implicitly added by the generator, and is 380used when building the Type instance (with `Base::get`). The distinction here is 381that we can provide the implementation of this `get` method. With this style of 382builder definition only the declaration is generated, the implementor of 383`MyType` will need to provide a definition of `MyType::get`. 384 385The second builder will generate the declaration of a builder method that looks 386like: 387 388```tablegen 389 let builders = [ 390 TypeBuilder<(ins CArg<"int", "0">:$intParam)>, 391 ]; 392``` 393 394```c++ 395class MyType : /*...*/ { 396 /*...*/ 397 static MyType get(::mlir::MLIRContext *context, int intParam = 0); 398}; 399``` 400 401The constraints here are identical to the first builder example except for the 402fact that `intParam` now has a default value attached. 403 404The third builder will generate the declaration of a builder method that looks 405like: 406 407```tablegen 408 let builders = [ 409 TypeBuilder<(ins CArg<"int", "0">:$intParam), [{ 410 // Write the body of the `get` builder inline here. 411 return Base::get($_ctxt, intParam); 412 }]>, 413 ]; 414``` 415 416```c++ 417class MyType : /*...*/ { 418 /*...*/ 419 static MyType get(::mlir::MLIRContext *context, int intParam = 0); 420}; 421 422MyType MyType::get(::mlir::MLIRContext *context, int intParam) { 423 // Write the body of the `get` builder inline here. 424 return Base::get(context, intParam); 425} 426``` 427 428This is identical to the second builder example. The difference is that now, a 429definition for the builder method will be generated automatically using the 430provided code block as the body. When specifying the body inline, `$_ctxt` may 431be used to access the `MLIRContext *` parameter. 432 433The fourth builder will generate the declaration of a builder method that looks 434like: 435 436```tablegen 437 let builders = [ 438 TypeBuilderWithInferredContext<(ins "Type":$typeParam), [{ 439 // This builder states that it can infer an MLIRContext instance from 440 // its arguments. 441 return Base::get(typeParam.getContext(), ...); 442 }]>, 443 ]; 444``` 445 446```c++ 447class MyType : /*...*/ { 448 /*...*/ 449 static MyType get(Type typeParam); 450}; 451 452MyType MyType::get(Type typeParam) { 453 // This builder states that it can infer an MLIRContext instance from its 454 // arguments. 455 return Base::get(typeParam.getContext(), ...); 456} 457``` 458 459In this builder example, the main difference from the third builder example 460there is that the `MLIRContext` parameter is no longer added. This is because 461the builder used `TypeBuilderWithInferredContext` implies that the context 462parameter is not necessary as it can be inferred from the arguments to the 463builder. 464 465The fifth builder will generate the declaration of a builder method with a 466custom return type, like: 467 468```tablegen 469 let builders = [ 470 TypeBuilder<(ins "int":$intParam), [{}], "IntegerType">, 471 ] 472``` 473 474```c++ 475class MyType : /*...*/ { 476 /*...*/ 477 static IntegerType get(::mlir::MLIRContext *context, int intParam); 478 479}; 480``` 481 482This generates a builder declaration the same as the first three examples, but 483the return type of the builder is user-specified instead of the attribute or 484type class. This is useful for defining builders of attributes and types that 485may fold or canonicalize on construction. 486 487### Parsing and Printing 488 489If a mnemonic was specified, the `hasCustomAssemblyFormat` and `assemblyFormat` 490fields may be used to specify the assembly format of an attribute or type. Attributes 491and Types with no parameters need not use either of these fields, in which case 492the syntax for the Attribute or Type is simply the mnemonic. 493 494For each dialect, two "dispatch" functions will be created: one for parsing and 495one for printing. These static functions placed alongside the class definitions 496and have the following function signatures: 497 498```c++ 499static ParseResult generatedAttributeParser(DialectAsmParser& parser, StringRef *mnemonic, Type attrType, Attribute &result); 500static LogicalResult generatedAttributePrinter(Attribute attr, DialectAsmPrinter& printer); 501 502static ParseResult generatedTypeParser(DialectAsmParser& parser, StringRef *mnemonic, Type &result); 503static LogicalResult generatedTypePrinter(Type type, DialectAsmPrinter& printer); 504``` 505 506The above functions should be added to the respective in your 507`Dialect::printType` and `Dialect::parseType` methods, or consider using the 508`useDefaultAttributePrinterParser` and `useDefaultTypePrinterParser` ODS Dialect 509options if all attributes or types define a mnemonic. 510 511The mnemonic, hasCustomAssemblyFormat, and assemblyFormat fields are optional. 512If none are defined, the generated code will not include any parsing or printing 513code and omit the attribute or type from the dispatch functions above. In this 514case, the dialect author is responsible for parsing/printing in the respective 515`Dialect::parseAttribute`/`Dialect::printAttribute` and 516`Dialect::parseType`/`Dialect::printType` methods. 517 518#### Using `hasCustomAssemblyFormat` 519 520Attributes and types defined in ODS with a mnemonic can define an 521`hasCustomAssemblyFormat` to specify custom parsers and printers defined in C++. 522When set to `1` a corresponding `parse` and `print` method will be declared on 523the Attribute or Type class to be defined by the user. 524 525For Types, these methods will have the form: 526 527- `static Type MyType::parse(AsmParser &parser)` 528 529- `Type MyType::print(AsmPrinter &p) const` 530 531For Attributes, these methods will have the form: 532 533- `static Attribute MyAttr::parse(AsmParser &parser, Type attrType)` 534 535- `Attribute MyAttr::print(AsmPrinter &p) const` 536 537#### Using `assemblyFormat` 538 539Attributes and types defined in ODS with a mnemonic can define an 540`assemblyFormat` to declaratively describe custom parsers and printers. The 541assembly format consists of literals, variables, and directives. 542 543- A literal is a keyword or valid punctuation enclosed in backticks, e.g. 544 `` `keyword` `` or `` `<` ``. 545- A variable is a parameter name preceded by a dollar sign, e.g. `$param0`, 546 which captures one attribute or type parameter. 547- A directive is a keyword followed by an optional argument list that defines 548 special parser and printer behaviour. 549 550```tablegen 551// An example type with an assembly format. 552def MyType : TypeDef<My_Dialect, "MyType"> { 553 // Define a mnemonic to allow the dialect's parser hook to call into the 554 // generated parser. 555 let mnemonic = "my_type"; 556 557 // Define two parameters whose C++ types are indicated in string literals. 558 let parameters = (ins "int":$count, "AffineMap":$map); 559 560 // Define the assembly format. Surround the format with less `<` and greater 561 // `>` so that MLIR's printer uses the pretty format. 562 let assemblyFormat = "`<` $count `,` `map` `=` $map `>`"; 563} 564``` 565 566The declarative assembly format for `MyType` results in the following format in 567the IR: 568 569```mlir 570!my_dialect.my_type<42, map = affine_map<(i, j) -> (j, i)>> 571``` 572 573##### Parameter Parsing and Printing 574 575For many basic parameter types, no additional work is needed to define how these 576parameters are parsed or printed. 577 578- The default printer for any parameter is `$_printer << $_self`, where `$_self` 579 is the C++ value of the parameter and `$_printer` is an `AsmPrinter`. 580- The default parser for a parameter is 581 `FieldParser<$cppClass>::parse($_parser)`, where `$cppClass` is the C++ type 582 of the parameter and `$_parser` is an `AsmParser`. 583 584Printing and parsing behaviour can be added to additional C++ types by 585overloading these functions or by defining a `parser` and `printer` in an ODS 586parameter class. 587 588Example of overloading: 589 590```c++ 591using MyParameter = std::pair<int, int>; 592 593AsmPrinter &operator<<(AsmPrinter &printer, MyParameter param) { 594 printer << param.first << " * " << param.second; 595} 596 597template <> struct FieldParser<MyParameter> { 598 static FailureOr<MyParameter> parse(AsmParser &parser) { 599 int a, b; 600 if (parser.parseInteger(a) || parser.parseStar() || 601 parser.parseInteger(b)) 602 return failure(); 603 return MyParameter(a, b); 604 } 605}; 606``` 607 608Example of using ODS parameter classes: 609 610```tablegen 611def MyParameter : TypeParameter<"std::pair<int, int>", "pair of ints"> { 612 let printer = [{ $_printer << $_self.first << " * " << $_self.second }]; 613 let parser = [{ [&] -> FailureOr<std::pair<int, int>> { 614 int a, b; 615 if ($_parser.parseInteger(a) || $_parser.parseStar() || 616 $_parser.parseInteger(b)) 617 return failure(); 618 return std::make_pair(a, b); 619 }() }]; 620} 621``` 622 623A type using this parameter with the assembly format `` `<` $myParam `>` `` will 624look as follows in the IR: 625 626```mlir 627!my_dialect.my_type<42 * 24> 628``` 629 630###### Non-POD Parameters 631 632Parameters that aren't plain-old-data (e.g. references) may need to define a 633`cppStorageType` to contain the data until it is copied into the allocator. For 634example, `StringRefParameter` uses `std::string` as its storage type, whereas 635`ArrayRefParameter` uses `SmallVector` as its storage type. The parsers for 636these parameters are expected to return `FailureOr<$cppStorageType>`. 637 638To add a custom conversion between the `cppStorageType` and the C++ type of the 639parameter, parameters can override `convertFromStorage`, which by default is 640`"$_self"` (i.e., it attempts an implicit conversion from `cppStorageType`). 641 642###### Optional Parameters 643 644Optional parameters in the assembly format can be indicated by setting 645`isOptional`. The C++ type of an optional parameter is required to satisfy the 646following requirements: 647 648- is default-constructible 649- is contextually convertible to `bool` 650- only the default-constructed value is `false` 651 652The parameter parser should return the default-constructed value to indicate "no 653value present". The printer will guard on the presence of a value to print the 654parameter. 655 656If a value was not parsed for an optional parameter, then the parameter will be 657set to its default-constructed C++ value. For example, `Optional<int>` will be 658set to `llvm::None` and `Attribute` will be set to `nullptr`. 659 660Only optional parameters or directives that only capture optional parameters can 661be used in optional groups. An optional group is a set of elements optionally 662printed based on the presence of an anchor. Suppose parameter `a` is an 663`IntegerAttr`. 664 665``` 666( `(` $a^ `)` ) : (`x`)? 667``` 668 669In the above assembly format, if `a` is present (non-null), then it will be 670printed as `(5 : i32)`. If it is not present, it will be `x`. Directives that 671are used inside optional groups are allowed only if all captured parameters are 672also optional. 673 674###### Default-Valued Parameters 675 676Optional parameters can be given default values by setting `defaultValue`, a 677string of the C++ default value, or by using `DefaultValuedParameter`. If a 678value for the parameter was not encountered during parsing, it is set to this 679default value. If a parameter is equal to its default value, it is not printed. 680The `comparator` field of the parameter is used, but if one is not specified, 681the equality operator is used. 682 683For example: 684 685```tablegen 686let parameters = (ins DefaultValuedParameter<"Optional<int>", "5">:$a) 687let mnemonic = "default_valued"; 688let assemblyFormat = "(`<` $a^ `>`)?"; 689``` 690 691Which will look like: 692 693```mlir 694!test.default_valued // a = 5 695!test.default_valued<10> // a = 10 696``` 697 698For optional `Attribute` or `Type` parameters, the current MLIR context is 699available through `$_ctxt`. E.g. 700 701```tablegen 702DefaultValuedParameter<"IntegerType", "IntegerType::get($_ctxt, 32)"> 703``` 704 705##### Assembly Format Directives 706 707Attribute and type assembly formats have the following directives: 708 709- `params`: capture all parameters of an attribute or type. 710- `qualified`: mark a parameter to be printed with its leading dialect and 711 mnemonic. 712- `struct`: generate a "struct-like" parser and printer for a list of key-value 713 pairs. 714- `custom`: dispatch a call to user-define parser and printer functions 715- `ref`: in a custom directive, references a previously bound variable 716 717###### `params` Directive 718 719This directive is used to refer to all parameters of an attribute or type. When 720used as a top-level directive, `params` generates a parser and printer for a 721comma-separated list of the parameters. For example: 722 723```tablegen 724def MyPairType : TypeDef<My_Dialect, "MyPairType"> { 725 let parameters = (ins "int":$a, "int":$b); 726 let mnemonic = "pair"; 727 let assemblyFormat = "`<` params `>`"; 728} 729``` 730 731In the IR, this type will appear as: 732 733```mlir 734!my_dialect.pair<42, 24> 735``` 736 737The `params` directive can also be passed to other directives, such as `struct`, 738as an argument that refers to all parameters in place of explicitly listing all 739parameters as variables. 740 741###### `qualified` Directive 742 743This directive can be used to wrap attribute or type parameters such that they 744are printed in a fully qualified form, i.e., they include the dialect name and 745mnemonic prefix. 746 747For example: 748 749```tablegen 750def OuterType : TypeDef<My_Dialect, "MyOuterType"> { 751 let parameters = (ins MyPairType:$inner); 752 let mnemonic = "outer"; 753 let assemblyFormat = "`<` pair `:` $inner `>`"; 754} 755def OuterQualifiedType : TypeDef<My_Dialect, "MyOuterQualifiedType"> { 756 let parameters = (ins MyPairType:$inner); 757 let mnemonic = "outer_qual"; 758 let assemblyFormat = "`<` pair `:` qualified($inner) `>`"; 759} 760``` 761 762In the IR, the types will appear as: 763 764```mlir 765!my_dialect.outer<pair : <42, 24>> 766!my_dialect.outer_qual<pair : !mydialect.pair<42, 24>> 767``` 768 769If optional parameters are present, they are not printed in the parameter list 770if they are not present. 771 772###### `struct` Directive 773 774The `struct` directive accepts a list of variables to capture and will generate 775a parser and printer for a comma-separated list of key-value pairs. If an 776optional parameter is included in the `struct`, it can be elided. The variables 777are printed in the order they are specified in the argument list **but can be 778parsed in any order**. For example: 779 780```tablegen 781def MyStructType : TypeDef<My_Dialect, "MyStructType"> { 782 let parameters = (ins StringRefParameter<>:$sym_name, 783 "int":$a, "int":$b, "int":$c); 784 let mnemonic = "struct"; 785 let assemblyFormat = "`<` $sym_name `->` struct($a, $b, $c) `>`"; 786} 787``` 788 789In the IR, this type can appear with any permutation of the order of the 790parameters captured in the directive. 791 792```mlir 793!my_dialect.struct<"foo" -> a = 1, b = 2, c = 3> 794!my_dialect.struct<"foo" -> b = 2, c = 3, a = 1> 795``` 796 797Passing `params` as the only argument to `struct` makes the directive capture 798all the parameters of the attribute or type. For the same type above, an 799assembly format of `` `<` struct(params) `>` `` will result in: 800 801```mlir 802!my_dialect.struct<b = 2, sym_name = "foo", c = 3, a = 1> 803``` 804 805The order in which the parameters are printed is the order in which they are 806declared in the attribute's or type's `parameter` list. 807 808###### `custom` and `ref` directive 809 810The `custom` directive is used to dispatch calls to user-defined printer and 811parser functions. For example, suppose we had the following type: 812 813```tablegen 814let parameters = (ins "int":$foo, "int":$bar); 815let assemblyFormat = "custom<Foo>($foo) custom<Bar>($bar, ref($foo))"; 816``` 817 818The `custom` directive `custom<Foo>($foo)` will in the parser and printer 819respectively generate calls to: 820 821```c++ 822LogicalResult parseFoo(AsmParser &parser, FailureOr<int> &foo); 823void printFoo(AsmPrinter &printer, int foo); 824``` 825 826A previously bound variable can be passed as a parameter to a `custom` directive 827by wrapping it in a `ref` directive. In the previous example, `$foo` is bound by 828the first directive. The second directive references it and expects the 829following printer and parser signatures: 830 831```c++ 832LogicalResult parseBar(AsmParser &parser, FailureOr<int> &bar, int foo); 833void printBar(AsmPrinter &printer, int bar, int foo); 834``` 835 836More complex C++ types can be used with the `custom` directive. The only caveat 837is that the parameter for the parser must use the storage type of the parameter. 838For example, `StringRefParameter` expects the parser and printer signatures as: 839 840```c++ 841LogicalResult parseStringParam(AsmParser &parser, 842 FailureOr<std::string> &value); 843void printStringParam(AsmPrinter &printer, StringRef value); 844``` 845 846The custom parser is considered to have failed if it returns failure or if any 847bound parameters have failure values afterwards. 848 849### Verification 850 851If the `genVerifyDecl` field is set, additional verification methods are 852generated on the class. 853 854- `static LogicalResult verify(function_ref<InFlightDiagnostic()> emitError, parameters...)` 855 856These methods are used to verify the parameters provided to the attribute or 857type class on construction, and emit any necessary diagnostics. This method is 858automatically invoked from the builders of the attribute or type class. 859 860- `AttrOrType getChecked(function_ref<InFlightDiagnostic()> emitError, parameters...)` 861 862As noted in the [Builders](#Builders) section, these methods are companions to 863`get` builders that are failable. If the `verify` invocation fails when these 864methods are called, they return nullptr instead of asserting. 865 866### Storage Classes 867 868Somewhat alluded to in the sections above is the concept of a "storage class" 869(often abbreviated to "storage"). Storage classes contain all of the data 870necessary to construct and unique a attribute or type instance. These classes 871are the "immortal" objects that get uniqued within an MLIRContext and get 872wrapped by the `Attribute` and `Type` classes. Every Attribute or Type class has 873a corresponding storage class, that can be accessed via the protected 874`getImpl()` method. 875 876In most cases the storage class is auto generated, but if necessary it can be 877manually defined by setting the `genStorageClass` field to 0. The name and 878namespace (defaults to `detail`) can additionally be controlled via the The 879`storageClass` and `storageNamespace` fields. 880 881#### Defining a storage class 882 883User defined storage classes must adhere to the following: 884 885- Inherit from the base type storage class of `AttributeStorage` or 886 `TypeStorage` respectively. 887- Define a type alias, `KeyTy`, that maps to a type that uniquely identifies an 888 instance of the derived type. For example, this could be a `std::tuple` of all 889 of the storage parameters. 890- Provide a construction method that is used to allocate a new instance of the 891 storage class. 892 - `static Storage *construct(StorageAllocator &allocator, const KeyTy &key)` 893- Provide a comparison method between an instance of the storage and the 894 `KeyTy`. 895 - `bool operator==(const KeyTy &) const` 896- Provide a method to generate the `KeyTy` from a list of arguments passed to 897 the uniquer when building an Attribute or Type. (Note: This is only necessary 898 if the `KeyTy` cannot be default constructed from these arguments). 899 - `static KeyTy getKey(Args...&& args)` 900- Provide a method to hash an instance of the `KeyTy`. (Note: This is not 901 necessary if an `llvm::DenseMapInfo<KeyTy>` specialization exists) 902 - `static llvm::hash_code hashKey(const KeyTy &)` 903 904Let's look at an example: 905 906```c++ 907/// Here we define a storage class for a ComplexType, that holds a non-zero 908/// integer and an integer type. 909struct ComplexTypeStorage : public TypeStorage { 910 ComplexTypeStorage(unsigned nonZeroParam, Type integerType) 911 : nonZeroParam(nonZeroParam), integerType(integerType) {} 912 913 /// The hash key for this storage is a pair of the integer and type params. 914 using KeyTy = std::pair<unsigned, Type>; 915 916 /// Define the comparison function for the key type. 917 bool operator==(const KeyTy &key) const { 918 return key == KeyTy(nonZeroParam, integerType); 919 } 920 921 /// Define a hash function for the key type. 922 /// Note: This isn't necessary because std::pair, unsigned, and Type all have 923 /// hash functions already available. 924 static llvm::hash_code hashKey(const KeyTy &key) { 925 return llvm::hash_combine(key.first, key.second); 926 } 927 928 /// Define a construction function for the key type. 929 /// Note: This isn't necessary because KeyTy can be directly constructed with 930 /// the given parameters. 931 static KeyTy getKey(unsigned nonZeroParam, Type integerType) { 932 return KeyTy(nonZeroParam, integerType); 933 } 934 935 /// Define a construction method for creating a new instance of this storage. 936 static ComplexTypeStorage *construct(StorageAllocator &allocator, const KeyTy &key) { 937 return new (allocator.allocate<ComplexTypeStorage>()) 938 ComplexTypeStorage(key.first, key.second); 939 } 940 941 /// The parametric data held by the storage class. 942 unsigned nonZeroParam; 943 Type integerType; 944}; 945``` 946 947### Mutable attributes and types 948 949Attributes and Types are immutable objects uniqued within an MLIRContext. That 950being said, some parameters may be treated as "mutable" and modified after 951construction. Mutable parameters should be reserved for parameters that can not 952be reasonably initialized during construction time. Given the mutable component, 953these parameters do not take part in the uniquing of the Attribute or Type. 954 955TODO: Mutable parameters are currently not supported in the declarative 956specification of attributes and types, and thus requires defining the Attribute 957or Type class in C++. 958 959#### Defining a mutable storage 960 961In addition to the base requirements for a storage class, instances with a 962mutable component must additionally adhere to the following: 963 964- The mutable component must not participate in the storage `KeyTy`. 965- Provide a mutation method that is used to modify an existing instance of the 966 storage. This method modifies the mutable component based on arguments, using 967 `allocator` for any newly dynamically-allocated storage, and indicates whether 968 the modification was successful. 969 - `LogicalResult mutate(StorageAllocator &allocator, Args ...&& args)` 970 971Let's define a simple storage for recursive types, where a type is identified by 972its name and may contain another type including itself. 973 974```c++ 975/// Here we define a storage class for a RecursiveType that is identified by its 976/// name and contains another type. 977struct RecursiveTypeStorage : public TypeStorage { 978 /// The type is uniquely identified by its name. Note that the contained type 979 /// is _not_ a part of the key. 980 using KeyTy = StringRef; 981 982 /// Construct the storage from the type name. Explicitly initialize the 983 /// containedType to nullptr, which is used as marker for the mutable 984 /// component being not yet initialized. 985 RecursiveTypeStorage(StringRef name) : name(name), containedType(nullptr) {} 986 987 /// Define the comparison function. 988 bool operator==(const KeyTy &key) const { return key == name; } 989 990 /// Define a construction method for creating a new instance of the storage. 991 static RecursiveTypeStorage *construct(StorageAllocator &allocator, 992 const KeyTy &key) { 993 // Note that the key string is copied into the allocator to ensure it 994 // remains live as long as the storage itself. 995 return new (allocator.allocate<RecursiveTypeStorage>()) 996 RecursiveTypeStorage(allocator.copyInto(key)); 997 } 998 999 /// Define a mutation method for changing the type after it is created. In 1000 /// many cases, we only want to set the mutable component once and reject 1001 /// any further modification, which can be achieved by returning failure from 1002 /// this function. 1003 LogicalResult mutate(StorageAllocator &, Type body) { 1004 // If the contained type has been initialized already, and the call tries 1005 // to change it, reject the change. 1006 if (containedType && containedType != body) 1007 return failure(); 1008 1009 // Change the body successfully. 1010 containedType = body; 1011 return success(); 1012 } 1013 1014 StringRef name; 1015 Type containedType; 1016}; 1017``` 1018 1019#### Type class definition 1020 1021Having defined the storage class, we can define the type class itself. 1022`Type::TypeBase` provides a `mutate` method that forwards its arguments to the 1023`mutate` method of the storage and ensures the mutation happens safely. 1024 1025```c++ 1026class RecursiveType : public Type::TypeBase<RecursiveType, Type, 1027 RecursiveTypeStorage> { 1028public: 1029 /// Inherit parent constructors. 1030 using Base::Base; 1031 1032 /// Creates an instance of the Recursive type. This only takes the type name 1033 /// and returns the type with uninitialized body. 1034 static RecursiveType get(MLIRContext *ctx, StringRef name) { 1035 // Call into the base to get a uniqued instance of this type. The parameter 1036 // (name) is passed after the context. 1037 return Base::get(ctx, name); 1038 } 1039 1040 /// Now we can change the mutable component of the type. This is an instance 1041 /// method callable on an already existing RecursiveType. 1042 void setBody(Type body) { 1043 // Call into the base to mutate the type. 1044 LogicalResult result = Base::mutate(body); 1045 1046 // Most types expect the mutation to always succeed, but types can implement 1047 // custom logic for handling mutation failures. 1048 assert(succeeded(result) && 1049 "attempting to change the body of an already-initialized type"); 1050 1051 // Avoid unused-variable warning when building without assertions. 1052 (void) result; 1053 } 1054 1055 /// Returns the contained type, which may be null if it has not been 1056 /// initialized yet. 1057 Type getBody() { return getImpl()->containedType; } 1058 1059 /// Returns the name. 1060 StringRef getName() { return getImpl()->name; } 1061}; 1062``` 1063 1064### Extra declarations 1065 1066The declarative Attribute and Type definitions try to auto-generate as much 1067logic and methods as possible. With that said, there will always be long-tail 1068cases that won't be covered. For such cases, `extraClassDeclaration` and 1069`extraClassDefinition` can be used. Code within the `extraClassDeclaration` 1070field will be copied literally to the generated C++ Attribute or Type class. 1071Code within `extraClassDefinition` will be added to the generated source file 1072inside the class's C++ namespace. The substitution `$cppClass` will be replaced 1073by the Attribute or Type's C++ class name. 1074 1075Note that these are mechanisms intended for long-tail cases by power users; for 1076not-yet-implemented widely-applicable cases, improving the infrastructure is 1077preferable. 1078 1079### Registering with the Dialect 1080 1081Once the attributes and types have been defined, they must then be registered 1082with the parent `Dialect`. This is done via the `addAttributes` and `addTypes` 1083methods. Note that when registering, the full definition of the storage classes 1084must be visible. 1085 1086```c++ 1087void MyDialect::initialize() { 1088 /// Add the defined attributes to the dialect. 1089 addAttributes< 1090#define GET_ATTRDEF_LIST 1091#include "MyDialect/Attributes.cpp.inc" 1092 >(); 1093 1094 /// Add the defined types to the dialect. 1095 addTypes< 1096#define GET_TYPEDEF_LIST 1097#include "MyDialect/Types.cpp.inc" 1098 >(); 1099} 1100``` 1101