1# Table-driven Declarative Rewrite Rule (DRR) 2 3In addition to subclassing the `mlir::RewritePattern` C++ class, MLIR also 4supports defining rewrite rules in a declarative manner. Similar to 5[Op Definition Specification](OpDefinitions.md) (ODS), this is achieved via 6[TableGen][TableGen], which is a language to maintain records of domain-specific 7information. The rewrite rules are specified concisely in a TableGen record, 8which will be expanded into an equivalent `mlir::RewritePattern` subclass at 9compiler build time. 10 11This manual explains in detail all of the available mechanisms for defining 12rewrite rules in such a declarative manner. It aims to be a specification 13instead of a tutorial. Please refer to 14[Quickstart tutorial to adding MLIR graph rewrite](QuickstartRewrites.md) for 15the latter. 16 17Given that declarative rewrite rules depend on op definition specification, this 18manual assumes knowledge of the [ODS](OpDefinitions.md) doc. 19 20## Benefits 21 22Compared to the hand-written C++ classes, this declarative approach has several 23benefits, including but not limited to: 24 25* **Being declarative**: The pattern creator just needs to state the rewrite 26 pattern declaratively, without worrying about the concrete C++ methods to 27 call. 28* **Removing boilerplate and showing the very essence of the rewrite**: 29 `mlir::RewritePattern` is already good at hiding boilerplate for defining a 30 rewrite rule. But we still need to write the class and function structures 31 required by the C++ programming language, inspect ops for matching, and call 32 op `build()` methods for constructing. These statements are typically quite 33 simple and similar, so they can be further condensed with auto-generation. 34 Because we reduce the boilerplate to the bare minimum, the declarative 35 rewrite rule will just contain the very essence of the rewrite. This makes 36 it very easy to understand the pattern. 37 38## Strengths and Limitations 39 40The declarative rewrite rule is **operation-based**: it describes a rule to 41match against a directed acyclic graph (DAG) of operations and generate DAGs of 42operations. This gives DRR both its strengths and limitations: it is good at 43expressing op to op conversions, but not that well suited for, say, converting 44an op into a loop nest. 45 46Per the current implementation, DRR does not have good support for the following 47features: 48 49* Matching and generating ops with regions. 50* Matching and generating ops with block arguments. 51* Matching multi-result ops in nested patterns. 52* Matching and generating variadic operand/result ops in nested patterns. 53* Packing and unpacking variadic operands/results during generation. 54* [`NativeCodeCall`](#native-code-call-transforming-the-generated-op) 55 returning more than one results. 56 57## Rule Definition 58 59The core construct for defining a rewrite rule is defined in 60[`OpBase.td`][OpBase] as 61 62```tablegen 63class Pattern< 64 dag sourcePattern, list<dag> resultPatterns, 65 list<dag> additionalConstraints = [], 66 dag benefitsAdded = (addBenefit 0)>; 67``` 68 69A declarative rewrite rule contains two main components: 70 71* A _source pattern_, which is used for matching a DAG of operations. 72* One or more _result patterns_, which are used for generating DAGs of 73 operations to replace the matched DAG of operations. 74 75We allow multiple result patterns to support 76[multi-result ops](#supporting-multi-result-ops) and 77[auxiliary ops](#supporting-auxiliary-ops), but frequently we just want to 78convert one DAG of operations to another DAG of operations. There is a handy 79wrapper of `Pattern`, `Pat`, which takes a single result pattern: 80 81```tablegen 82class Pat< 83 dag sourcePattern, dag resultPattern, 84 list<dag> additionalConstraints = [], 85 dag benefitsAdded = (addBenefit 0)> : 86 Pattern<sourcePattern, [resultPattern], additionalConstraints, benefitAdded>; 87``` 88 89Each pattern is specified as a TableGen `dag` object with the syntax of 90`(operator arg0, arg1, ...)`. 91 92`operator` is typically an MLIR op, but it can also be other 93[directives](#special-directives). `argN` is for matching (if used in source 94pattern) or generating (if used in result pattern) the `N`-th argument for 95`operator`. If the `operator` is some MLIR operation, it means the `N`-th 96argument as specified in the `arguments` list of the op's definition. 97Therefore, we say op argument specification in pattern is **position-based**: 98the position where they appear matters. 99 100`argN` can be a `dag` object itself, thus we can have nested `dag` tree to model 101the def-use relationship between ops. 102 103### Source pattern 104 105The source pattern is for matching a DAG of operations. Arguments in the `dag` 106object are intended to **capture** the op arguments. They can also be used to 107**further limit** the match criteria. The capturing is done by specifying a 108symbol starting with the `$` sign, while further constraints are introduced by 109specifying a `TypeConstraint` (for an operand) or a `AttrConstraint` (for an 110attribute). 111 112#### Binding op arguments and limiting the match 113 114For example, 115 116```tablegen 117def AOp : Op<"a_op"> { 118 let arguments = (ins 119 AnyType:$a_input, 120 AnyAttr:$a_attr 121 ); 122 123 let results = (outs 124 AnyType:$a_output 125 ); 126} 127 128def : Pat<(AOp $input, F32Attr:$attr), ...>; 129``` 130 131In the above, we are matching an `AOp` whose `$input` can be anything valid as 132defined by the op and whose `$attr` must be a float attribute. If the match 133succeeds, we bind the `$input` symbol to the op's only input (`$a_input`) and 134`$attr` to the only attribute (`$a_attr`); we can reference them using `$input` 135and `$attr` in result patterns and additional constraints. 136 137The pattern is position-based: the symbol names used for capturing here do not 138need to match with the op definition as shown in the above example. As another 139example, the pattern can be written as ` def : Pat<(AOp $a, F32Attr:$b), ...>;` 140and use `$a` and `$b` to refer to the captured input and attribute. But using 141the ODS name directly in the pattern is also allowed. 142 143Also note that we only need to add `TypeConstraint` or `AttributeConstraint` 144when we need to further limit the match criteria. If all valid cases to the op 145are acceptable, then we can leave the constraint unspecified. 146 147`$_` is a special symbol to mean ignore capturing an argument. For example, 148`def : Pat<(AOp $_, $b), ...>` means only `$b` is interesting to capture and 149will be referenced later in result patterns. It's still possible to place 150additional constraints even if the symbol is not to be captured; for such case, 151you can simply use just the `TypeConstraint` or `AttributeConstraint` without a 152bound symbol, for example, `def : Pat<(AOp $a, F32Attr), ...>`. 153 154#### Matching DAG of operations 155 156To match an DAG of ops, use nested `dag` objects: 157 158```tablegen 159 160def BOp : Op<"b_op"> { 161 let arguments = (ins); 162 163 let results = (outs 164 AnyType:$b_output 165 ); 166} 167 168 169def : Pat<(AOp (BOp), $attr), ...>; 170``` 171 172The above pattern matches an `AOp` whose only operand is generated by a `BOp`, 173that is, the following MLIR code: 174 175```mlir 176%0 = "b_op"() : () -> (...) 177%1 = "a_op"(%0) {attr: ...} : () -> (...) 178``` 179 180#### Binding op results 181 182To bind a symbol to the results of a matched op for later reference, attach the 183symbol to the op itself: 184 185```tablegen 186def : Pat<(AOp (BOp:$b_result), $attr), ...>; 187``` 188 189The above will bind `$b_result` to the matched `BOp`'s result. (There are more 190details regarding multi-result ops, which is covered 191[later](#supporting-multi-result-ops).) 192 193### Result pattern 194 195The result pattern is for generating a DAG of operations. Arguments in the `dag` 196object are intended to **reference** values captured in the source pattern and 197potentially **apply transformations**. 198 199#### Referencing bound symbols 200 201For example, 202 203```tablegen 204def COp : Op<"c_op"> { 205 let arguments = (ins 206 AnyType:$c_input, 207 AnyAttr:$c_attr 208 ); 209 210 let results = (outs 211 AnyType:$c_output 212 ); 213} 214 215def : Pat<(AOp $input, $attr), (COp $input, $attr)>; 216``` 217 218In the above, `AOp`'s only operand and attribute are bound to `$input` and 219`$attr`, respectively. We then reference them in the result pattern for 220generating the `COp` by passing them in as arguments to `COp`'s `build()` 221method. 222 223We can also reference symbols bound to matched op's results: 224 225```tablegen 226def : Pat<(AOp (BOp:$b_result) $attr), (COp $b_result $attr)>; 227``` 228 229In the above, we are using `BOp`'s result for building `COp`. 230 231#### Building operations 232 233Given that `COp` was specified with table-driven op definition, there will be 234several `build()` methods generated for it. One of them has aggregated 235parameters for result types, operands, and attributes in the signature: `void 236COp::build(..., ArrayRef<Type> resultTypes, Array<Value> operands, 237ArrayRef<NamedAttribute> attr)`. The pattern in the above calls this `build()` 238method for constructing the `COp`. 239 240In general, arguments in the result pattern will be passed directly to the 241`build()` method to leverage the auto-generated `build()` method, list them in 242the pattern by following the exact same order as the ODS `arguments` definition. 243Otherwise, a custom `build()` method that matches the argument list is required. 244 245Right now all ODS-generated `build()` methods require specifying the result 246type(s), unless the op has known traits like `SameOperandsAndResultType` that 247we can use to auto-generate a `build()` method with result type deduction. 248When generating an op to replace the result of the matched root op, we can use 249the matched root op's result type when calling the ODS-generated builder. 250Otherwise (e.g., generating an [auxiliary op](#supporting-auxiliary-ops) or 251generating an op with a nested result pattern), DRR will not be able to deduce 252the result type(s). The pattern author will need to define a custom builder 253that has result type deduction ability via `OpBuilder` in ODS. For example, 254in the following pattern 255 256```tablegen 257def : Pat<(AOp $input, $attr), (COp (AOp $input, $attr) $attr)>; 258``` 259 260`AOp` is generated via a nested result pattern; DRR won't be able to deduce the 261result type for it. A custom builder for `AOp` should be defined and it should 262deduce the result type by itself. The builder should have the separate parameter 263for each operand and attribute and deduce the result type internally by itself. 264For example, for the above `AOp`, a possible builder is: 265 266```c++ 267 268void AOp::build(Builder *builder, OperationState &state, 269 Value input, Attribute attr) { 270 state.addOperands({input}); 271 state.addAttribute("a_attr", attr); 272 Type type = ...; // Deduce result type here 273 state.addTypes({type}); 274} 275``` 276 277Failing to define such a builder will result in an error at C++ compilation time 278saying the call to `AOp::build()` cannot be resolved because of the number of 279parameters mismatch. 280 281#### Generating DAG of operations 282 283`dag` objects can be nested to generate a DAG of operations: 284 285```tablegen 286def : Pat<(AOp $input, $attr), (COp (BOp), $attr)>; 287``` 288 289In the above, we generate a `BOp`, and then use its result to generate the `COp` 290to replace the matched `AOp`. 291 292#### Binding op results 293 294In the result pattern, we can bind to the result(s) of a newly built op by 295attaching symbols to the op. (But we **cannot** bind to op arguments given that 296they are referencing previously bound symbols.) This is useful for reusing 297newly created results where suitable. For example, 298 299```tablegen 300def DOp : Op<"d_op"> { 301 let arguments = (ins 302 AnyType:$d_input1, 303 AnyType:$d_input2, 304 ); 305 306 let results = (outs 307 AnyType:$d_output 308 ); 309} 310 311def : Pat<(AOp $input, $ignored_attr), (DOp (BOp:$b_result) $b_result)>; 312``` 313 314In this pattern, an `AOp` is matched and replaced with a `DOp` whose two 315operands are from the result of a single `BOp`. This is only possible by binding 316the result of the `BOp` to a name and reuse it for the second operand of the 317`DOp` 318 319#### `NativeCodeCall`: transforming the generated op 320 321Sometimes the captured arguments are not exactly what we want so they cannot be 322directly fed in as arguments to build the new op. For such cases, we can apply 323transformations on the arguments by calling into C++ helper functions. This is 324achieved by `NativeCodeCall`. 325 326For example, if we want to capture some op's attributes and group them as an 327array attribute to construct a new op: 328 329```tablegen 330 331def TwoAttrOp : Op<"two_attr_op"> { 332 let arguments = (ins 333 AnyAttr:$op_attr1, 334 AnyAttr:$op_attr2 335 ); 336 337 let results = (outs 338 AnyType:$op_output 339 ); 340} 341 342def OneAttrOp : Op<"one_attr_op"> { 343 let arguments = (ins 344 ArrayAttr:$op_attr 345 ); 346 347 let results = (outs 348 AnyType:$op_output 349 ); 350} 351``` 352 353We can write a C++ helper function: 354 355```c++ 356Attribute createArrayAttr(Builder &builder, Attribute a, Attribute b) { 357 return builder.getArrayAttr({a, b}); 358} 359``` 360 361And then write the pattern as: 362 363```tablegen 364def createArrayAttr : NativeCodeCall<"createArrayAttr($_builder, $0, $1)">; 365 366def : Pat<(TwoAttrOp $attr1, $attr2), 367 (OneAttrOp (createArrayAttr $attr1, $attr2))>; 368``` 369 370And make sure the generated C++ code from the above pattern has access to the 371definition of the C++ helper function. 372 373In the above example, we are using a string to specialize the `NativeCodeCall` 374template. The string can be an arbitrary C++ expression that evaluates into 375some C++ object expected at the `NativeCodeCall` site (here it would be 376expecting an array attribute). Typically the string should be a function call. 377 378Note that currently `NativeCodeCall` must return no more than one value or 379attribute. This might change in the future. 380 381##### `NativeCodeCall` placeholders 382 383In `NativeCodeCall`, we can use placeholders like `$_builder`, `$N`. The former 384is called _special placeholder_, while the latter is called _positional 385placeholder_. 386 387`NativeCodeCall` right now only supports two special placeholders: `$_builder` 388and `$_self`: 389 390* `$_builder` will be replaced by the current `mlir::PatternRewriter`. 391* `$_self` will be replaced with the entity `NativeCodeCall` is attached to. 392 393We have seen how `$_builder` can be used in the above; it allows us to pass a 394`mlir::Builder` (`mlir::PatternRewriter` is a subclass of `mlir::OpBuilder`, 395which is a subclass of `mlir::Builder`) to the C++ helper function to use the 396handy methods on `mlir::Builder`. 397 398`$_self` is useful when we want to write something in the form of 399`NativeCodeCall<"...">:$symbol`. For example, if we want to reverse the previous 400example and decompose the array attribute into two attributes: 401 402```tablegen 403class getNthAttr<int n> : NativeCodeCall<"$_self[" # n # "]">; 404 405def : Pat<(OneAttrOp $attr), 406 (TwoAttrOp (getNthAttr<0>:$attr), (getNthAttr<1>:$attr)>; 407``` 408 409In the above, `$_self` is substituted by the attribute bound by `$attr`, which 410is `OneAttrOp`'s array attribute. 411 412Positional placeholders will be substituted by the `dag` object parameters at 413the `NativeCodeCall` use site. For example, if we define `SomeCall : 414NativeCodeCall<"someFn($1, $2, $0)">` and use it like `(SomeCall $in0, $in1, 415$in2)`, then this will be translated into C++ call `someFn($in1, $in2, $in0)`. 416 417##### Customizing entire op building 418 419`NativeCodeCall` is not only limited to transforming arguments for building an 420op; it can be also used to specify how to build an op entirely. An example: 421 422If we have a C++ function for building an op: 423 424```c++ 425Operation *createMyOp(OpBuilder builder, Value input, Attribute attr); 426``` 427 428We can wrap it up and invoke it like: 429 430```tablegen 431def createMyOp : NativeCodeCall<"createMyOp($_builder, $0, $1)">; 432 433def : Pat<(... $input, $attr), (createMyOp $input, $attr)>; 434``` 435 436### Supporting auxiliary ops 437 438A declarative rewrite rule supports multiple result patterns. One of the 439purposes is to allow generating _auxiliary ops_. Auxiliary ops are operations 440used for building the replacement ops; but they are not directly used for 441replacement themselves. 442 443For the case of uni-result ops, if there are multiple result patterns, only the 444value generated from the last result pattern will be used to replace the matched 445root op's result; all other result patterns will be considered as generating 446auxiliary ops. 447 448Normally we want to specify ops as nested `dag` objects if their def-use 449relationship can be expressed in the way that an op's result can feed as the 450argument to consuming op. But that is not always possible. For example, if we 451want to allocate memory and store some computation (in pseudocode): 452 453```mlir 454%dst = addi %lhs, %rhs 455``` 456 457into 458 459```mlir 460%shape = shape %lhs 461%mem = alloc %shape 462%sum = addi %lhs, %rhs 463store %mem, %sum 464%dst = load %mem 465``` 466 467We cannot fit in with just one result pattern given `store` does not return a 468value. Instead we can use multiple result patterns: 469 470```tablegen 471def : Pattern<(AddIOp $lhs, $rhs), 472 [(StoreOp (AllocOp:$mem (ShapeOp $lhs)), (AddIOp $lhs, $rhs)), 473 (LoadOp $mem)]; 474``` 475 476In the above we use the first result pattern to generate the first four ops, and 477use the last pattern to generate the last op, which is used to replace the 478matched op. 479 480### Supporting multi-result ops 481 482Multi-result ops bring extra complexity to declarative rewrite rules. We use 483TableGen `dag` objects to represent ops in patterns; there is no native way to 484indicate that an op generates multiple results. The approach adopted is based 485on **naming convention**: a `__N` suffix is added to a symbol to indicate the 486`N`-th result. 487 488#### `__N` suffix 489 490The `__N` suffix is specifying the `N`-th result as a whole (which can be 491[variadic](#supporting-variadic-ops)). For example, we can bind a symbol to some 492multi-result op and reference a specific result later: 493 494```tablegen 495def ThreeResultOp : Op<"three_result_op"> { 496 let arguments = (ins ...); 497 498 let results = (outs 499 AnyTensor:$op_output1, 500 AnyTensor:$op_output2, 501 AnyTensor:$op_output3 502 ); 503} 504 505def : Pattern<(ThreeResultOp:$results ...), 506 [(... $results__0), ..., (... $results__2), ...]>; 507``` 508 509In the above pattern we bind `$results` to all the results generated by 510`ThreeResultOp` and references its `$input1` and `$input3` later in the result 511patterns. 512 513We can also bind a symbol and reference one of its specific result at the same 514time, which is typically useful when generating multi-result ops: 515 516```tablegen 517// TwoResultOp has similar definition as ThreeResultOp, but only has two 518// results. 519 520def : Pattern<(TwoResultOp ...), 521 [(ThreeResultOp:$results__2, ...), 522 (replaceWithValue $results__0)]>; 523``` 524 525In the above, we created a `ThreeResultOp` and bind `results` to its results, 526and uses its last result (`$output3`) and first result (`$output1`) to replace 527the `TwoResultOp`'s two results, respectively. 528 529#### Replacing multi-result ops 530 531The above example also shows how to replace a matched multi-result op. 532 533To replace a `N`-result op, the result patterns must generate at least `N` 534declared values (see [Declared vs. actual value](#declared-vs-actual-value) for 535definition). If there are more than `N` declared values generated, only the 536last `N` declared values will be used to replace the matched op. Note that 537because of the existence of multi-result op, one result pattern **may** generate 538multiple declared values. So it means we do not necessarily need `N` result 539patterns to replace an `N`-result op. For example, to replace an op with three 540results, you can have 541 542```tablegen 543// ThreeResultOp/TwoResultOp/OneResultOp generates three/two/one result(s), 544// respectively. 545 546// Replace each result with a result generated from an individual op. 547def : Pattern<(ThreeResultOp ...), 548 [(OneResultOp ...), (OneResultOp ...), (OneResultOp ...)]>; 549 550// Replace the first two results with two results generated from the same op. 551def : Pattern<(ThreeResultOp ...), 552 [(TwoResultOp ...), (OneResultOp ...)]>; 553 554// Replace all three results with three results generated from the same op. 555def : Pat<(ThreeResultOp ...), (ThreeResultOp ...)>; 556 557def : Pattern<(ThreeResultOp ...), 558 [(AuxiliaryOp ...), (ThreeResultOp ...)]>; 559``` 560 561But using a single op to serve as both auxiliary op and replacement op is 562forbidden, i.e., the following is not allowed because that the first 563`TwoResultOp` generates two results but only the second result is used for 564replacing the matched op's result: 565 566```tablegen 567def : Pattern<(ThreeResultOp ...), 568 [(TwoResultOp ...), (TwoResultOp ...)]>; 569``` 570 571### Supporting variadic ops 572 573#### Declared vs. actual value 574 575Before going into details on variadic op support, we need to define a few terms 576regarding an op's values. 577 578* _Value_: either an operand or a result 579* _Declared operand/result/value_: an operand/result/value statically declared 580 in ODS of the op 581* _Actual operand/result/value_: an operand/result/value of an op instance at 582 runtime 583 584The above terms are needed because ops can have multiple results, and some of the 585results can also be variadic. For example, 586 587```tablegen 588def MultiVariadicOp : Op<"multi_variadic_op"> { 589 let arguments = (ins 590 AnyTensor:$input1, 591 Variadic<AnyTensor>:$input2, 592 AnyTensor:$input3 593 ); 594 595 let results = (outs 596 AnyTensor:$output1, 597 Variadic<AnyTensor>:$output2, 598 AnyTensor:$output3 599 ); 600} 601``` 602 603We say the above op has 3 declared operands and 3 declared results. But at 604runtime, an instance can have 3 values corresponding to `$input2` and 2 values 605correspond to `$output2`; we say it has 5 actual operands and 4 actual 606results. A variadic operand/result is a considered as a declared value that can 607correspond to multiple actual values. 608 609[TODO] 610 611### Supplying additional constraints 612 613Constraints can be placed on op arguments when matching. But sometimes we need 614to also place constraints on the matched op's results or sometimes need to limit 615the matching with some constraints that cover both the arguments and the 616results. The third parameter to `Pattern` (and `Pat`) is for this purpose. 617 618For example, we can write 619 620```tablegen 621def HasNoUseOf: Constraint<CPred<"$_self.use_empty()">, "has no use">; 622 623def HasSameElementType : Constraint< 624 CPred<"$0.cast<ShapedType>().getElementType() == " 625 "$1.cast<ShapedType>().getElementType()">, 626 "has same element type">; 627 628def : Pattern<(TwoResultOp:$results $input), 629 [(...), (...)], 630 [(F32Tensor:$results__0), (HasNoUseOf:$results__1), 631 (HasSameElementShape $results__0, $input)]>; 632``` 633 634You can 635 636* Use normal `TypeConstraint`s on previous bound symbols (the first result of 637 `TwoResultOp` must be a float tensor); 638* Define new `Constraint` for previous bound symbols (the second result of 639 `TwoResultOp` must has no use); 640* Apply constraints on multiple bound symbols (`$input` and `TwoResultOp`'s 641 first result must have the same element type). 642 643### Adjusting benefits 644 645The benefit of a `Pattern` is an integer value indicating the benefit of matching 646the pattern. It determines the priorities of patterns inside the pattern rewrite 647driver. A pattern with a higher benefit is applied before one with a lower 648benefit. 649 650In DRR, a rule is set to have a benefit of the number of ops in the source 651pattern. This is based on the heuristics and assumptions that: 652 653* Larger matches are more beneficial than smaller ones. 654* If a smaller one is applied first the larger one may not apply anymore. 655 656 657The fourth parameter to `Pattern` (and `Pat`) allows to manually tweak a 658pattern's benefit. Just supply `(addBenefit N)` to add `N` to the benefit value. 659 660## Special directives 661 662[TODO] 663 664## Debugging Tips 665 666### Run `mlir-tblgen` to see the generated content 667 668TableGen syntax sometimes can be obscure; reading the generated content can be 669a very helpful way to understand and debug issues. To build `mlir-tblgen`, run 670`cmake --build . --target mlir-tblgen` in your build directory and find the 671`mlir-tblgen` binary in the `bin/` subdirectory. All the supported generators 672can be found via `mlir-tblgen --help`. 673 674To see the generated code, invoke `mlir-tblgen` with a specific generator by 675providing include paths via `-I`. For example, 676 677```sh 678# To see all the C++ pattern rewrite classes 679mlir-tblgen --gen-rewriters -I /path/to/mlir/include /path/to/input/td/file 680``` 681 682### Compilation error: no matching member function for call to 'build' 683 684This is because DRR is failing to call a `build()` method with result type 685deduction ability. See [building operations](#building-operations) for more 686details. 687 688[TableGen]: https://llvm.org/docs/TableGen/index.html 689[OpBase]: https://github.com/llvm/llvm-project/blob/master/mlir/include/mlir/IR/OpBase.td 690