150
|
1 # Dialect Conversion
|
|
2
|
|
3 This document describes a framework in MLIR in which to perform operation
|
|
4 conversions between, and within dialects. This framework allows for transforming
|
|
5 illegal operations to those supported by a provided conversion target, via a set
|
|
6 of pattern-based operation rewriting patterns.
|
|
7
|
|
8 [TOC]
|
|
9
|
|
10 To utilize the framework, a few things must be provided:
|
|
11
|
|
12 * A [Conversion Target](#conversion-target)
|
|
13 * A set of [Rewrite Patterns](#rewrite-pattern-specification)
|
|
14 * A [Type Converter](#type-conversion) (Optional)
|
|
15
|
|
16 ## Modes of Conversion
|
|
17
|
|
18 When applying a conversion to a set of operations, there are several conversion
|
|
19 modes that can be selected from:
|
|
20
|
|
21 * Partial Conversion
|
|
22
|
|
23 - A partial conversion will legalize as many operations to the target as
|
|
24 possible, but will allow pre-existing operations that were not
|
|
25 explicitly marked as `illegal` to remain unconverted. This allows for
|
|
26 partially lowering parts of the module in the presence of unknown
|
|
27 operations.
|
|
28 - A partial conversion can be applied via `applyPartialConversion`.
|
|
29
|
|
30 * Full Conversion
|
|
31
|
|
32 - A full conversion is only successful if all operations are properly
|
|
33 legalized to the given conversion target. This ensures that only known
|
|
34 operations will exist after the conversion process.
|
|
35 - A full conversion can be applied via `applyFullConversion`.
|
|
36
|
|
37 * Analysis Conversion
|
|
38
|
|
39 - An analysis conversion will analyze which operations are legalizable to
|
|
40 the given conversion target if a conversion were to be applied. Note
|
|
41 that no rewrites, or transformations, are actually applied to the input
|
|
42 operations.
|
|
43 - An analysis conversion can be applied via `applyAnalysisConversion`.
|
|
44
|
|
45 ## Conversion Target
|
|
46
|
|
47 The conversion target is the formal definition of what is considered to be legal
|
|
48 during the conversion process. The final operations generated by the conversion
|
|
49 framework must be marked as legal on the `ConversionTarget` for the rewrite to
|
|
50 be a success. Existing operations need not always be legal, though; see the
|
|
51 different conversion modes for why. Operations and dialects may be marked with
|
|
52 any of the provided legality actions below:
|
|
53
|
|
54 * Legal
|
|
55
|
|
56 - This action signals that every instance of a given operation is legal,
|
|
57 i.e. any combination of attributes, operands, types, etc. are valid.
|
|
58
|
|
59 * Dynamic
|
|
60
|
|
61 - This action signals that only some instances of a given operation are
|
|
62 legal. This allows for defining fine-tune constraints, e.g. saying that
|
|
63 `addi` is only legal when operating on 32-bit integers.
|
|
64 - If a specific handler is not provided when setting the action, the
|
|
65 target must override the `isDynamicallyLegal` hook provided by
|
|
66 `ConversionTarget`.
|
|
67
|
|
68 * Illegal
|
|
69
|
|
70 - This action signals that no instance of a given operation is legal.
|
|
71 Operations marked as `illegal` must always be converted for the
|
|
72 conversion to be successful. This action also allows for selectively
|
|
73 marking specific operations as illegal in an otherwise legal dialect.
|
|
74
|
|
75 An example conversion target is shown below:
|
|
76
|
|
77 ```c++
|
|
78 struct MyTarget : public ConversionTarget {
|
|
79 MyTarget(MLIRContext &ctx) : ConversionTarget(ctx) {
|
|
80 //--------------------------------------------------------------------------
|
|
81 // Marking an operation as Legal:
|
|
82
|
|
83 /// Mark all operations within the LLVM dialect are legal.
|
|
84 addLegalDialects<LLVMDialect>();
|
|
85
|
|
86 /// Mark `std.constant` op is always legal on this target.
|
|
87 addLegalOps<ConstantOp>();
|
|
88
|
|
89 //--------------------------------------------------------------------------
|
|
90 // Marking an operation as dynamically legal.
|
|
91
|
|
92 /// Mark all operations within Affine dialect have dynamic legality
|
|
93 /// constraints.
|
|
94 addDynamicallyLegalDialects<AffineDialect>();
|
|
95
|
|
96 /// Mark `std.return` as dynamically legal.
|
|
97 addDynamicallyLegalOp<ReturnOp>();
|
|
98
|
|
99 /// Mark `std.return` as dynamically legal, but provide a specific legality
|
|
100 /// callback.
|
|
101 addDynamicallyLegalOp<ReturnOp>([](ReturnOp op) { ... });
|
|
102
|
|
103 /// Treat unknown operations, i.e. those without a legalization action
|
|
104 /// directly set, as dynamically legal.
|
|
105 markUnknownOpDynamicallyLegal();
|
|
106 markUnknownOpDynamicallyLegal([](Operation *op) { ... });
|
|
107
|
|
108 //--------------------------------------------------------------------------
|
|
109 // Marking an operation as illegal.
|
|
110
|
|
111 /// All operations within the GPU dialect are illegal.
|
|
112 addIllegalDialect<GPUDialect>();
|
|
113
|
|
114 /// Mark `std.br` and `std.cond_br` as illegal.
|
|
115 addIllegalOp<BranchOp, CondBranchOp>();
|
|
116 }
|
|
117
|
|
118 /// Implement the default legalization handler to handle operations marked as
|
|
119 /// dynamically legal that were not provided with an explicit handler.
|
|
120 bool isDynamicallyLegal(Operation *op) override { ... }
|
|
121 };
|
|
122 ```
|
|
123
|
|
124 ### Recursive Legality
|
|
125
|
|
126 In some cases, it may be desirable to mark entire regions of operations as
|
|
127 legal. This provides an additional granularity of context to the concept of
|
|
128 "legal". The `ConversionTarget` supports marking operations, that were
|
|
129 previously added as `Legal` or `Dynamic`, as `recursively` legal. Recursive
|
|
130 legality means that if an operation instance is legal, either statically or
|
|
131 dynamically, all of the operations nested within are also considered legal. An
|
|
132 operation can be marked via `markOpRecursivelyLegal<>`:
|
|
133
|
|
134 ```c++
|
|
135 ConversionTarget &target = ...;
|
|
136
|
|
137 /// The operation must first be marked as `Legal` or `Dynamic`.
|
|
138 target.addLegalOp<MyOp>(...);
|
|
139 target.addDynamicallyLegalOp<MySecondOp>(...);
|
|
140
|
|
141 /// Mark the operation as always recursively legal.
|
|
142 target.markOpRecursivelyLegal<MyOp>();
|
|
143 /// Mark optionally with a callback to allow selective marking.
|
|
144 target.markOpRecursivelyLegal<MyOp, MySecondOp>([](Operation *op) { ... });
|
|
145 /// Mark optionally with a callback to allow selective marking.
|
|
146 target.markOpRecursivelyLegal<MyOp>([](MyOp op) { ... });
|
|
147 ```
|
|
148
|
|
149 ## Rewrite Pattern Specification
|
|
150
|
|
151 After the conversion target has been defined, a set of legalization patterns
|
|
152 must be provided to transform illegal operations into legal ones. The patterns
|
|
153 supplied here, that do not [require type changes](#conversion-patterns), are the
|
|
154 same as those described in the
|
173
|
155 [quickstart rewrites guide](Tutorials/QuickstartRewrites.md#adding-patterns), but have a
|
150
|
156 few additional [restrictions](#restrictions). The patterns provided do not need
|
|
157 to generate operations that are directly legal on the target. The framework will
|
|
158 automatically build a graph of conversions to convert non-legal operations into
|
|
159 a set of legal ones.
|
|
160
|
|
161 As an example, say you define a target that supports one operation: `foo.add`.
|
|
162 When providing the following patterns: [`bar.add` -> `baz.add`, `baz.add` ->
|
|
163 `foo.add`], the framework will automatically detect that it can legalize
|
|
164 `baz.add` -> `foo.add` even though a direct conversion does not exist. This
|
|
165 means that you don’t have to define a direct legalization pattern for `bar.add`
|
|
166 -> `foo.add`.
|
|
167
|
|
168 ### Restrictions
|
|
169
|
|
170 The framework processes operations in topological order, trying to legalize them
|
|
171 individually. As such, patterns used in the conversion framework have a few
|
|
172 additional restrictions:
|
|
173
|
|
174 1. If a pattern matches, it must erase or replace the op it matched on.
|
|
175 Operations can *not* be updated in place.
|
|
176 2. Match criteria should not be based on the IR outside of the op itself. The
|
|
177 preceding ops will already have been processed by the framework (although it
|
|
178 may not update uses), and the subsequent IR will not yet be processed. This
|
|
179 can create confusion if a pattern attempts to match against a sequence of
|
|
180 ops (e.g. rewrite A + B -> C). That sort of rewrite should be performed in a
|
|
181 separate pass.
|
|
182
|
|
183 ## Type Conversion
|
|
184
|
|
185 It is sometimes necessary as part of a conversion to convert the set types of
|
|
186 being operated on. In these cases, a `TypeConverter` object may be defined that
|
|
187 details how types should be converted. The `TypeConverter` is used by patterns
|
|
188 and by the general conversion infrastructure to convert the signatures of blocks
|
|
189 and regions.
|
|
190
|
|
191 ### Type Converter
|
|
192
|
|
193 As stated above, the `TypeConverter` contains several hooks for detailing how to
|
|
194 convert types. Several of these hooks are detailed below:
|
|
195
|
|
196 ```c++
|
|
197 class TypeConverter {
|
|
198 public:
|
173
|
199 /// Register a conversion function. A conversion function must be convertible
|
|
200 /// to any of the following forms(where `T` is a class derived from `Type`:
|
|
201 /// * Optional<Type>(T)
|
|
202 /// - This form represents a 1-1 type conversion. It should return nullptr
|
|
203 /// or `llvm::None` to signify failure. If `llvm::None` is returned, the
|
|
204 /// converter is allowed to try another conversion function to perform
|
|
205 /// the conversion.
|
|
206 /// * Optional<LogicalResult>(T, SmallVectorImpl<Type> &)
|
|
207 /// - This form represents a 1-N type conversion. It should return
|
|
208 /// `failure` or `llvm::None` to signify a failed conversion. If the new
|
|
209 /// set of types is empty, the type is removed and any usages of the
|
|
210 /// existing value are expected to be removed during conversion. If
|
|
211 /// `llvm::None` is returned, the converter is allowed to try another
|
|
212 /// conversion function to perform the conversion.
|
|
213 ///
|
|
214 /// When attempting to convert a type, e.g. via `convertType`, the
|
|
215 /// `TypeConverter` will invoke each of the converters starting with the one
|
|
216 /// most recently registered.
|
|
217 template <typename ConversionFnT>
|
|
218 void addConversion(ConversionFnT &&callback);
|
150
|
219
|
|
220 /// This hook allows for materializing a conversion from a set of types into
|
|
221 /// one result type by generating a cast operation of some kind. The generated
|
|
222 /// operation should produce one result, of 'resultType', with the provided
|
|
223 /// 'inputs' as operands. This hook must be overridden when a type conversion
|
|
224 /// results in more than one type, or if a type conversion may persist after
|
|
225 /// the conversion has finished.
|
|
226 virtual Operation *materializeConversion(PatternRewriter &rewriter,
|
|
227 Type resultType,
|
|
228 ArrayRef<Value> inputs,
|
|
229 Location loc);
|
|
230 };
|
|
231 ```
|
|
232
|
|
233 ### Conversion Patterns
|
|
234
|
|
235 When type conversion comes into play, the general Rewrite Patterns can no longer
|
|
236 be used. This is due to the fact that the operands of the operation being
|
|
237 matched will not correspond with the operands of the correct type as determined
|
|
238 by `TypeConverter`. The operation rewrites on type boundaries must thus use a
|
|
239 special pattern, the `ConversionPattern`. This pattern provides, as an
|
|
240 additional argument to the `matchAndRewrite` and `rewrite` methods, the set of
|
|
241 remapped operands corresponding to the desired type. These patterns also utilize
|
|
242 a special `PatternRewriter`, `ConversionPatternRewriter`, that provides special
|
|
243 hooks for use with the conversion infrastructure.
|
|
244
|
|
245 ```c++
|
|
246 struct MyConversionPattern : public ConversionPattern {
|
|
247 /// The `matchAndRewrite` hooks on ConversionPatterns take an additional
|
|
248 /// `operands` parameter, containing the remapped operands of the original
|
|
249 /// operation.
|
173
|
250 virtual LogicalResult
|
150
|
251 matchAndRewrite(Operation *op, ArrayRef<Value> operands,
|
|
252 ConversionPatternRewriter &rewriter) const;
|
|
253 };
|
|
254 ```
|
|
255
|
|
256 These patterns have the same [restrictions](#restrictions) as the basic rewrite
|
|
257 patterns used in dialect conversion.
|
|
258
|
|
259 ### Region Signature Conversion
|
|
260
|
|
261 From the perspective of type conversion, the entry block to a region is often
|
|
262 special. The types of the entry block arguments are often tied semantically to
|
|
263 details on the operation, e.g. FuncOp, AffineForOp, etc. Given this, the
|
|
264 conversion of the types for this block must be done explicitly via a conversion
|
|
265 pattern. To convert the signature of a region entry block, a custom hook on the
|
|
266 ConversionPatternRewriter must be invoked `applySignatureConversion`. A
|
|
267 signature conversion, `TypeConverter::SignatureConversion`, can be built
|
|
268 programmatically:
|
|
269
|
|
270 ```c++
|
|
271 class SignatureConversion {
|
|
272 public:
|
|
273 /// Remap an input of the original signature with a new set of types. The
|
|
274 /// new types are appended to the new signature conversion.
|
|
275 void addInputs(unsigned origInputNo, ArrayRef<Type> types);
|
|
276
|
|
277 /// Append new input types to the signature conversion, this should only be
|
|
278 /// used if the new types are not intended to remap an existing input.
|
|
279 void addInputs(ArrayRef<Type> types);
|
|
280
|
|
281 /// Remap an input of the original signature with a range of types in the
|
|
282 /// new signature.
|
|
283 void remapInput(unsigned origInputNo, unsigned newInputNo,
|
|
284 unsigned newInputCount = 1);
|
|
285
|
|
286 /// Remap an input of the original signature to another `replacement`
|
|
287 /// value. This drops the original argument.
|
|
288 void remapInput(unsigned origInputNo, Value replacement);
|
|
289 };
|
|
290 ```
|
|
291
|
|
292 The `TypeConverter` provides several default utilities for signature conversion:
|
|
293 `convertSignatureArg`/`convertBlockSignature`.
|