1 # Operation Canonicalization
3 Canonicalization is an important part of compiler IR design: it makes it easier
4 to implement reliable compiler transformations and to reason about what is
5 better or worse in the code, and it forces interesting discussions about the
6 goals of a particular level of IR. Dan Gohman wrote
7 [an article](https://sunfishcode.github.io/blog/2018/10/22/Canonicalization.html)
8 exploring these issues; it is worth reading if you're not familiar with these
11 Most compilers have canonicalization passes, and sometimes they have many
12 different ones (e.g. instcombine, dag combine, etc in LLVM). Because MLIR is a
13 multi-level IR, we can provide a single canonicalization infrastructure and
14 reuse it across many different IRs that it represents. This document describes
15 the general approach, global canonicalizations performed, and provides sections
16 to capture IR-specific rules for reference.
22 MLIR has a single canonicalization pass, which iteratively applies the
23 canonicalization patterns of all loaded dialects in a greedy way.
24 Canonicalization is best-effort and not guaranteed to bring the entire IR in a
25 canonical form. It applies patterns until either fixpoint is reached or the
26 maximum number of iterations/rewrites (as specified via pass options) is
27 exhausted. This is for efficiency reasons and to ensure that faulty patterns
28 cannot cause infinite looping.
30 Canonicalization patterns are registered with the operations themselves, which
31 allows each dialect to define its own set of operations and canonicalizations
34 Some important things to think about w.r.t. canonicalization patterns:
36 * Pass pipelines should not rely on the canonicalizer pass for correctness.
37 They should work correctly with all instances of the canonicalization pass
40 * Repeated applications of patterns should converge. Unstable or cyclic
41 rewrites are considered a bug: they can make the canonicalizer pass less
42 predictable and less effective (i.e., some patterns may not be applied) and
43 prevent it from converging.
45 * It is generally better to canonicalize towards operations that have fewer
46 uses of a value when the operands are duplicated, because some patterns only
47 match when a value has a single user. For example, it is generally good to
48 canonicalize "x + x" into "x * 2", because this reduces the number of uses
51 * It is always good to eliminate operations entirely when possible, e.g. by
52 folding known identities (like "x + 0 = x").
54 ## Globally Applied Rules
56 These transformations are applied to all levels of IR:
58 * Elimination of operations that have no side effects and have no uses.
60 * Constant folding - e.g. "(addi 1, 2)" to "3". Constant folding hooks are
61 specified by operations.
63 * Move constant operands to commutative operators to the right side - e.g.
64 "(addi 4, x)" to "(addi x, 4)".
66 * `constant-like` operations are uniqued and hoisted into the entry block of
67 the first parent barrier region. This is a region that is either isolated
68 from above, e.g. the entry block of a function, or one marked as a barrier
69 via the `shouldMaterializeInto` method on the `DialectFoldInterface`.
71 ## Defining Canonicalizations
73 Two mechanisms are available with which to define canonicalizations;
74 general `RewritePattern`s and the `fold` method.
76 ### Canonicalizing with `RewritePattern`s
78 This mechanism allows for providing canonicalizations as a set of
79 `RewritePattern`s, either imperatively defined in C++ or declaratively as
80 [Declarative Rewrite Rules](DeclarativeRewrites.md). The pattern rewrite
81 infrastructure allows for expressing many different types of canonicalizations.
82 These transformations may be as simple as replacing a multiplication with a
83 shift, or even replacing a conditional branch with an unconditional one.
85 In [ODS](DefiningDialects/Operations.md), an operation can set the `hasCanonicalizer` bit or
86 the `hasCanonicalizeMethod` bit to generate a declaration for the
87 `getCanonicalizationPatterns` method:
91 // I want to define a fully general set of patterns for this op.
92 let hasCanonicalizer = 1;
96 // A single "matchAndRewrite" style RewritePattern implemented as a method
97 // is good enough for me.
98 let hasCanonicalizeMethod = 1;
102 Canonicalization patterns can then be provided in the source file:
105 void MyOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
106 MLIRContext *context) {
107 patterns.add<...>(...);
110 LogicalResult OtherOp::canonicalize(OtherOp op, PatternRewriter &rewriter) {
111 // patterns and rewrites go here.
116 See the [quickstart guide](Tutorials/QuickstartRewrites.md) for information on
117 defining operation rewrites.
119 ### Canonicalizing with the `fold` method
121 The `fold` mechanism is an intentionally limited, but powerful mechanism that
122 allows for applying canonicalizations in many places throughout the compiler.
123 For example, outside of the canonicalizer pass, `fold` is used within the
124 [dialect conversion infrastructure](DialectConversion.md) as a legalization
125 mechanism, and can be invoked directly anywhere with an `OpBuilder` via
126 `OpBuilder::createOrFold`.
128 `fold` has the restriction that no new operations may be created, and only the
129 root operation may be replaced (but not erased). It allows for updating an
130 operation in-place, or returning a set of pre-existing values (or attributes) to
131 replace the operation with. This ensures that the `fold` method is a truly
132 "local" transformation, and can be invoked without the need for a pattern
135 In [ODS](DefiningDialects/Operations.md), an operation can set the `hasFolder` bit to generate
136 a declaration for the `fold` method. This method takes on a different form,
137 depending on the structure of the operation.
145 If the operation has a single result the following will be generated:
148 /// Implementations of this hook can only perform the following changes to the
151 /// 1. They can leave the operation alone and without changing the IR, and
153 /// 2. They can mutate the operation in place, without changing anything else
154 /// in the IR. In this case, return the operation itself.
155 /// 3. They can return an existing value or attribute that can be used instead
156 /// of the operation. The caller will remove the operation and use that
159 OpFoldResult MyOp::fold(FoldAdaptor adaptor) {
164 Otherwise, the following is generated:
167 /// Implementations of this hook can only perform the following changes to the
170 /// 1. They can leave the operation alone and without changing the IR, and
172 /// 2. They can mutate the operation in place, without changing anything else
173 /// in the IR. In this case, return success.
174 /// 3. They can return a list of existing values or attribute that can be used
175 /// instead of the operation. In this case, fill in the results list and
176 /// return success. The results list must correspond 1-1 with the results of
177 /// the operation, partial folding is not supported. The caller will remove
178 /// the operation and use those results instead.
180 /// Note that this mechanism cannot be used to remove 0-result operations.
181 LogicalResult MyOp::fold(FoldAdaptor adaptor,
182 SmallVectorImpl<OpFoldResult> &results) {
187 In the above, for each method a `FoldAdaptor` is provided with getters for
188 each of the operands, returning the corresponding constant attribute. These
189 operands are those that implement the `ConstantLike` trait. If any of the
190 operands are non-constant, a null `Attribute` value is provided instead. For
191 example, if MyOp provides three operands [`a`, `b`, `c`], but only `b` is
192 constant then `adaptor` will return Attribute() for `getA()` and `getC()`,
193 and b-value for `getB()`.
195 Also above, is the use of `OpFoldResult`. This class represents the possible
196 result of folding an operation result: either an SSA `Value`, or an
197 `Attribute`(for a constant result). If an SSA `Value` is provided, it *must*
198 correspond to an existing value. The `fold` methods are not permitted to
199 generate new `Value`s. There are no specific restrictions on the form of the
200 `Attribute` value returned, but it is important to ensure that the `Attribute`
201 representation of a specific `Type` is consistent.
203 When the `fold` hook on an operation is not successful, the dialect can
204 provide a fallback by implementing the `DialectFoldInterface` and overriding
207 #### Generating Constants from Attributes
209 When a `fold` method returns an `Attribute` as the result, it signifies that
210 this result is "constant". The `Attribute` is the constant representation of the
211 value. Users of the `fold` method, such as the canonicalizer pass, will take
212 these `Attribute`s and materialize constant operations in the IR to represent
213 them. To enable this materialization, the dialect of the operation must
214 implement the `materializeConstant` hook. This hook takes in an `Attribute`
215 value, generally returned by `fold`, and produces a "constant-like" operation
216 that materializes that value.
218 In [ODS](DefiningDialects/_index.md), a dialect can set the `hasConstantMaterializer` bit
219 to generate a declaration for the `materializeConstant` method.
222 def MyDialect : ... {
223 let hasConstantMaterializer = 1;
227 Constants can then be materialized in the source file:
230 /// Hook to materialize a single constant operation from a given attribute value
231 /// with the desired resultant type. This method should use the provided builder
232 /// to create the operation without changing the insertion position. The
233 /// generated operation is expected to be constant-like. On success, this hook
234 /// should return the value generated to represent the constant value.
235 /// Otherwise, it should return nullptr on failure.
236 Operation *MyDialect::materializeConstant(OpBuilder &builder, Attribute value,
237 Type type, Location loc) {