1# Operation Definition Specification (ODS) 2 3In addition to specializing the `mlir::Op` C++ template, MLIR also supports 4defining operations and data types in a table-driven manner. This is achieved 5via [TableGen][TableGen], which is both a generic language and its tooling to 6maintain records of domain-specific information. Facts regarding an operation 7are specified concisely into a TableGen record, which will be expanded into an 8equivalent `mlir::Op` C++ template specialization at compiler build time. 9 10This manual explains in detail all the available mechanisms for defining 11operations in such a table-driven manner. It aims to be a specification instead 12of a tutorial. Please refer to 13[Quickstart tutorial to adding MLIR graph rewrite](Tutorials/QuickstartRewrites.md) 14for the latter. 15 16In addition to detailing each mechanism, this manual also tries to capture best 17practices. They are rendered as quoted bullet points. 18 19## Motivation 20 21MLIR allows pluggable dialects, and dialects contain, among others, a list of 22operations. This open and extensible ecosystem leads to the "stringly" type IR 23problem, e.g., repetitive string comparisons during optimization and analysis 24passes, unintuitive accessor methods (e.g., generic/error prone `getOperand(3)` 25vs self-documenting `getStride()`) with more generic return types, verbose and 26generic constructors without default arguments, verbose textual IR dump, and so 27on. Furthermore, operation verification is: 28 291. best case: a central string-to-verification-function map, 301. middle case: duplication of verification across the code base, or 311. worst case: no verification functions. 32 33The fix is to support defining ops in a table-driven manner. Then for each 34dialect, we can have a central place that contains everything you need to know 35about each op, including its constraints, custom assembly form, etc. This 36description is also used to generate helper functions and classes to allow 37building, verification, parsing, printing, analysis, and many more. 38 39## Benefits 40 41Compared to the C++ template, this table-driven approach has several benefits 42including but not limited to: 43 44* **Single source of truth**: We strive to encode all facts regarding an 45 operation into the record, so that readers don't need to jump among code 46 snippets to fully understand an operation. 47* **Removing boilerplate**: We can automatically generate 48 operand/attribute/result getter methods, operation build methods, operation 49 verify methods, and many more utilities from the record. This greatly 50 reduces the boilerplate needed for defining a new op. 51* **Facilitating auto-generation**: The usage of these operation information 52 records are by no means limited to op definition itself. We can use them to 53 drive the auto-generation of many other components, like computation graph 54 serialization. 55 56## TableGen Syntax 57 58We use TableGen as the language for specifying operation information. TableGen 59itself just provides syntax for writing records; the syntax and constructs 60allowed in a TableGen file (typically with filename suffix `.td`) can be found 61[here][TableGenProgRef]. 62 63* TableGen `class` is similar to C++ class; it can be templated and 64 subclassed. 65* TableGen `def` is similar to C++ object; it can be declared by specializing 66 a TableGen `class` (e.g., `def MyDef : MyClass<...>;`) or completely 67 independently (e.g., `def MyDef;`). It cannot be further templated or 68 subclassed. 69* TableGen `dag` is a dedicated type for directed acyclic graph of elements. A 70 `dag` has one operator and zero or more arguments. Its syntax is `(operator 71 arg0, arg1, argN)`. The operator can be any TableGen `def`; an argument can 72 be anything, including `dag` itself. We can have names attached to both the 73 operator and the arguments like `(MyOp:$op_name MyArg:$arg_name)`. 74 75Please see the [language reference][TableGenProgRef] to learn about all the 76types and expressions supported by TableGen. 77 78## Operation Definition 79 80MLIR defines several common constructs to help operation definition and provide 81their semantics via a special [TableGen backend][TableGenBackend]: 82[`OpDefinitionsGen`][OpDefinitionsGen]. These constructs are defined in 83[`OpBase.td`][OpBase]. The main ones are 84 85* The `Op` class: It is the main construct for defining operations. All facts 86 regarding the operation are specified when specializing this class, with the 87 help of the following constructs. 88* The `Dialect` class: Operations belonging to one logical group are placed in 89 the same dialect. The `Dialect` class contains dialect-level information. 90* The `OpTrait` class hierarchy: They are used to specify special properties 91 and constraints of the operation, including whether the operation has side 92 effect or whether its output has the same shape as the input. 93* The `ins`/`outs` marker: These are two special markers builtin to the 94 `OpDefinitionsGen` backend. They lead the definitions of operands/attributes 95 and results respectively. 96* The `TypeConstraint` class hierarchy: They are used to specify the 97 constraints over operands or results. A notable subclass hierarchy is 98 `Type`, which stands for constraints for common C++ types. 99* The `AttrConstraint` class hierarchy: They are used to specify the 100 constraints over attributes. A notable subclass hierarchy is `Attr`, which 101 stands for constraints for attributes whose values are of common types. 102 103An operation is defined by specializing the `Op` class with concrete contents 104for all the fields it requires. For example, `tf.AvgPool` is defined as 105 106```tablegen 107def TF_AvgPoolOp : TF_Op<"AvgPool", [NoSideEffect]> { 108 let summary = "Performs average pooling on the input."; 109 110 let description = [{ 111Each entry in `output` is the mean of the corresponding size `ksize` 112window in `value`. 113 }]; 114 115 let arguments = (ins 116 TF_FpTensor:$value, 117 118 Confined<I64ArrayAttr, [ArrayMinCount<4>]>:$ksize, 119 Confined<I64ArrayAttr, [ArrayMinCount<4>]>:$strides, 120 TF_AnyStrAttrOf<["SAME", "VALID"]>:$padding, 121 DefaultValuedAttr<TF_ConvertDataFormatAttr, "NHWC">:$data_format 122 ); 123 124 let results = (outs 125 TF_FpTensor:$output 126 ); 127 128 TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; 129} 130``` 131 132In the following we describe all the fields needed. Please see the definition of 133the `Op` class for the complete list of fields supported. 134 135### Operation name 136 137The operation name is a unique identifier of the operation within MLIR, e.g., 138`tf.Add` for addition operation in the TensorFlow dialect. This is the 139equivalent of the mnemonic in assembly language. It is used for parsing and 140printing in the textual format. It is also used for pattern matching in graph 141rewrites. 142 143The full operation name is composed of the dialect name and the op name, with 144the former provided via the dialect and the latter provided as the second 145template parameter to the `Op` class. 146 147### Operation documentation 148 149This includes both a one-line `summary` and a longer human-readable 150`description`. They will be used to drive automatic generation of dialect 151documentation. They need to be provided in the operation's definition body: 152 153```tablegen 154let summary = "..."; 155 156let description = [{ 157... 158}]; 159``` 160 161`description` should be written in Markdown syntax. 162 163Placing the documentation at the beginning is recommended since it helps in 164understanding the operation. 165 166> * Place documentation at the beginning of the operation definition 167> * The summary should be short and concise. It should be a one-liner without 168> trailing punctuation. Put expanded explanation in description. 169 170### Operation arguments 171 172There are two kinds of arguments: operands and attributes. Operands are runtime 173values produced by other ops; while attributes are compile-time known constant 174values, including two categories: 175 1761. Natural attributes: these attributes affect the behavior of the operations 177 (e.g., padding for convolution); 1781. Derived attributes: these attributes are not needed to define the operation 179 but are instead derived from information of the operation. E.g., the output 180 shape of type. This is mostly used for convenience interface generation or 181 interaction with other frameworks/translation. 182 183 All derived attributes should be materializable as an Attribute. That is, 184 even though they are not materialized, it should be possible to store as an 185 attribute. 186 187Both operands and attributes are specified inside the `dag`-typed `arguments`, 188led by `ins`: 189 190```tablegen 191let arguments = (ins 192 <type-constraint>:$<operand-name>, 193 ... 194 <attr-constraint>:$<attr-name>, 195 ... 196); 197``` 198 199Here `<type-constraint>` is a TableGen `def` from the `TypeConstraint` class 200hierarchy. Similarly, `<attr-constraint>` is a TableGen `def` from the 201`AttrConstraint` class hierarchy. See [Constraints](#constraints) for more 202information. 203 204There is no requirements on the relative order of operands and attributes; they 205can mix freely. The relative order of operands themselves matters. From each 206named argument a named getter will be generated that returns the argument with 207the return type (in the case of attributes the return type will be constructed 208from the storage type, while for operands it will be `Value`). Each attribute's 209raw value (e.g., as stored) can also be accessed via generated `<name>Attr` 210getters for use in transformation passes where the more user friendly return 211type is less suitable. 212 213All the arguments should be named to 1) provide documentation, 2) drive 214auto-generation of getter methods, 3) provide a handle to reference for other 215places like constraints. 216 217#### Variadic operands 218 219To declare a variadic operand, wrap the `TypeConstraint` for the operand with 220`Variadic<...>`. 221 222Normally operations have no variadic operands or just one variadic operand. For 223the latter case, it is easy to deduce which dynamic operands are for the static 224variadic operand definition. Though, if an operation has more than one variable 225length operands (either optional or variadic), it would be impossible to 226attribute dynamic operands to the corresponding static variadic operand 227definitions without further information from the operation. Therefore, either 228the `SameVariadicOperandSize` or `AttrSizedOperandSegments` trait is needed to 229indicate that all variable length operands have the same number of dynamic 230values. 231 232#### VariadicOfVariadic operands 233 234To declare a variadic operand that has a variadic number of sub-ranges, wrap the 235`TypeConstraint` for the operand with `VariadicOfVariadic<..., 236"<segment-attribute-name>">`. 237 238The second field of the `VariadicOfVariadic` is the name of an `I32ElementsAttr` 239argument that contains the sizes of the variadic sub-ranges. This attribute will 240be used when determining the size of sub-ranges, or when updating the size of 241sub-ranges. 242 243#### Optional operands 244 245To declare an optional operand, wrap the `TypeConstraint` for the operand with 246`Optional<...>`. 247 248Normally operations have no optional operands or just one optional operand. For 249the latter case, it is easy to deduce which dynamic operands are for the static 250operand definition. Though, if an operation has more than one variable length 251operands (either optional or variadic), it would be impossible to attribute 252dynamic operands to the corresponding static variadic operand definitions 253without further information from the operation. Therefore, either the 254`SameVariadicOperandSize` or `AttrSizedOperandSegments` trait is needed to 255indicate that all variable length operands have the same number of dynamic 256values. 257 258#### Optional attributes 259 260To declare an optional attribute, wrap the `AttrConstraint` for the attribute 261with `OptionalAttr<...>`. 262 263#### Attributes with default values 264 265To declare an attribute with a default value, wrap the `AttrConstraint` for the 266attribute with `DefaultValuedAttr<..., "...">`. 267 268The second parameter to `DefaultValuedAttr` should be a string containing the 269C++ default value. For example, a float default value should be specified as 270like `"0.5f"`, and an integer array default value should be specified as like 271`"{1, 2, 3}"`. 272 273#### Confining attributes 274 275`Confined` is provided as a general mechanism to help modelling further 276constraints on attributes beyond the ones brought by value types. You can use 277`Confined` to compose complex constraints out of more primitive ones. For 278example, a 32-bit integer attribute whose minimum value must be 10 can be 279expressed as `Confined<I32Attr, [IntMinValue<10>]>`. 280 281Right now, the following primitive constraints are supported: 282 283* `IntMinValue<N>`: Specifying an integer attribute to be greater than or 284 equal to `N` 285* `IntMaxValue<N>`: Specifying an integer attribute to be less than or equal 286 to `N` 287* `ArrayMinCount<N>`: Specifying an array attribute to have at least `N` 288 elements 289* `IntArrayNthElemEq<I, N>`: Specifying an integer array attribute's `I`-th 290 element to be equal to `N` 291* `IntArrayNthElemMinValue<I, N>`: Specifying an integer array attribute's 292 `I`-th element to be greater than or equal to `N` 293 294TODO: Design and implement more primitive constraints 295 296### Operation regions 297 298The regions of an operation are specified inside of the `dag`-typed `regions`, 299led by `region`: 300 301```tablegen 302let regions = (region 303 <region-constraint>:$<region-name>, 304 ... 305); 306``` 307 308#### Variadic regions 309 310Similar to the `Variadic` class used for variadic operands and results, 311`VariadicRegion<...>` can be used for regions. Variadic regions can currently 312only be specified as the last region in the regions list. 313 314### Operation results 315 316Similar to operands, results are specified inside the `dag`-typed `results`, led 317by `outs`: 318 319```tablegen 320let results = (outs 321 <type-constraint>:$<result-name>, 322 ... 323); 324``` 325 326#### Variadic results 327 328Similar to variadic operands, `Variadic<...>` can also be used for results. And 329similarly, `SameVariadicResultSize` for multiple variadic results in the same 330operation. 331 332### Operation successors 333 334For terminator operations, the successors are specified inside of the 335`dag`-typed `successors`, led by `successor`: 336 337```tablegen 338let successors = (successor 339 <successor-constraint>:$<successor-name>, 340 ... 341); 342``` 343 344#### Variadic successors 345 346Similar to the `Variadic` class used for variadic operands and results, 347`VariadicSuccessor<...>` can be used for successors. Variadic successors can 348currently only be specified as the last successor in the successor list. 349 350### Operation traits and constraints 351 352Traits are operation properties that affect syntax or semantics. MLIR C++ models 353various traits in the `mlir::OpTrait` namespace. 354 355Both operation traits, [interfaces](Interfaces.md/#utilizing-the-ods-framework), 356and constraints involving multiple operands/attributes/results are provided as 357the third template parameter to the `Op` class. They should be deriving from 358the `OpTrait` class. See [Constraints](#constraints) for more information. 359 360### Builder methods 361 362For each operation, there are a few builders automatically generated based on 363the arguments and returns types. For example, given the following op definition: 364 365```tablegen 366def MyOp : ... { 367 let arguments = (ins 368 I32:$i32_operand, 369 F32:$f32_operand, 370 ..., 371 372 I32Attr:$i32_attr, 373 F32Attr:$f32_attr, 374 ... 375 ); 376 377 let results = (outs 378 I32:$i32_result, 379 F32:$f32_result, 380 ... 381 ); 382} 383``` 384 385The following builders are generated: 386 387```c++ 388// All result-types/operands/attributes have one aggregate parameter. 389static void build(OpBuilder &odsBuilder, OperationState &odsState, 390 ArrayRef<Type> resultTypes, 391 ValueRange operands, 392 ArrayRef<NamedAttribute> attributes); 393 394// Each result-type/operand/attribute has a separate parameter. The parameters 395// for attributes are of mlir::Attribute types. 396static void build(OpBuilder &odsBuilder, OperationState &odsState, 397 Type i32_result, Type f32_result, ..., 398 Value i32_operand, Value f32_operand, ..., 399 IntegerAttr i32_attr, FloatAttr f32_attr, ...); 400 401// Each result-type/operand/attribute has a separate parameter. The parameters 402// for attributes are raw values unwrapped with mlir::Attribute instances. 403// (Note that this builder will not always be generated. See the following 404// explanation for more details.) 405static void build(OpBuilder &odsBuilder, OperationState &odsState, 406 Type i32_result, Type f32_result, ..., 407 Value i32_operand, Value f32_operand, ..., 408 APInt i32_attr, StringRef f32_attr, ...); 409 410// Each operand/attribute has a separate parameter but result type is aggregate. 411static void build(OpBuilder &odsBuilder, OperationState &odsState, 412 ArrayRef<Type> resultTypes, 413 Value i32_operand, Value f32_operand, ..., 414 IntegerAttr i32_attr, FloatAttr f32_attr, ...); 415 416// All operands/attributes have aggregate parameters. 417// Generated if return type can be inferred. 418static void build(OpBuilder &odsBuilder, OperationState &odsState, 419 ValueRange operands, ArrayRef<NamedAttribute> attributes); 420 421// (And manually specified builders depending on the specific op.) 422``` 423 424The first form provides basic uniformity so that we can create ops using the 425same form regardless of the exact op. This is particularly useful for 426implementing declarative pattern rewrites. 427 428The second and third forms are good for use in manually written code given that 429they provide better guarantee via signatures. 430 431The third form will be generated if any of the op's attribute has different 432`Attr.returnType` from `Attr.storageType` and we know how to build an attribute 433from an unwrapped value (i.e., `Attr.constBuilderCall` is defined.) 434Additionally, for the third form, if an attribute appearing later in the 435`arguments` list has a default value, the default value will be supplied in the 436declaration. This works for `BoolAttr`, `StrAttr`, `EnumAttr` for now and the 437list can grow in the future. So if possible, default valued attribute should be 438placed at the end of the `arguments` list to leverage this feature. (This 439behavior is essentially due to C++ function parameter default value placement 440restrictions.) Otherwise, the builder of the third form will still be generated 441but default values for the attributes not at the end of the `arguments` list 442will not be supplied in the builder's signature. 443 444ODS will generate a builder that doesn't require return type specified if 445 446* Op implements InferTypeOpInterface interface; 447* All return types are either buildable types or are the same as a given 448 operand (e.g., `AllTypesMatch` constraint between operand and result); 449 450And there may potentially exist other builders depending on the specific op; 451please refer to the 452[generated C++ file](#run-mlir-tblgen-to-see-the-generated-content) for the 453complete list. 454 455#### Custom builder methods 456 457However, if the above cases cannot satisfy all needs, you can define additional 458convenience build methods in the `builders` field as follows. 459 460```tablegen 461def MyOp : Op<"my_op", []> { 462 let arguments = (ins F32Attr:$attr); 463 464 let builders = [ 465 OpBuilder<(ins "float":$val)> 466 ]; 467} 468``` 469 470The `builders` field is a list of custom builders that are added to the Op 471class. In this example, we provide a convenience builder that takes a floating 472point value instead of an attribute. The `ins` prefix is common to many function 473declarations in ODS, which use a TableGen [`dag`](#tablegen-syntax). What 474follows is a comma-separated list of types (quoted string) and names prefixed 475with the `$` sign. This will generate the declaration of a builder method that 476looks like: 477 478```c++ 479class MyOp : /*...*/ { 480 /*...*/ 481 static void build(::mlir::OpBuilder &builder, ::mlir::OperationState &state, 482 float val); 483}; 484``` 485 486Note that the method has two additional leading arguments. These arguments are 487useful to construct the operation. In particular, the method must populate 488`state` with attributes, operands, regions and result types of the operation to 489be constructed. `builder` can be used to construct any IR objects that belong to 490the Op, such as types or nested operations. Since the type and name are 491generated as is in the C++ code, they should be valid C++ constructs for a type 492(in the namespace of the Op) and an identifier (e.g., `class` is not a valid 493identifier). 494 495Implementations of the builder can be provided directly in ODS, using TableGen 496code block as follows. 497 498```tablegen 499def MyOp : Op<"my_op", []> { 500 let arguments = (ins F32Attr:$attr); 501 502 let builders = [ 503 OpBuilder<(ins "float":$val), [{ 504 $_state.addAttribute("attr", $_builder.getF32FloatAttr(val)); 505 }]> 506 ]; 507} 508``` 509 510The equivalents of `builder` and `state` arguments are available as `$_builder` 511and `$_state` special variables. The named arguments listed in the `ins` part 512are available directly, e.g. `val`. The body of the builder will be generated by 513substituting special variables and should otherwise be valid C++. While there is 514no limitation on the code size, we encourage one to define only short builders 515inline in ODS and put definitions of longer builders in C++ files. 516 517Finally, if some arguments need a default value, they can be defined using 518`CArg` to wrap the type and this value as follows. 519 520```tablegen 521def MyOp : Op<"my_op", []> { 522 let arguments = (ins F32Attr:$attr); 523 524 let builders = [ 525 OpBuilder<(ins CArg<"float", "0.5f">:$val), [{ 526 $_state.addAttribute("attr", $_builder.getF32FloatAttr(val)); 527 }]> 528 ]; 529} 530``` 531 532The generated code will use default value in the declaration, but not in the 533definition, as required by C++. 534 535```c++ 536/// Header file. 537class MyOp : /*...*/ { 538 /*...*/ 539 static void build(::mlir::OpBuilder &builder, ::mlir::OperationState &state, 540 float val = 0.5f); 541}; 542 543/// Source file. 544MyOp::build(::mlir::OpBuilder &builder, ::mlir::OperationState &state, 545 float val) { 546 state.addAttribute("attr", builder.getF32FloatAttr(val)); 547} 548``` 549 550**Deprecated:** `OpBuilder` class allows one to specify the custom builder 551signature as a raw string, without separating parameters into different `dag` 552arguments. It also supports leading parameters of `OpBuilder &` and 553`OperationState &` types, which will be used instead of the autogenerated ones 554if present. 555 556### Custom parser and printer methods 557 558Functions to parse and print the operation's custom assembly form. 559 560### Custom verifier code 561 562Verification code will be automatically generated for 563[constraints](#constraints) specified on various entities of the op. To perform 564_additional_ verification, you can use 565 566```tablegen 567let hasVerifier = 1; 568``` 569 570or 571 572```tablegen 573let hasRegionVerifier = 1; 574``` 575 576This will generate either `LogicalResult verify()` or 577`LogicalResult verifyRegions()` method declaration on the op class 578that can be defined with any additional verification constraints. These method 579will be invoked on its verification order. 580 581#### Verification Ordering 582 583The verification of an operation involves several steps, 584 5851. StructuralOpTrait will be verified first, they can be run independently. 5861. `verifyInvariants` which is constructed by ODS, it verifies the type, 587 attributes, .etc. 5881. Other Traits/Interfaces that have marked their verifier as `verifyTrait` or 589 `verifyWithRegions=0`. 5901. Custom verifier which is defined in the op and has marked `hasVerifier=1` 591 592If an operation has regions, then it may have the second phase, 593 5941. Traits/Interfaces that have marked their verifier as `verifyRegionTrait` or 595 `verifyWithRegions=1`. This implies the verifier needs to access the 596 operations in its regions. 5971. Custom verifier which is defined in the op and has marked 598 `hasRegionVerifier=1` 599 600Note that the second phase will be run after the operations in the region are 601verified. Verifiers further down the order can rely on certain invariants being 602verified by a previous verifier and do not need to re-verify them. 603 604#### Emitting diagnostics in custom verifiers 605 606Custom verifiers should avoid printing operations using custom operation 607printers, because they require the printed operation (and sometimes its parent 608operation) to be verified first. In particular, when emitting diagnostics, 609custom verifiers should use the `Error` severity level, which prints operations 610in generic form by default, and avoid using lower severity levels (`Note`, 611`Remark`, `Warning`). 612 613### Declarative Assembly Format 614 615The custom assembly form of the operation may be specified in a declarative 616string that matches the operations operands, attributes, etc. With the ability 617to express additional information that needs to be parsed to build the 618operation: 619 620```tablegen 621def CallOp : Std_Op<"call", ...> { 622 let arguments = (ins FlatSymbolRefAttr:$callee, Variadic<AnyType>:$args); 623 let results = (outs Variadic<AnyType>); 624 625 let assemblyFormat = [{ 626 $callee `(` $args `)` attr-dict `:` functional-type($args, results) 627 }]; 628} 629``` 630 631The format is comprised of three components: 632 633#### Directives 634 635A directive is a type of builtin function, with an optional set of arguments. 636The available directives are as follows: 637 638* `attr-dict` 639 640 - Represents the attribute dictionary of the operation. 641 642* `attr-dict-with-keyword` 643 644 - Represents the attribute dictionary of the operation, but prefixes the 645 dictionary with an `attributes` keyword. 646 647* `custom` < UserDirective > ( Params ) 648 649 - Represents a custom directive implemented by the user in C++. 650 - See the [Custom Directives](#custom-directives) section below for more 651 details. 652 653* `functional-type` ( inputs , results ) 654 655 - Formats the `inputs` and `results` arguments as a 656 [function type](Dialects/Builtin.md/#functiontype). 657 - The constraints on `inputs` and `results` are the same as the `input` of 658 the `type` directive. 659 660* `oilist` ( \`keyword\` elements | \`otherKeyword\` elements ...) 661 662 - Represents an optional order-independent list of clauses. Each clause 663 has a keyword and corresponding assembly format. 664 - Each clause can appear 0 or 1 time (in any order). 665 - Only literals, types and variables can be used within an oilist element. 666 - All the variables must be optional or variadic. 667 668* `operands` 669 670 - Represents all of the operands of an operation. 671 672* `ref` ( input ) 673 674 - Represents a reference to the a variable or directive, that must have 675 already been resolved, that may be used as a parameter to a `custom` 676 directive. 677 - Used to pass previously parsed entities to custom directives. 678 - The input may be any directive or variable, aside from `functional-type` 679 and `custom`. 680 681* `regions` 682 683 - Represents all of the regions of an operation. 684 685* `results` 686 687 - Represents all of the results of an operation. 688 689* `successors` 690 691 - Represents all of the successors of an operation. 692 693* `type` ( input ) 694 695 - Represents the type of the given input. 696 - `input` must be either an operand or result [variable](#variables), the 697 `operands` directive, or the `results` directive. 698 699* `qualified` ( type_or_attribute ) 700 701 - Wraps a `type` directive or an attribute parameter. 702 - Used to force printing the type or attribute prefixed with its dialect 703 and mnemonic. For example the `vector.multi_reduction` operation has a 704 `kind` attribute ; by default the declarative assembly will print: 705 `vector.multi_reduction <minf>, ...` but using `qualified($kind)` in the 706 declarative assembly format will print it instead as: 707 `vector.multi_reduction #vector.kind<minf>, ...`. 708 709#### Literals 710 711A literal is either a keyword or punctuation surrounded by \`\`. 712 713The following are the set of valid punctuation: 714 715`:`, `,`, `=`, `<`, `>`, `(`, `)`, `{`, `}`, `[`, `]`, `->`, `?`, `+`, `*` 716 717The following are valid whitespace punctuation: 718 719`\n`, ` ` 720 721The `\n` literal emits a newline an indents to the start of the operation. An 722example is shown below: 723 724```tablegen 725let assemblyFormat = [{ 726 `{` `\n` ` ` ` ` `this_is_on_a_newline` `\n` `}` attr-dict 727}]; 728``` 729 730```mlir 731%results = my.operation { 732 this_is_on_a_newline 733} 734``` 735 736An empty literal \`\` may be used to remove a space that is inserted implicitly 737after certain literal elements, such as `)`/`]`/etc. For example, "`]`" may 738result in an output of `]` it is not the last element in the format. "`]` \`\`" 739would trim the trailing space in this situation. 740 741#### Variables 742 743A variable is an entity that has been registered on the operation itself, i.e. 744an argument(attribute or operand), region, result, successor, etc. In the 745`CallOp` example above, the variables would be `$callee` and `$args`. 746 747Attribute variables are printed with their respective value type, unless that 748value type is buildable. In those cases, the type of the attribute is elided. 749 750#### Custom Directives 751 752The declarative assembly format specification allows for handling a large 753majority of the common cases when formatting an operation. For the operations 754that require or desire specifying parts of the operation in a form not supported 755by the declarative syntax, custom directives may be specified. A custom 756directive essentially allows for users to use C++ for printing and parsing 757subsections of an otherwise declaratively specified format. Looking at the 758specification of a custom directive above: 759 760``` 761custom-directive ::= `custom` `<` UserDirective `>` `(` Params `)` 762``` 763 764A custom directive has two main parts: The `UserDirective` and the `Params`. A 765custom directive is transformed into a call to a `print*` and a `parse*` method 766when generating the C++ code for the format. The `UserDirective` is an 767identifier used as a suffix to these two calls, i.e., `custom<MyDirective>(...)` 768would result in calls to `parseMyDirective` and `printMyDirective` within the 769parser and printer respectively. `Params` may be any combination of variables 770(i.e. Attribute, Operand, Successor, etc.), type directives, and `attr-dict`. 771The type directives must refer to a variable, but that variable need not also be 772a parameter to the custom directive. 773 774The arguments to the `parse<UserDirective>` method are firstly a reference to 775the `OpAsmParser`(`OpAsmParser &`), and secondly a set of output parameters 776corresponding to the parameters specified in the format. The mapping of 777declarative parameter to `parse` method argument is detailed below: 778 779* Attribute Variables 780 - Single: `<Attribute-Storage-Type>(e.g. Attribute) &` 781 - Optional: `<Attribute-Storage-Type>(e.g. Attribute) &` 782* Operand Variables 783 - Single: `OpAsmParser::OperandType &` 784 - Optional: `Optional<OpAsmParser::OperandType> &` 785 - Variadic: `SmallVectorImpl<OpAsmParser::OperandType> &` 786 - VariadicOfVariadic: 787 `SmallVectorImpl<SmallVector<OpAsmParser::OperandType>> &` 788* Ref Directives 789 - A reference directive is passed to the parser using the same mapping as 790 the input operand. For example, a single region would be passed as a 791 `Region &`. 792* Region Variables 793 - Single: `Region &` 794 - Variadic: `SmallVectorImpl<std::unique_ptr<Region>> &` 795* Successor Variables 796 - Single: `Block *&` 797 - Variadic: `SmallVectorImpl<Block *> &` 798* Type Directives 799 - Single: `Type &` 800 - Optional: `Type &` 801 - Variadic: `SmallVectorImpl<Type> &` 802 - VariadicOfVariadic: `SmallVectorImpl<SmallVector<Type>> &` 803* `attr-dict` Directive: `NamedAttrList &` 804 805When a variable is optional, the value should only be specified if the variable 806is present. Otherwise, the value should remain `None` or null. 807 808The arguments to the `print<UserDirective>` method is firstly a reference to the 809`OpAsmPrinter`(`OpAsmPrinter &`), second the op (e.g. `FooOp op` which can be 810`Operation *op` alternatively), and finally a set of output parameters 811corresponding to the parameters specified in the format. The mapping of 812declarative parameter to `print` method argument is detailed below: 813 814* Attribute Variables 815 - Single: `<Attribute-Storage-Type>(e.g. Attribute)` 816 - Optional: `<Attribute-Storage-Type>(e.g. Attribute)` 817* Operand Variables 818 - Single: `Value` 819 - Optional: `Value` 820 - Variadic: `OperandRange` 821 - VariadicOfVariadic: `OperandRangeRange` 822* Ref Directives 823 - A reference directive is passed to the printer using the same mapping as 824 the input operand. For example, a single region would be passed as a 825 `Region &`. 826* Region Variables 827 - Single: `Region &` 828 - Variadic: `MutableArrayRef<Region>` 829* Successor Variables 830 - Single: `Block *` 831 - Variadic: `SuccessorRange` 832* Type Directives 833 - Single: `Type` 834 - Optional: `Type` 835 - Variadic: `TypeRange` 836 - VariadicOfVariadic: `TypeRangeRange` 837* `attr-dict` Directive: `DictionaryAttr` 838 839When a variable is optional, the provided value may be null. 840 841#### Optional Groups 842 843In certain situations operations may have "optional" information, e.g. 844attributes or an empty set of variadic operands. In these situations a section 845of the assembly format can be marked as `optional` based on the presence of this 846information. An optional group is defined as follows: 847 848``` 849optional-group: `(` elements `)` (`:` `(` else-elements `)`)? `?` 850``` 851 852The `elements` of an optional group have the following requirements: 853 854* The first element of the group must either be a attribute, literal, operand, 855 or region. 856 - This is because the first element must be optionally parsable. 857* Exactly one argument variable or type directive within the group must be 858 marked as the anchor of the group. 859 - The anchor is the element whose presence controls whether the group 860 should be printed/parsed. 861 - An element is marked as the anchor by adding a trailing `^`. 862 - The first element is *not* required to be the anchor of the group. 863 - When a non-variadic region anchors a group, the detector for printing 864 the group is if the region is empty. 865* Literals, variables, custom directives, and type directives are the only 866 valid elements within the group. 867 - Any attribute variable may be used, but only optional attributes can be 868 marked as the anchor. 869 - Only variadic or optional results and operand arguments and can be used. 870 - All region variables can be used. When a non-variable length region is 871 used, if the group is not present the region is empty. 872 873An example of an operation with an optional group is `func.return`, which has a 874variadic number of operands. 875 876```tablegen 877def ReturnOp : ... { 878 let arguments = (ins Variadic<AnyType>:$operands); 879 880 // We only print the operands and types if there are a non-zero number 881 // of operands. 882 let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?"; 883} 884``` 885 886##### Unit Attributes 887 888In MLIR, the [`unit` Attribute](Dialects/Builtin.md/#unitattr) is special in that it 889only has one possible value, i.e. it derives meaning from its existence. When a 890unit attribute is used to anchor an optional group and is not the first element 891of the group, the presence of the unit attribute can be directly correlated with 892the presence of the optional group itself. As such, in these situations the unit 893attribute will not be printed or present in the output and will be automatically 894inferred when parsing by the presence of the optional group itself. 895 896For example, the following operation: 897 898```tablegen 899def FooOp : ... { 900 let arguments = (ins UnitAttr:$is_read_only); 901 902 let assemblyFormat = "attr-dict (`is_read_only` $is_read_only^)?"; 903} 904``` 905 906would be formatted as such: 907 908```mlir 909// When the unit attribute is present: 910foo.op is_read_only 911 912// When the unit attribute is not present: 913foo.op 914``` 915 916##### Optional "else" Group 917 918Optional groups also have support for an "else" group of elements. These are 919elements that are parsed/printed if the `anchor` element of the optional group 920is *not* present. Unlike the main element group, the "else" group has no 921restriction on the first element and none of the elements may act as the 922`anchor` for the optional. An example is shown below: 923 924```tablegen 925def FooOp : ... { 926 let arguments = (ins UnitAttr:$foo); 927 928 let assemblyFormat = "attr-dict (`foo_is_present` $foo^):(`foo_is_absent`)?"; 929} 930``` 931 932would be formatted as such: 933 934```mlir 935// When the `foo` attribute is present: 936foo.op foo_is_present 937 938// When the `foo` attribute is not present: 939foo.op foo_is_absent 940``` 941 942#### Requirements 943 944The format specification has a certain set of requirements that must be adhered 945to: 946 9471. The output and operation name are never shown as they are fixed and cannot 948 be altered. 9491. All operands within the operation must appear within the format, either 950 individually or with the `operands` directive. 9511. All regions within the operation must appear within the format, either 952 individually or with the `regions` directive. 9531. All successors within the operation must appear within the format, either 954 individually or with the `successors` directive. 9551. All operand and result types must appear within the format using the various 956 `type` directives, either individually or with the `operands` or `results` 957 directives. 9581. The `attr-dict` directive must always be present. 9591. Must not contain overlapping information; e.g. multiple instances of 960 'attr-dict', types, operands, etc. 961 - Note that `attr-dict` does not overlap with individual attributes. These 962 attributes will simply be elided when printing the attribute dictionary. 963 964##### Type Inference 965 966One requirement of the format is that the types of operands and results must 967always be present. In certain instances, the type of a variable may be deduced 968via type constraints or other information available. In these cases, the type of 969that variable may be elided from the format. 970 971* Buildable Types 972 973Some type constraints may only have one representation, allowing for them to be 974directly buildable; for example the `I32` or `Index` types. Types in `ODS` may 975mark themselves as buildable by setting the `builderCall` field or inheriting 976from the `BuildableType` class. 977 978* Trait Equality Constraints 979 980There are many operations that have known type equality constraints registered 981as traits on the operation; for example the true, false, and result values of a 982`select` operation often have the same type. The assembly format may inspect 983these equal constraints to discern the types of missing variables. The currently 984supported traits are: `AllTypesMatch`, `TypesMatchWith`, `SameTypeOperands`, and 985`SameOperandsAndResultType`. 986 987* InferTypeOpInterface 988 989Operations that implement `InferTypeOpInterface` can omit their result types in 990their assembly format since the result types can be inferred from the operands. 991 992### `hasCanonicalizer` 993 994This boolean field indicate whether canonicalization patterns have been defined 995for this operation. If it is `1`, then `::getCanonicalizationPatterns()` should 996be defined. 997 998### `hasCanonicalizeMethod` 999 1000When this boolean field is set to `true`, it indicates that the op implements a 1001`canonicalize` method for simple "matchAndRewrite" style canonicalization 1002patterns. If `hasCanonicalizer` is 0, then an implementation of 1003`::getCanonicalizationPatterns()` is implemented to call this function. 1004 1005### `hasFolder` 1006 1007This boolean field indicate whether general folding rules have been defined for 1008this operation. If it is `1`, then `::fold()` should be defined. 1009 1010### Extra declarations 1011 1012One of the goals of table-driven op definition is to auto-generate as much logic 1013and methods needed for each op as possible. With that said, there will always be 1014long-tail cases that won't be covered. For such cases, you can use 1015`extraClassDeclaration`. Code in `extraClassDeclaration` will be copied 1016literally to the generated C++ op class. 1017 1018Note that `extraClassDeclaration` is a mechanism intended for long-tail cases by 1019power users; for not-yet-implemented widely-applicable cases, improving the 1020infrastructure is preferable. 1021 1022### Extra definitions 1023 1024When defining base op classes in TableGen that are inherited many times by 1025different ops, users may want to provide common definitions of utility and 1026interface functions. However, many of these definitions may not be desirable or 1027possible in `extraClassDeclaration`, which append them to the op's C++ class 1028declaration. In these cases, users can add an `extraClassDefinition` to define 1029code that is added to the generated source file inside the op's C++ namespace. 1030The substitution `$cppClass` is replaced by the op's C++ class name. 1031 1032### Generated C++ code 1033 1034[OpDefinitionsGen][OpDefinitionsGen] processes the op definition spec file and 1035generates two files containing the corresponding C++ code: one for declarations, 1036the other for definitions. The former is generated via the `-gen-op-decls` 1037command-line option, while the latter is via the `-gen-op-defs` option. 1038 1039The definition file contains all the op method definitions, which can be 1040included and enabled by defining `GET_OP_CLASSES`. For each operation, 1041OpDefinitionsGen generates an operation class and an 1042[operand adaptor](#operand-adaptors) class. Besides, it also contains a 1043comma-separated list of all defined ops, which can be included and enabled by 1044defining `GET_OP_LIST`. 1045 1046#### Class name and namespaces 1047 1048For each operation, its generated C++ class name is the symbol `def`ed with 1049TableGen with dialect prefix removed. The first `_` serves as the delimiter. For 1050example, for `def TF_AddOp`, the C++ class name would be `AddOp`. We remove the 1051`TF` prefix because it is for scoping ops; other dialects may as well define 1052their own `AddOp`s. 1053 1054The namespaces of the generated C++ class will come from the dialect's 1055`cppNamespace` field. For example, if a dialect's `cppNamespace` is `A::B`, then 1056an op of that dialect will be placed in `namespace A { namespace B { ... } }`. 1057If a dialect does not specify a `cppNamespace`, we then use the dialect's name 1058as the namespace. 1059 1060This means the qualified name of the generated C++ class does not necessarily 1061match exactly with the operation name as explained in 1062[Operation name](#operation-name). This is to allow flexible naming to satisfy 1063coding style requirements. 1064 1065#### Operand adaptors 1066 1067For each operation, we automatically generate an _operand adaptor_. This class 1068solves the problem of accessing operands provided as a list of `Value`s without 1069using "magic" constants. The operand adaptor takes a reference to an array of 1070`Value` and provides methods with the same names as those in the operation class 1071to access them. For example, for a binary arithmetic operation, it may provide 1072`.lhs()` to access the first operand and `.rhs()` to access the second operand. 1073 1074The operand adaptor class lives in the same namespace as the operation class, 1075and has the name of the operation followed by `Adaptor` as well as an alias 1076`Adaptor` inside the op class. 1077 1078Operand adaptors can be used in function templates that also process operations: 1079 1080```c++ 1081template <typename BinaryOpTy> 1082std::pair<Value, Value> zip(BinaryOpTy &&op) { 1083 return std::make_pair(op.lhs(), op.rhs());; 1084} 1085 1086void process(AddOp op, ArrayRef<Value> newOperands) { 1087 zip(op); 1088 zip(Adaptor<AddOp>(newOperands)); 1089 /*...*/ 1090} 1091``` 1092 1093## Constraints 1094 1095Constraint is a core concept in table-driven operation definition: operation 1096verification and graph operation matching are all based on satisfying 1097constraints. So both the operation definition and rewrite rules specification 1098significantly involve writing constraints. We have the `Constraint` class in 1099[`OpBase.td`][OpBase] as the common base class for all constraints. 1100 1101An operation's constraint can cover different range; it may 1102 1103* Only concern a single attribute (e.g. being a 32-bit integer greater than 1104 5), 1105* Multiple operands and results (e.g., the 1st result's shape must be the same 1106 as the 1st operand), or 1107* Intrinsic to the operation itself (e.g., having no side effect). 1108 1109We call them as single-entity constraint, multi-entity constraint, and traits, 1110respectively. 1111 1112### Single-entity constraint 1113 1114Constraints scoped to a single operand, attribute, or result are specified at 1115the entity's declaration place as described in 1116[Operation arguments](#operation-arguments) and 1117[Operation results](#operation-results). 1118 1119To help modelling constraints of common types, a set of `TypeConstraint`s are 1120created; they are the `Type` subclass hierarchy. It includes `F32` for the 1121constraints of being a float, `TensorOf<[F32]>` for the constraints of being a 1122float tensor, and so on. 1123 1124Similarly, a set of `AttrConstraint`s are created for helping modelling 1125constraints of common attribute kinds. They are the `Attr` subclass hierarchy. 1126It includes `F32Attr` for the constraints of being a float attribute, 1127`F32ArrayAttr` for the constraints of being a float array attribute, and so on. 1128 1129### Multi-entity constraint 1130 1131Constraints involving more than one operand/attribute/result are quite common on 1132operations, like the element type and shape relation between operands and 1133results. These constraints should be specified as the `Op` class template 1134parameter as described in 1135[Operation traits and constraints](#operation-traits-and-constraints). 1136 1137Multi-entity constraints are modeled as `PredOpTrait` (a subclass of `OpTrait`) 1138in [`OpBase.td`][OpBase].A bunch of constraint primitives are provided to help 1139specification. See [`OpBase.td`][OpBase] for the complete list. 1140 1141### Trait 1142 1143Traits are intrinsic properties of the operation like having side effect or not, 1144commutative or not, whether is a terminator, etc. These constraints should be 1145specified as the `Op` class template parameter as described in 1146[Operation traits and constraints](#operation-traits-and-constraints). 1147 1148Traits are modeled as `NativeOpTrait` (a subclass of `OpTrait`) in 1149[`OpBase.td`][OpBase]. They are backed and will be translated into the 1150corresponding C++ `mlir::OpTrait` classes. 1151 1152### How to specify new constraint 1153 1154To write a constraint, you need to provide its predicates and give it a 1155descriptive name. Predicates, modeled with the `Pred` class, are the workhorse 1156for composing constraints. The predicate for a constraint is typically built up 1157in a nested manner, using the two categories of predicates: 1158 11591. `CPred`: the primitive leaf predicate. 11602. Compound predicate: a predicate composed from child predicates using 1161 predicate combiners (conjunction: `And`, disjunction: `Or`, negation: `Neg`, 1162 substitution: `SubstLeaves`, concatenation: `Concat`). 1163 1164`CPred` is the basis for composing more complex predicates. It is the "atom" 1165predicate from the perspective of TableGen and the "interface" between TableGen 1166and C++. What is inside is already C++ code, which will be treated as opaque 1167strings with special placeholders to be substituted. 1168 1169You can put any C++ code that returns a boolean value inside a `CPred`, 1170including evaluating expressions, calling functions, calling class methods, and 1171so on. 1172 1173To help interaction with the C++ environment, there are a few special 1174placeholders provided to refer to entities in the context where this predicate 1175is used. They serve as "hooks" to the enclosing environment. This includes 1176`$_builder`, `$_op`, and `$_self`: 1177 1178* `$_builder` will be replaced by a `mlir::Builder` instance so that you can 1179 access common build methods. 1180* `$_op` will be replaced by the current operation so that you can access 1181 information of the current operation. 1182* `$_self` will be replaced with the entity this predicate is attached to. 1183 E.g., `BoolAttr` is an attribute constraint that wraps a 1184 `CPred<"$_self.isa<BoolAttr>()">`. Then for `BoolAttr:$attr`,`$_self` will be 1185 replaced by `$attr`. For type constraints, it's a little bit special since 1186 we want the constraints on each type definition reads naturally and we want 1187 to attach type constraints directly to an operand/result, `$_self` will be 1188 replaced by the operand/result's type. E.g., for `F32` in `F32:$operand`, 1189 its `$_self` will be expanded as `operand(...).getType()`. 1190 1191TODO: Reconsider the leading symbol for special placeholders. Eventually we want 1192to allow referencing operand/result `$-name`s; such `$-name`s can start with 1193underscore. 1194 1195For example, to write an attribute `attr` is an `IntegerAttr`, in C++ you can 1196just call `attr.isa<IntegerAttr>()`. The code can be wrapped in a `CPred` as 1197`$_self.isa<IntegerAttr>()`, with `$_self` as the special placeholder to be 1198replaced by the current attribute `attr` at expansion time. 1199 1200For more complicated predicates, you can wrap it in a single `CPred`, or you can 1201use predicate combiners to combine them. For example, to write the constraint 1202that an attribute `attr` is a 32-bit or 64-bit integer, you can write it as 1203 1204```tablegen 1205And<[ 1206 CPred<"$_self.isa<IntegerAttr>()">, 1207 Or<[ 1208 CPred<"$_self.cast<IntegerAttr>().getType().isInteger(32)">, 1209 CPred<"$_self.cast<IntegerAttr>().getType().isInteger(64)"> 1210 ]> 1211]> 1212``` 1213 1214(Note that the above is just to show with a familiar example how you can use 1215`CPred` and predicate combiners to write complicated predicates. For integer 1216attributes specifically, [`OpBase.td`][OpBase] already defines `I32Attr` and 1217`I64Attr`. So you can actually reuse them to write it as `Or<[I32Attr.predicate, 1218I64Attr.predicate]>`.) 1219 1220TODO: Build up a library of reusable primitive constraints 1221 1222If the predicate is very complex to write with `CPred` together with predicate 1223combiners, you can also write it as a normal C++ function and use the `CPred` as 1224a way to "invoke" the function. For example, to verify an attribute `attr` has 1225some property, you can write a C++ function like 1226 1227```cpp 1228bool HasSomeProperty(Attribute attr) { ... } 1229``` 1230 1231and then define the op as: 1232 1233```tablegen 1234def HasSomeProperty : AttrConstraint<CPred<"HasSomeProperty($_self)">, 1235 "has some property">; 1236 1237def MyOp : Op<...> { 1238 let arguments = (ins 1239 ... 1240 HasSomeProperty:$attr 1241 ); 1242} 1243``` 1244 1245As to whether we should define the predicate using a single `CPred` wrapping the 1246whole expression, multiple `CPred`s with predicate combiners, or a single 1247`CPred` "invoking" a function, there are no clear-cut criteria. Defining using 1248`CPred` and predicate combiners is preferable since it exposes more information 1249(instead hiding all the logic behind a C++ function) into the op definition spec 1250so that it can potentially drive more auto-generation cases. But it will require 1251a nice library of common predicates as the building blocks to avoid the 1252duplication, which is being worked on right now. 1253 1254## Attribute Definition 1255 1256An attribute is a compile-time known constant of an operation. 1257 1258ODS provides attribute wrappers over C++ attribute classes. There are a few 1259common C++ [attribute classes][AttrClasses] defined in MLIR's core IR library 1260and one is free to define dialect-specific attribute classes. ODS allows one to 1261use these attributes in TableGen to define operations, potentially with more 1262fine-grained constraints. For example, `StrAttr` directly maps to `StringAttr`; 1263`F32Attr`/`F64Attr` requires the `FloatAttr` to additionally be of a certain 1264bitwidth. 1265 1266ODS attributes are defined as having a storage type (corresponding to a backing 1267`mlir::Attribute` that _stores_ the attribute), a return type (corresponding to 1268the C++ _return_ type of the generated helper getters) as well as a method 1269to convert between the internal storage and the helper method. 1270 1271### Attribute decorators 1272 1273There are a few important attribute adapters/decorators/modifiers that can be 1274applied to ODS attributes to specify common additional properties like 1275optionality, default values, etc.: 1276 1277* `DefaultValuedAttr`: specifies the 1278 [default value](#attributes-with-default-values) for an attribute. 1279* `OptionalAttr`: specifies an attribute as [optional](#optional-attributes). 1280* `Confined`: adapts an attribute with 1281 [further constraints](#confining-attributes). 1282 1283### Enum attributes 1284 1285Some attributes can only take values from a predefined enum, e.g., the 1286comparison kind of a comparison op. To define such attributes, ODS provides 1287several mechanisms: `StrEnumAttr`, `IntEnumAttr`, and `BitEnumAttr`. 1288 1289* `StrEnumAttr`: each enum case is a string, the attribute is stored as a 1290 [`StringAttr`][StringAttr] in the op. 1291* `IntEnumAttr`: each enum case is an integer, the attribute is stored as a 1292 [`IntegerAttr`][IntegerAttr] in the op. 1293* `BitEnumAttr`: each enum case is a either the empty case, a single bit, 1294 or a group of single bits, and the attribute is stored as a 1295 [`IntegerAttr`][IntegerAttr] in the op. 1296 1297All these `*EnumAttr` attributes require fully specifying all of the allowed 1298cases via their corresponding `*EnumAttrCase`. With this, ODS is able to 1299generate additional verification to only accept allowed cases. To facilitate the 1300interaction between `*EnumAttr`s and their C++ consumers, the 1301[`EnumsGen`][EnumsGen] TableGen backend can generate a few common utilities: a 1302C++ enum class, `llvm::DenseMapInfo` for the enum class, conversion functions 1303from/to strings. This is controlled via the `-gen-enum-decls` and 1304`-gen-enum-defs` command-line options of `mlir-tblgen`. 1305 1306For example, given the following `EnumAttr`: 1307 1308```tablegen 1309def Case15: I32EnumAttrCase<"Case15", 15>; 1310def Case20: I32EnumAttrCase<"Case20", 20>; 1311 1312def MyIntEnum: I32EnumAttr<"MyIntEnum", "An example int enum", 1313 [Case15, Case20]> { 1314 let cppNamespace = "Outer::Inner"; 1315 let stringToSymbolFnName = "ConvertToEnum"; 1316 let symbolToStringFnName = "ConvertToString"; 1317} 1318``` 1319 1320The following will be generated via `mlir-tblgen -gen-enum-decls`: 1321 1322```c++ 1323namespace Outer { 1324namespace Inner { 1325// An example int enum 1326enum class MyIntEnum : uint32_t { 1327 Case15 = 15, 1328 Case20 = 20, 1329}; 1330 1331llvm::Optional<MyIntEnum> symbolizeMyIntEnum(uint32_t); 1332llvm::StringRef ConvertToString(MyIntEnum); 1333llvm::Optional<MyIntEnum> ConvertToEnum(llvm::StringRef); 1334inline constexpr unsigned getMaxEnumValForMyIntEnum() { 1335 return 20; 1336} 1337 1338} // namespace Inner 1339} // namespace Outer 1340 1341namespace llvm { 1342template<> struct DenseMapInfo<Outer::Inner::MyIntEnum> { 1343 using StorageInfo = llvm::DenseMapInfo<uint32_t>; 1344 1345 static inline Outer::Inner::MyIntEnum getEmptyKey() { 1346 return static_cast<Outer::Inner::MyIntEnum>(StorageInfo::getEmptyKey()); 1347 } 1348 1349 static inline Outer::Inner::MyIntEnum getTombstoneKey() { 1350 return static_cast<Outer::Inner::MyIntEnum>(StorageInfo::getTombstoneKey()); 1351 } 1352 1353 static unsigned getHashValue(const Outer::Inner::MyIntEnum &val) { 1354 return StorageInfo::getHashValue(static_cast<uint32_t>(val)); 1355 } 1356 1357 static bool isEqual(const Outer::Inner::MyIntEnum &lhs, const Outer::Inner::MyIntEnum &rhs) { 1358 return lhs == rhs; 1359 } 1360}; 1361} 1362``` 1363 1364The following will be generated via `mlir-tblgen -gen-enum-defs`: 1365 1366```c++ 1367namespace Outer { 1368namespace Inner { 1369llvm::StringRef ConvertToString(MyIntEnum val) { 1370 switch (val) { 1371 case MyIntEnum::Case15: return "Case15"; 1372 case MyIntEnum::Case20: return "Case20"; 1373 } 1374 return ""; 1375} 1376 1377llvm::Optional<MyIntEnum> ConvertToEnum(llvm::StringRef str) { 1378 return llvm::StringSwitch<llvm::Optional<MyIntEnum>>(str) 1379 .Case("Case15", MyIntEnum::Case15) 1380 .Case("Case20", MyIntEnum::Case20) 1381 .Default(llvm::None); 1382} 1383llvm::Optional<MyIntEnum> symbolizeMyIntEnum(uint32_t value) { 1384 switch (value) { 1385 case 15: return MyIntEnum::Case15; 1386 case 20: return MyIntEnum::Case20; 1387 default: return llvm::None; 1388 } 1389} 1390 1391} // namespace Inner 1392} // namespace Outer 1393``` 1394 1395Similarly for the following `BitEnumAttr` definition: 1396 1397```tablegen 1398def None: BitEnumAttrCaseNone<"None">; 1399def Bit0: BitEnumAttrCaseBit<"Bit0", 0>; 1400def Bit1: BitEnumAttrCaseBit<"Bit1", 1>; 1401def Bit2: BitEnumAttrCaseBit<"Bit2", 2>; 1402def Bit3: BitEnumAttrCaseBit<"Bit3", 3>; 1403 1404def MyBitEnum: BitEnumAttr<"MyBitEnum", "An example bit enum", 1405 [None, Bit0, Bit1, Bit2, Bit3]>; 1406``` 1407 1408We can have: 1409 1410```c++ 1411// An example bit enum 1412enum class MyBitEnum : uint32_t { 1413 None = 0, 1414 Bit0 = 1, 1415 Bit1 = 2, 1416 Bit2 = 4, 1417 Bit3 = 8, 1418}; 1419 1420llvm::Optional<MyBitEnum> symbolizeMyBitEnum(uint32_t); 1421std::string stringifyMyBitEnum(MyBitEnum); 1422llvm::Optional<MyBitEnum> symbolizeMyBitEnum(llvm::StringRef); 1423inline MyBitEnum operator|(MyBitEnum lhs, MyBitEnum rhs) { 1424 return static_cast<MyBitEnum>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs)); 1425} 1426inline MyBitEnum operator&(MyBitEnum lhs, MyBitEnum rhs) { 1427 return static_cast<MyBitEnum>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs)); 1428} 1429inline bool bitEnumContains(MyBitEnum bits, MyBitEnum bit) { 1430 return (static_cast<uint32_t>(bits) & static_cast<uint32_t>(bit)) != 0; 1431} 1432 1433namespace llvm { 1434template<> struct DenseMapInfo<::MyBitEnum> { 1435 using StorageInfo = llvm::DenseMapInfo<uint32_t>; 1436 1437 static inline ::MyBitEnum getEmptyKey() { 1438 return static_cast<::MyBitEnum>(StorageInfo::getEmptyKey()); 1439 } 1440 1441 static inline ::MyBitEnum getTombstoneKey() { 1442 return static_cast<::MyBitEnum>(StorageInfo::getTombstoneKey()); 1443 } 1444 1445 static unsigned getHashValue(const ::MyBitEnum &val) { 1446 return StorageInfo::getHashValue(static_cast<uint32_t>(val)); 1447 } 1448 1449 static bool isEqual(const ::MyBitEnum &lhs, const ::MyBitEnum &rhs) { 1450 return lhs == rhs; 1451 } 1452}; 1453``` 1454 1455```c++ 1456std::string stringifyMyBitEnum(MyBitEnum symbol) { 1457 auto val = static_cast<uint32_t>(symbol); 1458 assert(15u == (15u | val) && "invalid bits set in bit enum"); 1459 // Special case for all bits unset. 1460 if (val == 0) return "None"; 1461 llvm::SmallVector<llvm::StringRef, 2> strs; 1462 if (1u == (1u & val)) { strs.push_back("Bit0"); } 1463 if (2u == (2u & val)) { strs.push_back("Bit1"); } 1464 if (4u == (4u & val)) { strs.push_back("Bit2"); } 1465 if (8u == (8u & val)) { strs.push_back("Bit3"); } 1466 1467 return llvm::join(strs, "|"); 1468} 1469 1470llvm::Optional<MyBitEnum> symbolizeMyBitEnum(llvm::StringRef str) { 1471 // Special case for all bits unset. 1472 if (str == "None") return MyBitEnum::None; 1473 1474 llvm::SmallVector<llvm::StringRef, 2> symbols; 1475 str.split(symbols, "|"); 1476 1477 uint32_t val = 0; 1478 for (auto symbol : symbols) { 1479 auto bit = llvm::StringSwitch<llvm::Optional<uint32_t>>(symbol) 1480 .Case("Bit0", 1) 1481 .Case("Bit1", 2) 1482 .Case("Bit2", 4) 1483 .Case("Bit3", 8) 1484 .Default(llvm::None); 1485 if (bit) { val |= *bit; } else { return llvm::None; } 1486 } 1487 return static_cast<MyBitEnum>(val); 1488} 1489 1490llvm::Optional<MyBitEnum> symbolizeMyBitEnum(uint32_t value) { 1491 // Special case for all bits unset. 1492 if (value == 0) return MyBitEnum::None; 1493 1494 if (value & ~(1u | 2u | 4u | 8u)) return llvm::None; 1495 return static_cast<MyBitEnum>(value); 1496} 1497``` 1498 1499## Type Definitions 1500 1501MLIR defines the `TypeDef` class hierarchy to enable generation of data types from 1502their specifications. A type is defined by specializing the `TypeDef` class with 1503concrete contents for all the fields it requires. For example, an integer type 1504could be defined as: 1505 1506```tablegen 1507// All of the types will extend this class. 1508class Test_Type<string name> : TypeDef<Test_Dialect, name> { } 1509 1510// An alternate int type. 1511def IntegerType : Test_Type<"TestInteger"> { 1512 let mnemonic = "int"; 1513 1514 let summary = "An integer type with special semantics"; 1515 1516 let description = [{ 1517 An alternate integer type. This type differentiates itself from the 1518 standard integer type by not having a SignednessSemantics parameter, just 1519 a width. 1520 }]; 1521 1522 let parameters = (ins "unsigned":$width); 1523 1524 // We define the printer inline. 1525 let printer = [{ 1526 $_printer << "int<" << getImpl()->width << ">"; 1527 }]; 1528 1529 // The parser is defined here also. 1530 let parser = [{ 1531 if ($_parser.parseLess()) 1532 return Type(); 1533 int width; 1534 if ($_parser.parseInteger(width)) 1535 return Type(); 1536 if ($_parser.parseGreater()) 1537 return Type(); 1538 return get($_ctxt, width); 1539 }]; 1540} 1541``` 1542 1543### Type name 1544 1545The name of the C++ class which gets generated defaults to 1546`<classParamName>Type` (e.g. `TestIntegerType` in the above example). This can 1547be overridden via the `cppClassName` field. The field `mnemonic` is to specify 1548the asm name for parsing. It is optional and not specifying it will imply that 1549no parser or printer methods are attached to this class. 1550 1551### Type documentation 1552 1553The `summary` and `description` fields exist and are to be used the same way as 1554in Operations. Namely, the summary should be a one-liner and `description` 1555should be a longer explanation. 1556 1557### Type parameters 1558 1559The `parameters` field is a list of the type's parameters. If no parameters are 1560specified (the default), this type is considered a singleton type. Parameters 1561are in the `"c++Type":$paramName` format. To use C++ types as parameters which 1562need allocation in the storage constructor, there are two options: 1563 1564- Set `hasCustomStorageConstructor` to generate the TypeStorage class with a 1565 constructor which is just declared -- no definition -- so you can write it 1566 yourself. 1567- Use the `TypeParameter` tablegen class instead of the "c++Type" string. 1568 1569### TypeParameter tablegen class 1570 1571This is used to further specify attributes about each of the types parameters. 1572It includes documentation (`summary` and `syntax`), the C++ type to use, a 1573custom allocator to use in the storage constructor method, and a custom 1574comparator to decide if two instances of the parameter type are equal. 1575 1576```tablegen 1577// DO NOT DO THIS! 1578let parameters = (ins "ArrayRef<int>":$dims); 1579``` 1580 1581The default storage constructor blindly copies fields by value. It does not know 1582anything about the types. In this case, the ArrayRef<int> requires allocation 1583with `dims = allocator.copyInto(dims)`. 1584 1585You can specify the necessary constructor by specializing the `TypeParameter` 1586tblgen class: 1587 1588```tablegen 1589class ArrayRefIntParam : 1590 TypeParameter<"::llvm::ArrayRef<int>", "Array of ints"> { 1591 let allocator = "$_dst = $_allocator.copyInto($_self);"; 1592} 1593 1594... 1595 1596let parameters = (ins ArrayRefIntParam:$dims); 1597``` 1598 1599The `allocator` code block has the following substitutions: 1600 1601- `$_allocator` is the TypeStorageAllocator in which to allocate objects. 1602- `$_dst` is the variable in which to place the allocated data. 1603 1604The `comparator` code block has the following substitutions: 1605 1606- `$_lhs` is an instance of the parameter type. 1607- `$_rhs` is an instance of the parameter type. 1608 1609MLIR includes several specialized classes for common situations: 1610 1611- `StringRefParameter<descriptionOfParam>` for StringRefs. 1612- `ArrayRefParameter<arrayOf, descriptionOfParam>` for ArrayRefs of value 1613 types 1614- `SelfAllocationParameter<descriptionOfParam>` for C++ classes which contain 1615 a method called `allocateInto(StorageAllocator &allocator)` to allocate 1616 itself into `allocator`. 1617- `ArrayRefOfSelfAllocationParameter<arrayOf, descriptionOfParam>` for arrays 1618 of objects which self-allocate as per the last specialization. 1619 1620If we were to use one of these included specializations: 1621 1622```tablegen 1623let parameters = (ins 1624 ArrayRefParameter<"int", "The dimensions">:$dims 1625); 1626``` 1627 1628### Parsing and printing 1629 1630If a mnemonic is specified, the `printer` and `parser` code fields are active. 1631The rules for both are: 1632 1633- If null, generate just the declaration. 1634- If non-null and non-empty, use the code in the definition. The `$_printer` 1635 or `$_parser` substitutions are valid and should be used. 1636- It is an error to have an empty code block. 1637 1638For each dialect, two "dispatch" functions will be created: one for parsing and 1639one for printing. You should add calls to these in your `Dialect::printType` and 1640`Dialect::parseType` methods. They are static functions placed alongside the 1641type class definitions and have the following function signatures: 1642 1643```c++ 1644static Type generatedTypeParser(MLIRContext* ctxt, DialectAsmParser& parser, StringRef mnemonic); 1645LogicalResult generatedTypePrinter(Type type, DialectAsmPrinter& printer); 1646``` 1647 1648The mnemonic, parser, and printer fields are optional. If they're not defined, 1649the generated code will not include any parsing or printing code and omit the 1650type from the dispatch functions above. In this case, the dialect author is 1651responsible for parsing/printing the types in `Dialect::printType` and 1652`Dialect::parseType`. 1653 1654### Other fields 1655 1656- If the `genStorageClass` field is set to 1 (the default) a storage class is 1657 generated with member variables corresponding to each of the specified 1658 `parameters`. 1659- If the `genAccessors` field is 1 (the default) accessor methods will be 1660 generated on the Type class (e.g. `int getWidth() const` in the example 1661 above). 1662- If the `genVerifyDecl` field is set, a declaration for a method `static 1663 LogicalResult verify(emitErrorFn, parameters...)` is added to the class as 1664 well as a `getChecked(emitErrorFn, parameters...)` method which checks the 1665 result of `verify` before calling `get`. 1666- The `storageClass` field can be used to set the name of the storage class. 1667- The `storageNamespace` field is used to set the namespace where the storage 1668 class should sit. Defaults to "detail". 1669- The `extraClassDeclaration` field is used to include extra code in the class 1670 declaration. 1671 1672### Type builder methods 1673 1674For each type, there are a few builders(`get`/`getChecked`) automatically 1675generated based on the parameters of the type. For example, given the following 1676type definition: 1677 1678```tablegen 1679def MyType : ... { 1680 let parameters = (ins "int":$intParam); 1681} 1682``` 1683 1684The following builders are generated: 1685 1686```c++ 1687// Type builders are named `get`, and return a new instance of a type for a 1688// given set of parameters. 1689static MyType get(MLIRContext *context, int intParam); 1690 1691// If `genVerifyDecl` is set to 1, the following method is also generated. 1692static MyType getChecked(function_ref<InFlightDiagnostic()> emitError, 1693 MLIRContext *context, int intParam); 1694``` 1695 1696If these autogenerated methods are not desired, such as when they conflict with 1697a custom builder method, a type can set `skipDefaultBuilders` to 1 to signal 1698that they should not be generated. 1699 1700#### Custom type builder methods 1701 1702The default build methods may cover a majority of the simple cases related to 1703type construction, but when they cannot satisfy a type's needs, you can define 1704additional convenience 'get' methods in the `builders` field as follows: 1705 1706```tablegen 1707def MyType : ... { 1708 let parameters = (ins "int":$intParam); 1709 1710 let builders = [ 1711 TypeBuilder<(ins "int":$intParam)>, 1712 TypeBuilder<(ins CArg<"int", "0">:$intParam)>, 1713 TypeBuilder<(ins CArg<"int", "0">:$intParam), [{ 1714 // Write the body of the `get` builder inline here. 1715 return Base::get($_ctxt, intParam); 1716 }]>, 1717 TypeBuilderWithInferredContext<(ins "Type":$typeParam), [{ 1718 // This builder states that it can infer an MLIRContext instance from 1719 // its arguments. 1720 return Base::get(typeParam.getContext(), ...); 1721 }]>, 1722 ]; 1723} 1724``` 1725 1726The `builders` field is a list of custom builders that are added to the type 1727class. In this example, we provide several different convenience builders that 1728are useful in different scenarios. The `ins` prefix is common to many function 1729declarations in ODS, which use a TableGen [`dag`](#tablegen-syntax). What 1730follows is a comma-separated list of types (quoted string or `CArg`) and names 1731prefixed with the `$` sign. The use of `CArg` allows for providing a default 1732value to that argument. Let's take a look at each of these builders individually 1733 1734The first builder will generate the declaration of a builder method that looks 1735like: 1736 1737```tablegen 1738 let builders = [ 1739 TypeBuilder<(ins "int":$intParam)>, 1740 ]; 1741``` 1742 1743```c++ 1744class MyType : /*...*/ { 1745 /*...*/ 1746 static MyType get(::mlir::MLIRContext *context, int intParam); 1747}; 1748``` 1749 1750This builder is identical to the one that will be automatically generated for 1751`MyType`. The `context` parameter is implicitly added by the generator, and is 1752used when building the Type instance (with `Base::get`). The distinction 1753here is that we can provide the implementation of this `get` method. With this 1754style of builder definition only the declaration is generated, the implementor 1755of `MyType` will need to provide a definition of `MyType::get`. 1756 1757The second builder will generate the declaration of a builder method that looks 1758like: 1759 1760```tablegen 1761 let builders = [ 1762 TypeBuilder<(ins CArg<"int", "0">:$intParam)>, 1763 ]; 1764``` 1765 1766```c++ 1767class MyType : /*...*/ { 1768 /*...*/ 1769 static MyType get(::mlir::MLIRContext *context, int intParam = 0); 1770}; 1771``` 1772 1773The constraints here are identical to the first builder example except for the 1774fact that `intParam` now has a default value attached. 1775 1776The third builder will generate the declaration of a builder method that looks 1777like: 1778 1779```tablegen 1780 let builders = [ 1781 TypeBuilder<(ins CArg<"int", "0">:$intParam), [{ 1782 // Write the body of the `get` builder inline here. 1783 return Base::get($_ctxt, intParam); 1784 }]>, 1785 ]; 1786``` 1787 1788```c++ 1789class MyType : /*...*/ { 1790 /*...*/ 1791 static MyType get(::mlir::MLIRContext *context, int intParam = 0); 1792}; 1793 1794MyType MyType::get(::mlir::MLIRContext *context, int intParam) { 1795 // Write the body of the `get` builder inline here. 1796 return Base::get(context, intParam); 1797} 1798``` 1799 1800This is identical to the second builder example. The difference is that now, a 1801definition for the builder method will be generated automatically using the 1802provided code block as the body. When specifying the body inline, `$_ctxt` may 1803be used to access the `MLIRContext *` parameter. 1804 1805The fourth builder will generate the declaration of a builder method that looks 1806like: 1807 1808```tablegen 1809 let builders = [ 1810 TypeBuilderWithInferredContext<(ins "Type":$typeParam), [{ 1811 // This builder states that it can infer an MLIRContext instance from 1812 // its arguments. 1813 return Base::get(typeParam.getContext(), ...); 1814 }]>, 1815 ]; 1816``` 1817 1818```c++ 1819class MyType : /*...*/ { 1820 /*...*/ 1821 static MyType get(Type typeParam); 1822}; 1823 1824MyType MyType::get(Type typeParam) { 1825 // This builder states that it can infer an MLIRContext instance from its 1826 // arguments. 1827 return Base::get(typeParam.getContext(), ...); 1828} 1829``` 1830 1831In this builder example, the main difference from the third builder example 1832there is that the `MLIRContext` parameter is no longer added. This is because 1833the type builder used `TypeBuilderWithInferredContext` implies that the context 1834parameter is not necessary as it can be inferred from the arguments to the 1835builder. 1836 1837## Debugging Tips 1838 1839### Run `mlir-tblgen` to see the generated content 1840 1841TableGen syntax sometimes can be obscure; reading the generated content can be a 1842very helpful way to understand and debug issues. To build `mlir-tblgen`, run 1843`cmake --build . --target mlir-tblgen` in your build directory and find the 1844`mlir-tblgen` binary in the `bin/` subdirectory. All the supported generators 1845can be found via `mlir-tblgen --help`. For example, `--gen-op-decls` and 1846`--gen-op-defs` as explained in [Generated C++ code](#generated-c-code). 1847 1848To see the generated code, invoke `mlir-tblgen` with a specific generator by 1849providing include paths via `-I`. For example, 1850 1851```sh 1852# To see op C++ class declaration 1853mlir-tblgen --gen-op-decls -I /path/to/mlir/include /path/to/input/td/file 1854# To see op C++ class definition 1855mlir-tblgen --gen-op-defs -I /path/to/mlir/include /path/to/input/td/file 1856# To see op documentation 1857mlir-tblgen --gen-dialect-doc -I /path/to/mlir/include /path/to/input/td/file 1858 1859# To see op interface C++ class declaration 1860mlir-tblgen --gen-op-interface-decls -I /path/to/mlir/include /path/to/input/td/file 1861# To see op interface C++ class definition 1862mlir-tblgen --gen-op-interface-defs -I /path/to/mlir/include /path/to/input/td/file 1863# To see op interface documentation 1864mlir-tblgen --gen-op-interface-doc -I /path/to/mlir/include /path/to/input/td/file 1865``` 1866 1867## Appendix 1868 1869### Requirements and existing mechanisms analysis 1870 1871The op description should be as declarative as possible to allow a wide range of 1872tools to work with them and query methods generated from them. In particular 1873this means specifying traits, constraints and shape inference information in a 1874way that is easily analyzable (e.g., avoid opaque calls to C++ functions where 1875possible). 1876 1877We considered the approaches of several contemporary systems and focused on 1878requirements that were desirable: 1879 1880* Ops registered using a registry separate from C++ code. 1881 * Unknown ops are allowed in MLIR, so ops need not be registered. The 1882 ability of the compiler to optimize those ops or graphs containing those 1883 ops is constrained but correct. 1884 * The current proposal does not include a runtime op description, but it 1885 does not preclude such description, it can be added later. 1886 * The op registry is essential for generating C++ classes that make 1887 manipulating ops, verifying correct construction etc. in C++ easier by 1888 providing a typed representation and accessors. 1889* The op registry will be defined in 1890 [TableGen](https://llvm.org/docs/TableGen/index.html) and be used to 1891 generate C++ classes and utility functions 1892 (builder/verifier/parser/printer). 1893 * TableGen is a modelling specification language used by LLVM's backends 1894 and fits in well with trait-based modelling. This is an implementation 1895 decision and there are alternative ways of doing this. But the 1896 specification language is good for the requirements of modelling the 1897 traits (as seen from usage in LLVM processor backend modelling) and easy 1898 to extend, so a practical choice. If another good option comes up, we 1899 will consider it. 1900* MLIR allows both defined and undefined ops. 1901 * Defined ops should have fixed semantics and could have a corresponding 1902 reference implementation defined. 1903 * Dialects are under full control of the dialect owner and normally live 1904 with the framework of the dialect. 1905* The op's traits (e.g., commutative) are modelled along with the op in the 1906 registry. 1907* The op's operand/return type constraints are modelled along with the op in 1908 the registry (see [Shape inference](ShapeInference.md) discussion below), 1909 this allows (e.g.) optimized concise syntax in textual dumps. 1910* Behavior of the op is documented along with the op with a summary and a 1911 description. The description is written in markdown and extracted for 1912 inclusion in the generated LangRef section of the dialect. 1913* The generic assembly form of printing and parsing is available as normal, 1914 but a custom parser and printer can either be specified or automatically 1915 generated from an optional string representation showing the mapping of the 1916 "assembly" string to operands/type. 1917 * Parser-level remappings (e.g., `eq` to enum) will be supported as part 1918 of the parser generation. 1919* Matching patterns are specified separately from the op description. 1920 * Contrasted with LLVM there is no "base" set of ops that every backend 1921 needs to be aware of. Instead there are many different dialects and the 1922 transformations/legalizations between these dialects form a graph of 1923 transformations. 1924* Reference implementation may be provided along with the op definition. 1925 1926 * The reference implementation may be in terms of either standard ops or 1927 other reference implementations. 1928 1929 TODO: document expectation if the dependent op's definition changes. 1930 1931[TableGen]: https://llvm.org/docs/TableGen/index.html 1932[TableGenProgRef]: https://llvm.org/docs/TableGen/ProgRef.html 1933[TableGenBackend]: https://llvm.org/docs/TableGen/BackEnds.html#introduction 1934[OpBase]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/IR/OpBase.td 1935[OpDefinitionsGen]: https://github.com/llvm/llvm-project/blob/main/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp 1936[EnumsGen]: https://github.com/llvm/llvm-project/blob/main/mlir/tools/mlir-tblgen/EnumsGen.cpp 1937[StringAttr]: Dialects/Builtin.md/#stringattr 1938[IntegerAttr]: Dialects/Builtin.md/#integertype 1939[AttrClasses]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/IR/Attributes.h 1940