forked from OSchip/llvm-project
820 lines
29 KiB
Markdown
820 lines
29 KiB
Markdown
# 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](OpDefinitions.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](OpDefinitions.md) doc.
|
|
|
|
## 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<dag> resultPatterns,
|
|
list<dag> 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<dag> additionalConstraints = [],
|
|
dag benefitsAdded = (addBenefit 0)> :
|
|
Pattern<sourcePattern, [resultPattern], additionalConstraints, benefitAdded>;
|
|
```
|
|
|
|
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<Type> resultTypes, Array<Value> operands,
|
|
ArrayRef<NamedAttribute> 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 `$<name>__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<AnyTensor>:$input2,
|
|
AnyTensor:$input3
|
|
);
|
|
|
|
let results = (outs
|
|
AnyTensor:$output1,
|
|
Variadic<AnyTensor>:$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<CPred<"$_self.use_empty()">, "has no use">;
|
|
|
|
def HasSameElementType : Constraint<
|
|
CPred<"$0.cast<ShapedType>().getElementType() == "
|
|
"$1.cast<ShapedType>().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
|