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