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