forked from OSchip/llvm-project
Add doc for declarative rewrite rules
This doc serves as a manual for table-driven declarative rewrite rules. It lists all the details regarding supported mechanisms. PiperOrigin-RevId: 267761702
This commit is contained in:
parent
06398f32f6
commit
6e5d1b9d62
|
@ -0,0 +1,610 @@
|
|||
# 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](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 essense the 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 essense 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 also does not have good support for regions
|
||||
in general.
|
||||
|
||||
## Rule Definition
|
||||
|
||||
The core construct for defining a rewrite rule is defined in
|
||||
[`OpBase.td`][OpBase] as
|
||||
|
||||
```tblgen
|
||||
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:
|
||||
|
||||
```tblgen
|
||||
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](#special-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,
|
||||
|
||||
```tblgen
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
#### Matching DAG of operations
|
||||
|
||||
To match an DAG of ops, use nested `dag` objects:
|
||||
|
||||
```tblgen
|
||||
|
||||
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:
|
||||
|
||||
```tblgen
|
||||
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,
|
||||
|
||||
```tblgen
|
||||
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:
|
||||
|
||||
```tblgen
|
||||
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 a separate argument
|
||||
in the signature for each argument appearing in the op's `arguments` list:
|
||||
`void COp::build(..., Value *input, Attribute attr)`. The pattern in the above
|
||||
calls this `build()` method for constructing the `COp`.
|
||||
|
||||
In general, arguments in the 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.
|
||||
|
||||
#### Generating DAG of operations
|
||||
|
||||
`dag` objects can be nested to generate a DAG of operations:
|
||||
|
||||
```tblgen
|
||||
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 an 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,
|
||||
|
||||
```tblgen
|
||||
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, a `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 catpure some op's attributes and group them as an
|
||||
array attribute to construct a new op:
|
||||
|
||||
```tblgen
|
||||
|
||||
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++
|
||||
Attribute createArrayAttr(Builder &builder, Attribute a, Attribute b) {
|
||||
return builder.getArrayAttr({a, b});
|
||||
}
|
||||
```
|
||||
|
||||
And then write the pattern as:
|
||||
|
||||
```tblgen
|
||||
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`. The former
|
||||
is called _special placeholder_, while the latter is called _positional
|
||||
placeholder_.
|
||||
|
||||
`NativeCodeCall` right now only supports two special placeholders: `$_builder`
|
||||
and `$_self`:
|
||||
|
||||
* `$_builder` will be replaced by the current `mlir::PatternRewriter`.
|
||||
* `$_self` will be replaced with the entity `NativeCodeCall` is attached to.
|
||||
|
||||
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`.
|
||||
|
||||
`$_self` is useful when we want to write something in the form of
|
||||
`NativeCodeCall<"...">:$symbol`. For example, if we want to reverse the previous
|
||||
example and decompose the array attribute into two attributes:
|
||||
|
||||
```tblgen
|
||||
class getNthAttr<int n> : NativeCodeCall<"$_self.getValue()[" # n # "]">;
|
||||
|
||||
def : Pat<(OneAttrOp $attr),
|
||||
(TwoAttrOp (getNthAttr<0>:$attr), (getNthAttr<1>:$attr)>;
|
||||
```
|
||||
|
||||
In the above, `$_self` is substitutated by the attribute bound by `$attr`, which
|
||||
is `OnAttrOp`'s array attribute.
|
||||
|
||||
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)`.
|
||||
|
||||
##### Customizing entire op building
|
||||
|
||||
`NativeCodeCall` is not only limited to transforming arguments for building an
|
||||
op; it can 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:
|
||||
|
||||
```tblgen
|
||||
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 purpose
|
||||
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 = addi %lhs, %rhs
|
||||
```
|
||||
|
||||
into
|
||||
|
||||
```mlir
|
||||
%shape = shape %lhs
|
||||
%mem = alloc %shape
|
||||
%sum = addi %lhs, %rhs
|
||||
store %mem, %sum
|
||||
%dst = 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:
|
||||
|
||||
```tblgen
|
||||
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` sufix 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:
|
||||
|
||||
```tblgen
|
||||
def ThreeResultOp : Op<"three_result_op"> {
|
||||
let arguments = (ins ...);
|
||||
|
||||
let results = (outs
|
||||
AnyTensor:$op_output1,
|
||||
AnyTensor:$op_output2,
|
||||
AnyTensor:$op_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 `$input1` and `$input3` 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:
|
||||
|
||||
```tblgen
|
||||
// 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 a `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
|
||||
|
||||
```tblgen
|
||||
// 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:
|
||||
|
||||
```tblgen
|
||||
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,
|
||||
|
||||
```tblgen
|
||||
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 arugments and the
|
||||
results. The third parameter to `Pattern` (and `Pat`) is for this purpose.
|
||||
|
||||
For example, we can write
|
||||
|
||||
```tblgen
|
||||
def HasNoUseOf: Constraint<
|
||||
CPred<"$_self->use_begin() == $_self->use_end()">, "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 benfit 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 forth parameter to `Pattern` (and `Pat`) allows to manually tweak a
|
||||
pattern's benefit. Just supply `(addBenefit N)` to add `N` to the benefit value.
|
||||
|
||||
## Special directives
|
||||
|
||||
[TODO]
|
||||
|
||||
[TableGen]: https://llvm.org/docs/TableGen/index.html
|
||||
[OpBase]: https://github.com/tensorflow/mlir/blob/master/include/mlir/IR/OpBase.td
|
|
@ -930,57 +930,6 @@ the shape function. The reference implementation is general and can support the
|
|||
arbitrary computations needed to specify output shapes.
|
||||
|
||||
|
||||
|
||||
# Rewrite pattern description
|
||||
|
||||
TODO: Move this section to a dedicated doc for graph rewrites
|
||||
|
||||
MLIR aims to support many graph transformations across multiple levels of
|
||||
representation using declarative patterns. These patterns can be expressed using
|
||||
TableGen as well as dynamically (TBD).
|
||||
|
||||
## Op DAG pattern rewrites
|
||||
|
||||
The most direct pattern supported in MLIR is rewrites of the form `(dag of
|
||||
operations) -> (dag of operations)` along with constraints (on operands and
|
||||
operations). The matchers require both dialects being matched between to be
|
||||
included in the same TableGen file. Hence pattern matching is normally defined
|
||||
in either a separate file that imports both. Matchers are defined in terms of
|
||||
the TableGen instances rather than mnemonics to allow for better error checking
|
||||
and verification generation.
|
||||
|
||||
```tablegen
|
||||
def : Pat<(TF_LeakyReluOp $arg, F32Attr:$a), (TFL_LeakyReluOp $arg, $a)>;
|
||||
def : Pat<(TF_ReluOp (TF_AddOp $lhs, $rhs)), (TFL_AddOp $lhs, $rhs, TFL_AF_Relu)>;
|
||||
def : Pat<(TF_BiasAddOp F32Tensor:$l, F32Tensor:$r),
|
||||
(TFL_AddOp $l, $r, TFL_AF_None)>;
|
||||
```
|
||||
|
||||
In the above examples it was shown how to construct matching rules between two
|
||||
dialects (TensorFlow and TensorFlowLite), showing matching arguments (attributes
|
||||
and operands) as well as matching a DAG pattern of multiple input operations to
|
||||
single output.
|
||||
|
||||
1. Matchers can be partially specified on the input (e.g., not all arguments
|
||||
constrained) and so multiple matchers can match the same set of nodes. The
|
||||
most discriminative matcher (as determined by the number of
|
||||
constrained/matching terms) will be selected, if two patterns are equally
|
||||
discriminative then an error will be reported.
|
||||
|
||||
1. Matchers between dialects have to be completely specified on the output
|
||||
(i.e., there can be no unspecified attributes of the op generated).
|
||||
|
||||
1. Operands and attributes can be further constrained from the op definition
|
||||
(e.g., bias add rule only matches the case where both Tensors have F32
|
||||
elements).
|
||||
|
||||
1. Attributes can be transformed by transform rules to produce an attribute
|
||||
of a type different than the type matched.
|
||||
|
||||
TODO: Add constraints on the matching rules.
|
||||
|
||||
TODO: Describe the generation of benefit metric given pattern.
|
||||
|
||||
[TableGen]: https://llvm.org/docs/TableGen/index.html
|
||||
[TableGenIntro]: https://llvm.org/docs/TableGen/LangIntro.html
|
||||
[TableGenRef]: https://llvm.org/docs/TableGen/LangRef.html
|
||||
|
|
Loading…
Reference in New Issue