# Table-driven Declarative Rewrite Rule (DRR) In addition to subclassing the `mlir::RewritePattern` C++ class, MLIR also supports defining rewrite rules in a declarative manner. Similar to [Op Definition Specification](DefiningDialects/Operations.md) (ODS), this is achieved via [TableGen][TableGen], which is a language to maintain records of domain-specific information. The rewrite rules are specified concisely in a TableGen record, which will be expanded into an equivalent `mlir::RewritePattern` subclass at compiler build time. This manual explains in detail all of the available mechanisms for defining rewrite rules in such a declarative manner. It aims to be a specification instead of a tutorial. Please refer to [Quickstart tutorial to adding MLIR graph rewrite](Tutorials/QuickstartRewrites.md) for the latter. Given that declarative rewrite rules depend on op definition specification, this manual assumes knowledge of the [ODS](DefiningDialects/Operations.md) doc. [TOC] ## Benefits Compared to the hand-written C++ classes, this declarative approach has several benefits, including but not limited to: * **Being declarative**: The pattern creator just needs to state the rewrite pattern declaratively, without worrying about the concrete C++ methods to call. * **Removing boilerplate and showing the very essence of the rewrite**: `mlir::RewritePattern` is already good at hiding boilerplate for defining a rewrite rule. But we still need to write the class and function structures required by the C++ programming language, inspect ops for matching, and call op `build()` methods for constructing. These statements are typically quite simple and similar, so they can be further condensed with auto-generation. Because we reduce the boilerplate to the bare minimum, the declarative rewrite rule will just contain the very essence of the rewrite. This makes it very easy to understand the pattern. ## Strengths and Limitations The declarative rewrite rule is **operation-based**: it describes a rule to match against a directed acyclic graph (DAG) of operations and generate DAGs of operations. This gives DRR both its strengths and limitations: it is good at expressing op to op conversions, but not that well suited for, say, converting an op into a loop nest. Per the current implementation, DRR does not have good support for the following features: * Matching and generating ops with regions. * Matching and generating ops with block arguments. * Matching multi-result ops in nested patterns. * Matching and generating variadic operand/result ops in nested patterns. * Packing and unpacking variadic operands/results during generation. * [`NativeCodeCall`](#nativecodecall-transforming-the-generated-op) returning more than one results. ## Rule Definition The core construct for defining a rewrite rule is defined in [`OpBase.td`][OpBase] as ```tablegen class Pattern< dag sourcePattern, list resultPatterns, list additionalConstraints = [], dag benefitsAdded = (addBenefit 0)>; ``` A declarative rewrite rule contains two main components: * A *source pattern*, which is used for matching a DAG of operations. * One or more *result patterns*, which are used for generating DAGs of operations to replace the matched DAG of operations. We allow multiple result patterns to support [multi-result ops](#supporting-multi-result-ops) and [auxiliary ops](#supporting-auxiliary-ops), but frequently we just want to convert one DAG of operations to another DAG of operations. There is a handy wrapper of `Pattern`, `Pat`, which takes a single result pattern: ```tablegen class Pat< dag sourcePattern, dag resultPattern, list additionalConstraints = [], dag benefitsAdded = (addBenefit 0)> : Pattern; ``` Each pattern is specified as a TableGen `dag` object with the syntax of `(operator arg0, arg1, ...)`. `operator` is typically an MLIR op, but it can also be other [directives](#rewrite-directives). `argN` is for matching (if used in source pattern) or generating (if used in result pattern) the `N`-th argument for `operator`. If the `operator` is some MLIR operation, it means the `N`-th argument as specified in the `arguments` list of the op's definition. Therefore, we say op argument specification in pattern is **position-based**: the position where they appear matters. `argN` can be a `dag` object itself, thus we can have nested `dag` tree to model the def-use relationship between ops. ### Source pattern The source pattern is for matching a DAG of operations. Arguments in the `dag` object are intended to **capture** the op arguments. They can also be used to **further limit** the match criteria. The capturing is done by specifying a symbol starting with the `$` sign, while further constraints are introduced by specifying a `TypeConstraint` (for an operand) or a `AttrConstraint` (for an attribute). #### Binding op arguments and limiting the match For example, ```tablegen def AOp : Op<"a_op"> { let arguments = (ins AnyType:$a_input, AnyAttr:$a_attr ); let results = (outs AnyType:$a_output ); } def : Pat<(AOp $input, F32Attr:$attr), ...>; ``` In the above, we are matching an `AOp` whose `$input` can be anything valid as defined by the op and whose `$attr` must be a float attribute. If the match succeeds, we bind the `$input` symbol to the op's only input (`$a_input`) and `$attr` to the only attribute (`$a_attr`); we can reference them using `$input` and `$attr` in result patterns and additional constraints. The pattern is position-based: the symbol names used for capturing here do not need to match with the op definition as shown in the above example. As another example, the pattern can be written as `def : Pat<(AOp $a, F32Attr:$b), ...>;` and use `$a` and `$b` to refer to the captured input and attribute. But using the ODS name directly in the pattern is also allowed. Operands in the source pattern can have the same name. This bounds one operand to the name while verifying the rest are all equal. Also note that we only need to add `TypeConstraint` or `AttributeConstraint` when we need to further limit the match criteria. If all valid cases to the op are acceptable, then we can leave the constraint unspecified. `$_` is a special symbol to mean ignore capturing an argument. For example, `def : Pat<(AOp $_, $b), ...>` means only `$b` is interesting to capture and will be referenced later in result patterns. It's still possible to place additional constraints even if the symbol is not to be captured; for such case, you can simply use just the `TypeConstraint` or `AttributeConstraint` without a bound symbol, for example, `def : Pat<(AOp $a, F32Attr), ...>`. #### Matching DAG of operations To match a DAG of ops, use nested `dag` objects: ```tablegen def BOp : Op<"b_op"> { let arguments = (ins); let results = (outs AnyType:$b_output ); } def : Pat<(AOp (BOp), $attr), ...>; ``` The above pattern matches an `AOp` whose only operand is generated by a `BOp`, that is, the following MLIR code: ```mlir %0 = "b_op"() : () -> (...) %1 = "a_op"(%0) {attr: ...} : () -> (...) ``` #### Binding op results To bind a symbol to the results of a matched op for later reference, attach the symbol to the op itself: ```tablegen def : Pat<(AOp (BOp:$b_result), $attr), ...>; ``` The above will bind `$b_result` to the matched `BOp`'s result. (There are more details regarding multi-result ops, which is covered [later](#supporting-multi-result-ops).) ### Result pattern The result pattern is for generating a DAG of operations. Arguments in the `dag` object are intended to **reference** values captured in the source pattern and potentially **apply transformations**. #### Referencing bound symbols For example, ```tablegen def COp : Op<"c_op"> { let arguments = (ins AnyType:$c_input, AnyAttr:$c_attr ); let results = (outs AnyType:$c_output ); } def : Pat<(AOp $input, $attr), (COp $input, $attr)>; ``` In the above, `AOp`'s only operand and attribute are bound to `$input` and `$attr`, respectively. We then reference them in the result pattern for generating the `COp` by passing them in as arguments to `COp`'s `build()` method. We can also reference symbols bound to matched op's results: ```tablegen def : Pat<(AOp (BOp:$b_result) $attr), (COp $b_result $attr)>; ``` In the above, we are using `BOp`'s result for building `COp`. #### Building operations Given that `COp` was specified with table-driven op definition, there will be several `build()` methods generated for it. One of them has aggregated parameters for result types, operands, and attributes in the signature: `void COp::build(..., ArrayRef resultTypes, Array operands, ArrayRef attr)`. The pattern in the above calls this `build()` method for constructing the `COp`. In general, arguments in the result pattern will be passed directly to the `build()` method to leverage the auto-generated `build()` method, list them in the pattern by following the exact same order as the ODS `arguments` definition. Otherwise, a custom `build()` method that matches the argument list is required. Right now all ODS-generated `build()` methods require specifying the result type(s), unless the op has known traits like `SameOperandsAndResultType` that we can use to auto-generate a `build()` method with result type deduction. When generating an op to replace the result of the matched root op, we can use the matched root op's result type when calling the ODS-generated builder. Otherwise (e.g., generating an [auxiliary op](#supporting-auxiliary-ops) or generating an op with a nested result pattern), DRR will not be able to deduce the result type(s). The pattern author will need to define a custom builder that has result type deduction ability via `OpBuilder` in ODS. For example, in the following pattern ```tablegen def : Pat<(AOp $input, $attr), (COp (AOp $input, $attr) $attr)>; ``` `AOp` is generated via a nested result pattern; DRR won't be able to deduce the result type for it. A custom builder for `AOp` should be defined and it should deduce the result type by itself. The builder should have the separate parameter for each operand and attribute and deduce the result type internally by itself. For example, for the above `AOp`, a possible builder is: ```c++ void AOp::build(OpBuilder &builder, OperationState &state, Value input, Attribute attr) { state.addOperands({input}); state.addAttribute("a_attr", attr); Type type = ...; // Deduce result type here state.addTypes({type}); } ``` Failing to define such a builder will result in an error at C++ compilation time saying the call to `AOp::build()` cannot be resolved because of the number of parameters mismatch. #### Generating DAG of operations `dag` objects can be nested to generate a DAG of operations: ```tablegen def : Pat<(AOp $input, $attr), (COp (BOp), $attr)>; ``` In the above, we generate a `BOp`, and then use its result to generate the `COp` to replace the matched `AOp`. #### Binding op results In the result pattern, we can bind to the result(s) of a newly built op by attaching symbols to the op. (But we **cannot** bind to op arguments given that they are referencing previously bound symbols.) This is useful for reusing newly created results where suitable. For example, ```tablegen def DOp : Op<"d_op"> { let arguments = (ins AnyType:$d_input1, AnyType:$d_input2, ); let results = (outs AnyType:$d_output ); } def : Pat<(AOp $input, $ignored_attr), (DOp (BOp:$b_result) $b_result)>; ``` In this pattern, an `AOp` is matched and replaced with a `DOp` whose two operands are from the result of a single `BOp`. This is only possible by binding the result of the `BOp` to a name and reuse it for the second operand of the `DOp` #### `NativeCodeCall`: transforming the generated op Sometimes the captured arguments are not exactly what we want so they cannot be directly fed in as arguments to build the new op. For such cases, we can apply transformations on the arguments by calling into C++ helper functions. This is achieved by `NativeCodeCall`. For example, if we want to capture some op's attributes and group them as an array attribute to construct a new op: ```tablegen def TwoAttrOp : Op<"two_attr_op"> { let arguments = (ins AnyAttr:$op_attr1, AnyAttr:$op_attr2 ); let results = (outs AnyType:$op_output ); } def OneAttrOp : Op<"one_attr_op"> { let arguments = (ins ArrayAttr:$op_attr ); let results = (outs AnyType:$op_output ); } ``` We can write a C++ helper function: ```c++ ArrayAttr createArrayAttr(Builder &builder, Attribute a, Attribute b) { return builder.getArrayAttr({a, b}); } ``` And then write the pattern as: ```tablegen def createArrayAttr : NativeCodeCall<"createArrayAttr($_builder, $0, $1)">; def : Pat<(TwoAttrOp $attr1, $attr2), (OneAttrOp (createArrayAttr $attr1, $attr2))>; ``` And make sure the generated C++ code from the above pattern has access to the definition of the C++ helper function. In the above example, we are using a string to specialize the `NativeCodeCall` template. The string can be an arbitrary C++ expression that evaluates into some C++ object expected at the `NativeCodeCall` site (here it would be expecting an array attribute). Typically the string should be a function call. ##### `NativeCodeCall` placeholders In `NativeCodeCall`, we can use placeholders like `$_builder`, `$N` and `$N...`. The former is called *special placeholder*, while the latter is called *positional placeholder* and *positional range placeholder*. `NativeCodeCall` right now only supports three special placeholders: `$_builder`, `$_loc`, and `$_self`: * `$_builder` will be replaced by the current `mlir::PatternRewriter`. * `$_loc` will be replaced by the fused location or custom location (as determined by location directive). * `$_self` will be replaced by the defining operation in a source pattern. We have seen how `$_builder` can be used in the above; it allows us to pass a `mlir::Builder` (`mlir::PatternRewriter` is a subclass of `mlir::OpBuilder`, which is a subclass of `mlir::Builder`) to the C++ helper function to use the handy methods on `mlir::Builder`. Here's an example how we should use `$_self` in source pattern, ```tablegen def : Pat<(OneAttrOp (NativeCodeCall<"Foo($_self, &$0)"> I32Attr:$val)), (TwoAttrOp $val, $val)>; ``` In the above, `$_self` is substituted by the defining operation of the first operand of OneAttrOp. Note that we don't support binding name to `NativeCodeCall` in the source pattern. To carry some return values from a helper function, put the names (constraint is optional) in the parameter list and they will be bound to the variables with corresponding type. Then these names must be either passed by reference or pointer to the variable used as argument so that the matched value can be returned. In the same example, `$val` will be bound to a variable with `Attribute` type (as `I32Attr`) and the type of the second argument in `Foo()` could be `Attribute&` or `Attribute*`. Names with attribute constraints will be captured as `Attribute`s while everything else will be treated as `Value`s. Positional placeholders will be substituted by the `dag` object parameters at the `NativeCodeCall` use site. For example, if we define `SomeCall : NativeCodeCall<"someFn($1, $2, $0)">` and use it like `(SomeCall $in0, $in1, $in2)`, then this will be translated into C++ call `someFn($in1, $in2, $in0)`. Positional range placeholders will be substituted by multiple `dag` object parameters at the `NativeCodeCall` use site. For example, if we define `SomeCall : NativeCodeCall<"someFn($1...)">` and use it like `(SomeCall $in0, $in1, $in2)`, then this will be translated into C++ call `someFn($in1, $in2)`. ##### `NativeCodeCall` binding multi-results To bind multi-results and access the N-th result with `$__N`, specify the number of return values in the template. Note that only `Value` type is supported for multiple results binding. For example, ```tablegen def PackAttrs : NativeCodeCall<"packAttrs($0, $1)", 2>; def : Pattern<(TwoResultOp $attr1, $attr2), [(OneResultOp (PackAttr:$res__0, $attr1, $attr2)), (OneResultOp $res__1)]>; ``` Use `NativeCodeCallVoid` for cases with no return value. The correct number of returned value specified in NativeCodeCall is important. It will be used to verify the consistency of the number of return values. Additionally, `mlir-tblgen` will try to capture the return values of `NativeCodeCall` in the generated code so that it will trigger a later compilation error if a `NativeCodeCall` that doesn't return any result isn't labeled with 0 returns. ##### Customizing entire op building `NativeCodeCall` is not only limited to transforming arguments for building an op; it can be also used to specify how to build an op entirely. An example: If we have a C++ function for building an op: ```c++ Operation *createMyOp(OpBuilder builder, Value input, Attribute attr); ``` We can wrap it up and invoke it like: ```tablegen def createMyOp : NativeCodeCall<"createMyOp($_builder, $0, $1)">; def : Pat<(... $input, $attr), (createMyOp $input, $attr)>; ``` ### Supporting auxiliary ops A declarative rewrite rule supports multiple result patterns. One of the purposes is to allow generating *auxiliary ops*. Auxiliary ops are operations used for building the replacement ops; but they are not directly used for replacement themselves. For the case of uni-result ops, if there are multiple result patterns, only the value generated from the last result pattern will be used to replace the matched root op's result; all other result patterns will be considered as generating auxiliary ops. Normally we want to specify ops as nested `dag` objects if their def-use relationship can be expressed in the way that an op's result can feed as the argument to consuming op. But that is not always possible. For example, if we want to allocate memory and store some computation (in pseudocode): ```mlir %dst = arith.addi %lhs, %rhs ``` into ```mlir %shape = shape %lhs %mem = memref.alloc %shape %sum = arith.addi %lhs, %rhs memref.store %mem, %sum %dst = memref.load %mem ``` We cannot fit in with just one result pattern given `store` does not return a value. Instead we can use multiple result patterns: ```tablegen def : Pattern<(AddIOp $lhs, $rhs), [(StoreOp (AllocOp:$mem (ShapeOp $lhs)), (AddIOp $lhs, $rhs)), (LoadOp $mem)]; ``` In the above we use the first result pattern to generate the first four ops, and use the last pattern to generate the last op, which is used to replace the matched op. ### Supporting multi-result ops Multi-result ops bring extra complexity to declarative rewrite rules. We use TableGen `dag` objects to represent ops in patterns; there is no native way to indicate that an op generates multiple results. The approach adopted is based on **naming convention**: a `__N` suffix is added to a symbol to indicate the `N`-th result. #### `__N` suffix The `__N` suffix is specifying the `N`-th result as a whole (which can be [variadic](#supporting-variadic-ops)). For example, we can bind a symbol to some multi-result op and reference a specific result later: ```tablegen def ThreeResultOp : Op<"three_result_op"> { let arguments = (ins ...); let results = (outs AnyTensor:$output1, AnyTensor:$output2, AnyTensor:$output3 ); } def : Pattern<(ThreeResultOp:$results ...), [(... $results__0), ..., (... $results__2), ...]>; ``` In the above pattern we bind `$results` to all the results generated by `ThreeResultOp` and references its `$output1` and `$output3` later in the result patterns. We can also bind a symbol and reference one of its specific result at the same time, which is typically useful when generating multi-result ops: ```tablegen // TwoResultOp has similar definition as ThreeResultOp, but only has two // results. def : Pattern<(TwoResultOp ...), [(ThreeResultOp:$results__2, ...), (replaceWithValue $results__0)]>; ``` In the above, we created a `ThreeResultOp` and bind `results` to its results, and uses its last result (`$output3`) and first result (`$output1`) to replace the `TwoResultOp`'s two results, respectively. #### Replacing multi-result ops The above example also shows how to replace a matched multi-result op. To replace an `N`-result op, the result patterns must generate at least `N` declared values (see [Declared vs. actual value](#declared-vs-actual-value) for definition). If there are more than `N` declared values generated, only the last `N` declared values will be used to replace the matched op. Note that because of the existence of multi-result op, one result pattern **may** generate multiple declared values. So it means we do not necessarily need `N` result patterns to replace an `N`-result op. For example, to replace an op with three results, you can have ```tablegen // ThreeResultOp/TwoResultOp/OneResultOp generates three/two/one result(s), // respectively. // Replace each result with a result generated from an individual op. def : Pattern<(ThreeResultOp ...), [(OneResultOp ...), (OneResultOp ...), (OneResultOp ...)]>; // Replace the first two results with two results generated from the same op. def : Pattern<(ThreeResultOp ...), [(TwoResultOp ...), (OneResultOp ...)]>; // Replace all three results with three results generated from the same op. def : Pat<(ThreeResultOp ...), (ThreeResultOp ...)>; def : Pattern<(ThreeResultOp ...), [(AuxiliaryOp ...), (ThreeResultOp ...)]>; ``` But using a single op to serve as both auxiliary op and replacement op is forbidden, i.e., the following is not allowed because that the first `TwoResultOp` generates two results but only the second result is used for replacing the matched op's result: ```tablegen def : Pattern<(ThreeResultOp ...), [(TwoResultOp ...), (TwoResultOp ...)]>; ``` ### Supporting variadic ops #### Declared vs. actual value Before going into details on variadic op support, we need to define a few terms regarding an op's values. * *Value*: either an operand or a result * *Declared operand/result/value*: an operand/result/value statically declared in ODS of the op * *Actual operand/result/value*: an operand/result/value of an op instance at runtime The above terms are needed because ops can have multiple results, and some of the results can also be variadic. For example, ```tablegen def MultiVariadicOp : Op<"multi_variadic_op"> { let arguments = (ins AnyTensor:$input1, Variadic:$input2, AnyTensor:$input3 ); let results = (outs AnyTensor:$output1, Variadic:$output2, AnyTensor:$output3 ); } ``` We say the above op has 3 declared operands and 3 declared results. But at runtime, an instance can have 3 values corresponding to `$input2` and 2 values correspond to `$output2`; we say it has 5 actual operands and 4 actual results. A variadic operand/result is a considered as a declared value that can correspond to multiple actual values. [TODO] ### Supplying additional constraints Constraints can be placed on op arguments when matching. But sometimes we need to also place constraints on the matched op's results or sometimes need to limit the matching with some constraints that cover both the arguments and the results. The third parameter to `Pattern` (and `Pat`) is for this purpose. For example, we can write ```tablegen def HasNoUseOf: Constraint, "has no use">; def HasSameElementType : Constraint< CPred<"$0.cast().getElementType() == " "$1.cast().getElementType()">, "has same element type">; def : Pattern<(TwoResultOp:$results $input), [(...), (...)], [(F32Tensor:$results__0), (HasNoUseOf:$results__1), (HasSameElementShape $results__0, $input)]>; ``` You can * Use normal `TypeConstraint`s on previous bound symbols (the first result of `TwoResultOp` must be a float tensor); * Define new `Constraint` for previous bound symbols (the second result of `TwoResultOp` must has no use); * Apply constraints on multiple bound symbols (`$input` and `TwoResultOp`'s first result must have the same element type). ### Adjusting benefits The benefit of a `Pattern` is an integer value indicating the benefit of matching the pattern. It determines the priorities of patterns inside the pattern rewrite driver. A pattern with a higher benefit is applied before one with a lower benefit. In DRR, a rule is set to have a benefit of the number of ops in the source pattern. This is based on the heuristics and assumptions that: * Larger matches are more beneficial than smaller ones. * If a smaller one is applied first the larger one may not apply anymore. The fourth parameter to `Pattern` (and `Pat`) allows to manually tweak a pattern's benefit. Just supply `(addBenefit N)` to add `N` to the benefit value. ## Rewrite directives ### `location` By default the C++ pattern expanded from a DRR pattern uses the fused location of all source ops as the location for all generated ops. This is not always the best location mapping relationship. For such cases, DRR provides the `location` directive to provide finer control. `location` is of the following syntax: ```tablegen (location $symbol0, $symbol1, ...) ``` where all `$symbol` should be bound previously in the pattern and one optional string may be specified as an attribute. The following locations are created: * If only 1 symbol is specified then that symbol's location is used, * If multiple are specified then a fused location is created; * If no symbol is specified then string must be specified and a NamedLoc is created instead; `location` must be used as a trailing argument to an op creation. For example, ```tablegen def : Pat<(LocSrc1Op:$src1 (LocSrc2Op:$src2 ...), (LocDst1Op (LocDst2Op ..., (location $src2)), (location "outer"))>; ``` In the above pattern, the generated `LocDst2Op` will use the matched location of `LocSrc2Op` while the root `LocDst1Op` node will used the named location `outer`. ### `replaceWithValue` The `replaceWithValue` directive is used to eliminate a matched op by replacing all of its uses with a captured value. It is of the following syntax: ```tablegen (replaceWithValue $symbol) ``` where `$symbol` should be a symbol bound previously in the pattern. For example, ```tablegen def : Pat<(Foo $input), (replaceWithValue $input)>; ``` The above pattern removes the `Foo` and replaces all uses of `Foo` with `$input`. ### `returnType` The `returnType` directive allows patterns to directly specify return types for replacement ops that lack return type inference with op traits or user-defined builders with return type deduction. The `returnType` directive must be used as a trailing argument to a node describing a replacement op. The directive comes in three forms: * `(returnType $value)`: copy the type of the operand or result bound to `value`. * `(returnType "$_builder.getI32Type()")`: a string literal embedding C++. The embedded snippet is expected to return a `Type` or a `TypeRange`. * `(returnType (NativeCodeCall<"myFunc($0)"> $value))`: a DAG node with a native code call that can be passed any bound variables arguments. Specify multiple return types with a mix of any of the above. Example: ```tablegen def : Pat<(SourceOp $arg0, $arg1), (OpA $arg0, (TwoResultOp:$res__1 $arg1, (returnType $arg1, "$_builder.getI64Type()")))>; ``` Explicitly-specified return types will take precedence over return types inferred from op traits or user-defined builders. The return types of values replacing root op results cannot be overridden. ### `either` The `either` directive is used to specify the operands may be matched in either order. ```tablegen def : Pat<(TwoArgOp (either $firstArg, (AnOp $secondArg))), (...)>; ``` The above pattern will accept either `"test.TwoArgOp"(%I32Arg, %AnOpArg)` and `"test.TwoArgOp"(%AnOpArg, %I32Arg)`. Only operand is supported with `either` and note that an operation with `Commutative` trait doesn't imply that it'll have the same behavior than `either` while pattern matching. ## Debugging Tips ### Run `mlir-tblgen` to see the generated content TableGen syntax sometimes can be obscure; reading the generated content can be a very helpful way to understand and debug issues. To build `mlir-tblgen`, run `cmake --build . --target mlir-tblgen` in your build directory and find the `mlir-tblgen` binary in the `bin/` subdirectory. All the supported generators can be found via `mlir-tblgen --help`. To see the generated code, invoke `mlir-tblgen` with a specific generator by providing include paths via `-I`. For example, ```sh # To see all the C++ pattern rewrite classes mlir-tblgen --gen-rewriters -I /path/to/mlir/include /path/to/input/td/file ``` ### Compilation error: no matching member function for call to 'build' This is because DRR is failing to call a `build()` method with result type deduction ability. See [building operations](#building-operations) for more details. [TableGen]: https://llvm.org/docs/TableGen/index.html [OpBase]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/IR/OpBase.td