xref: /llvm-project/mlir/docs/DialectConversion.md (revision 3ace685105d3b50bca68328bf0c945af22d70f23)
1# Dialect Conversion
2
3This document describes a framework in MLIR in which to perform operation
4conversions between, and within dialects. This framework allows for transforming
5illegal operations to those supported by a provided conversion target, via a set
6of pattern-based operation rewriting patterns.
7
8The dialect conversion framework consists of the following components:
9
10*   A [Conversion Target](#conversion-target)
11*   A set of [Rewrite Patterns](#rewrite-pattern-specification)
12*   A [Type Converter](#type-conversion) (Optional)
13
14[TOC]
15
16## Modes of Conversion
17
18When applying a conversion to a set of operations, there are several different
19conversion modes that may 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 input 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 legalizes all input operations, and is only successful
33        if all operations are properly legalized to the given conversion target.
34        This ensures that only known operations will exist after the conversion
35        process.
36    -   A full conversion can be applied via `applyFullConversion`.
37
38*   Analysis Conversion
39
40    -   An analysis conversion will analyze which operations are legalizable to
41        the given conversion target if a conversion were to be applied. This is
42        done by performing a 'partial' conversion and recording which operations
43        would have been successfully converted if successful. Note that no
44        rewrites, or transformations, are actually applied to the input
45        operations.
46    -   An analysis conversion can be applied via `applyAnalysisConversion`.
47
48In all cases, the framework walks the operations in preorder, examining an op
49before the ops in any regions it has.
50
51## Conversion Target
52
53The conversion target is a formal definition of what is considered to be legal
54during the conversion process. The final operations generated by the conversion
55framework must be marked as legal on the `ConversionTarget` for the rewrite to
56be a success. Depending on the conversion mode, existing operations need not
57always be legal. Operations and dialects may be marked with any of the provided
58legality actions below:
59
60*   Legal
61
62    -   This action signals that every instance of a given operation is legal,
63        i.e. any combination of attributes, operands, types, etc. are valid.
64
65*   Dynamic
66
67    -   This action signals that only some instances of a given operation are
68        legal. This allows for defining fine-tune constraints, e.g. saying that
69        `arith.addi` is only legal when operating on 32-bit integers.
70
71*   Illegal
72
73    -   This action signals that no instance of a given operation is legal.
74        Operations marked as "illegal" must always be converted for the
75        conversion to be successful. This action also allows for selectively
76        marking specific operations as illegal in an otherwise legal dialect.
77
78Operations and dialects that are neither explicitly marked legal nor illegal are
79separate from the above ("unknown" operations) and are treated differently, for
80example, for the purposes of partial conversion as mentioned above.
81
82An example conversion target is shown below:
83
84```c++
85struct MyTarget : public ConversionTarget {
86  MyTarget(MLIRContext &ctx) : ConversionTarget(ctx) {
87    //--------------------------------------------------------------------------
88    // Marking an operation as Legal:
89
90    /// Mark all operations within the LLVM dialect are legal.
91    addLegalDialect<LLVMDialect>();
92
93    /// Mark `arith.constant` op is always legal on this target.
94    addLegalOp<arith::ConstantOp>();
95
96    //--------------------------------------------------------------------------
97    // Marking an operation as dynamically legal.
98
99    /// Mark all operations within Affine dialect have dynamic legality
100    /// constraints.
101    addDynamicallyLegalDialect<affine::AffineDialect>(
102        [](Operation *op) { ... });
103
104    /// Mark `func.return` as dynamically legal, but provide a specific legality
105    /// callback.
106    addDynamicallyLegalOp<func::ReturnOp>([](func::ReturnOp op) { ... });
107
108    /// Treat unknown operations, i.e. those without a legalization action
109    /// directly set, as dynamically legal.
110    markUnknownOpDynamicallyLegal([](Operation *op) { ... });
111
112    //--------------------------------------------------------------------------
113    // Marking an operation as illegal.
114
115    /// All operations within the GPU dialect are illegal.
116    addIllegalDialect<GPUDialect>();
117
118    /// Mark `cf.br` and `cf.cond_br` as illegal.
119    addIllegalOp<cf::BranchOp, cf::CondBranchOp>();
120  }
121
122  /// Implement the default legalization handler to handle operations marked as
123  /// dynamically legal that were not provided with an explicit handler.
124  bool isDynamicallyLegal(Operation *op) override { ... }
125};
126```
127
128### Recursive Legality
129
130In some cases, it may be desirable to mark entire regions as legal. This
131provides an additional granularity of context to the concept of "legal". If an
132operation is marked recursively legal, either statically or dynamically, then
133all of the operations nested within are also considered legal even if they would
134otherwise be considered "illegal". An operation can be marked via
135`markOpRecursivelyLegal<>`:
136
137```c++
138ConversionTarget &target = ...;
139
140/// The operation must first be marked as `Legal` or `Dynamic`.
141target.addLegalOp<MyOp>(...);
142target.addDynamicallyLegalOp<MySecondOp>(...);
143
144/// Mark the operation as always recursively legal.
145target.markOpRecursivelyLegal<MyOp>();
146/// Mark optionally with a callback to allow selective marking.
147target.markOpRecursivelyLegal<MyOp, MySecondOp>([](Operation *op) { ... });
148/// Mark optionally with a callback to allow selective marking.
149target.markOpRecursivelyLegal<MyOp>([](MyOp op) { ... });
150```
151
152## Rewrite Pattern Specification
153
154After the conversion target has been defined, a set of legalization patterns
155must be provided to transform illegal operations into legal ones. The patterns
156supplied here have the same structure and restrictions as those described in the
157main [Pattern](PatternRewriter.md) documentation. The patterns provided do not
158need to generate operations that are directly legal on the target. The framework
159will automatically build a graph of conversions to convert non-legal operations
160into a set of legal ones.
161
162As an example, say you define a target that supports one operation: `foo.add`.
163When providing the following patterns: [`bar.add` -> `baz.add`, `baz.add` ->
164`foo.add`], the framework will automatically detect that it can legalize
165`bar.add` -> `foo.add` even though a direct conversion does not exist. This
166means that you don’t have to define a direct legalization pattern for `bar.add`
167-> `foo.add`.
168
169### Conversion Patterns
170
171Along with the general `RewritePattern` classes, the conversion framework
172provides a special type of rewrite pattern that can be used when a pattern
173relies on interacting with constructs specific to the conversion process, the
174`ConversionPattern`. For example, the conversion process does not necessarily
175update operations in-place and instead creates a mapping of events such as
176replacements and erasures, and only applies them when the entire conversion
177process is successful. Certain classes of patterns rely on using the
178updated/remapped operands of an operation, such as when the types of results
179defined by an operation have changed. The general Rewrite Patterns can no longer
180be used in these situations, as the types of the operands of the operation being
181matched will not correspond with those expected by the user. This pattern
182provides, as an additional argument to the `matchAndRewrite` and `rewrite`
183methods, the list of operands that the operation should use after conversion. If
184an operand was the result of a non-converted operation, for example if it was
185already legal, the original operand is used. This means that the operands
186provided always have a 1-1 non-null correspondence with the operands on the
187operation. The original operands of the operation are still intact and may be
188inspected as normal. These patterns also utilize a special `PatternRewriter`,
189`ConversionPatternRewriter`, that provides special hooks for use with the
190conversion infrastructure.
191
192```c++
193struct MyConversionPattern : public ConversionPattern {
194  /// The `matchAndRewrite` hooks on ConversionPatterns take an additional
195  /// `operands` parameter, containing the remapped operands of the original
196  /// operation.
197  virtual LogicalResult
198  matchAndRewrite(Operation *op, ArrayRef<Value> operands,
199                  ConversionPatternRewriter &rewriter) const;
200};
201```
202
203#### Type Safety
204
205The types of the remapped operands provided to a conversion pattern must be of a
206type expected by the pattern. The expected types of a pattern are determined by
207a provided [TypeConverter](#type-converter). If no type converter is provided,
208the types of the remapped operands are expected to match the types of the
209original operands. If a type converter is provided, the types of the remapped
210operands are expected to be legal as determined by the converter. If the
211remapped operand types are not of an expected type, and a materialization to the
212expected type could not be performed, the pattern fails application before the
213`matchAndRewrite` hook is invoked. This ensures that patterns do not have to
214explicitly ensure type safety, or sanitize the types of the incoming remapped
215operands. More information on type conversion is detailed in the
216[dedicated section](#type-conversion) below.
217
218## Type Conversion
219
220It is sometimes necessary as part of a conversion to convert the set types of
221being operated on. In these cases, a `TypeConverter` object may be defined that
222details how types should be converted when interfacing with a pattern. A
223`TypeConverter` may be used to convert the signatures of block arguments and
224regions, to define the expected inputs types of the pattern, and to reconcile
225type differences in general.
226
227### Type Converter
228
229The `TypeConverter` contains several hooks for detailing how to convert types,
230and how to materialize conversions between types in various situations. The two
231main aspects of the `TypeConverter` are conversion and materialization.
232
233A `conversion` describes how a given source `Type` should be converted to N
234target types. If the source type is converted to itself, we say it is a "legal"
235type. Type conversions are specified via the `addConversion` method described
236below.
237
238A `materialization` describes how a list of values should be converted to a
239list of values with specific types. An important distinction from a
240`conversion` is that a `materialization` can produce IR, whereas a `conversion`
241cannot. These materializations are used by the conversion framework to ensure
242type safety during the conversion process. There are several types of
243materializations depending on the situation.
244
245*   Source Materialization
246
247    -   A source materialization is used when a value was replaced with a value
248        of a different type, but there are still users that expects the original
249        ("source") type at the end of the conversion process. A source
250        materialization converts the replacement value back to the source type.
251    -   This materialization is used in the following situations:
252        *   When a block argument has been converted to a different type, but
253            the original argument still has users that will remain live after
254            the conversion process has finished.
255        *   When a block argument has been dropped, but the argument still has
256            users that will remain live after the conversion process has
257            finished.
258        *   When the result type of an operation has been converted to a
259            different type, but the original result still has users that will
260            remain live after the conversion process is finished.
261
262*   Target Materialization
263
264    -   A target materialization converts a value to the type that is expected
265        by a conversion pattern according to its type converter.
266    -   A target materialization is used when a pattern expects the remapped
267        operands to be of a certain set of types, but the original input
268        operands have either not been replaced or been replaced with values of
269        a different type.
270
271If a converted value is used by an operation that isn't converted, it needs a
272conversion back to the `source` type, hence source materialization; if an
273unconverted value is used by an operation that is being converted, it needs
274conversion to the `target` type, hence target materialization.
275
276As noted above, the conversion process guarantees that the type contract of the
277IR is preserved during the conversion. This means that the types of value uses
278will not implicitly change during the conversion process. When the type of a
279value definition, either block argument or operation result, is being changed,
280the users of that definition must also be updated during the conversion process.
281If they aren't, a type conversion must be materialized to ensure that a value of
282the expected type is still present within the IR. If a materialization is
283required, but cannot be performed, the entire conversion process fails.
284
285Several of the available hooks are detailed below:
286
287```c++
288class TypeConverter {
289 public:
290  /// Register a conversion function. A conversion function defines how a given
291  /// source type should be converted. A conversion function must be convertible
292  /// to any of the following forms(where `T` is a class derived from `Type`:
293  ///   * Optional<Type>(T)
294  ///     - This form represents a 1-1 type conversion. It should return nullptr
295  ///       or `std::nullopt` to signify failure. If `std::nullopt` is returned, the
296  ///       converter is allowed to try another conversion function to perform
297  ///       the conversion.
298  ///   * Optional<LogicalResult>(T, SmallVectorImpl<Type> &)
299  ///     - This form represents a 1-N type conversion. It should return
300  ///       `failure` or `std::nullopt` to signify a failed conversion. If the new
301  ///       set of types is empty, the type is removed and any usages of the
302  ///       existing value are expected to be removed during conversion. If
303  ///       `std::nullopt` is returned, the converter is allowed to try another
304  ///       conversion function to perform the conversion.
305  ///   * Optional<LogicalResult>(T, SmallVectorImpl<Type> &, ArrayRef<Type>)
306  ///     - This form represents a 1-N type conversion supporting recursive
307  ///       types. The first two arguments and the return value are the same as
308  ///       for the regular 1-N form. The third argument is contains is the
309  ///       "call stack" of the recursive conversion: it contains the list of
310  ///       types currently being converted, with the current type being the
311  ///       last one. If it is present more than once in the list, the
312  ///       conversion concerns a recursive type.
313  /// Note: When attempting to convert a type, e.g. via 'convertType', the
314  ///       mostly recently added conversions will be invoked first.
315  template <typename FnT,
316            typename T = typename llvm::function_traits<FnT>::template arg_t<0>>
317  void addConversion(FnT &&callback) {
318    registerConversion(wrapCallback<T>(std::forward<FnT>(callback)));
319  }
320
321  /// All of the following materializations require function objects that are
322  /// convertible to the following form:
323  ///   `std::optional<Value>(OpBuilder &, T, ValueRange, Location)`,
324  /// where `T` is any subclass of `Type`. This function is responsible for
325  /// creating an operation, using the OpBuilder and Location provided, that
326  /// "casts" a range of values into a single value of the given type `T`. It
327  /// must return a Value of the converted type on success, an `std::nullopt` if
328  /// it failed but other materialization can be attempted, and `nullptr` on
329  /// unrecoverable failure. It will only be called for (sub)types of `T`.
330  /// Materialization functions must be provided when a type conversion may
331  /// persist after the conversion has finished.
332
333  /// This method registers a materialization that will be called when
334  /// converting a replacement value back to its original source type.
335  /// This is used when some uses of the original value persist beyond the main
336  /// conversion.
337  template <typename FnT,
338            typename T = typename llvm::function_traits<FnT>::template arg_t<1>>
339  void addSourceMaterialization(FnT &&callback) {
340    sourceMaterializations.emplace_back(
341        wrapMaterialization<T>(std::forward<FnT>(callback)));
342  }
343
344  /// This method registers a materialization that will be called when
345  /// converting a value to a target type according to a pattern's type
346  /// converter.
347  ///
348  /// Note: Target materializations can optionally inspect the "original"
349  /// type. This type may be different from the type of the input value.
350  /// For example, let's assume that a conversion pattern "P1" replaced an SSA
351  /// value "v1" (type "t1") with "v2" (type "t2"). Then a different conversion
352  /// pattern "P2" matches an op that has "v1" as an operand. Let's furthermore
353  /// assume that "P2" determines that the converted target type of "t1" is
354  /// "t3", which may be different from "t2". In this example, the target
355  /// materialization will be invoked with: outputType = "t3", inputs = "v2",
356  /// originalType = "t1". Note that the original type "t1" cannot be recovered
357  /// from just "t3" and "v2"; that's why the originalType parameter exists.
358  ///
359  /// Note: During a 1:N conversion, the result types can be a TypeRange. In
360  /// that case the materialization produces a SmallVector<Value>.
361  template <typename FnT,
362            typename T = typename llvm::function_traits<FnT>::template arg_t<1>>
363  void addTargetMaterialization(FnT &&callback) {
364    targetMaterializations.emplace_back(
365        wrapMaterialization<T>(std::forward<FnT>(callback)));
366  }
367};
368```
369
370Materializations through the type converter are optional. If the
371`ConversionConfig::buildMaterializations` flag is set to "false", the dialect
372conversion driver builds an `unrealized_conversion_cast` op instead of calling
373the respective type converter callback whenever a materialization is required.
374
375### Region Signature Conversion
376
377From the perspective of type conversion, the types of block arguments are a bit
378special. Throughout the conversion process, blocks may move between regions of
379different operations. Given this, the conversion of the types for blocks must be
380done explicitly via a conversion pattern.
381
382To convert the types of block arguments within a Region, a custom hook on the
383`ConversionPatternRewriter` must be invoked; `convertRegionTypes`. This hook
384uses a provided type converter to apply type conversions to all blocks of a
385given region. This hook also takes an optional
386`TypeConverter::SignatureConversion` parameter that applies a custom conversion
387to the entry block of the region. The types of the entry block arguments are
388often tied semantically to the operation, e.g., `func::FuncOp`, `AffineForOp`,
389etc.
390
391To convert the signature of just one given block, the
392`applySignatureConversion` hook can be used.
393
394A signature conversion, `TypeConverter::SignatureConversion`, can be built
395programmatically:
396
397```c++
398class SignatureConversion {
399public:
400    /// Remap an input of the original signature with a new set of types. The
401    /// new types are appended to the new signature conversion.
402    void addInputs(unsigned origInputNo, ArrayRef<Type> types);
403
404    /// Append new input types to the signature conversion, this should only be
405    /// used if the new types are not intended to remap an existing input.
406    void addInputs(ArrayRef<Type> types);
407
408    /// Remap an input of the original signature with a range of types in the
409    /// new signature.
410    void remapInput(unsigned origInputNo, unsigned newInputNo,
411                    unsigned newInputCount = 1);
412
413    /// Remap an input of the original signature to another `replacement`
414    /// value. This drops the original argument.
415    void remapInput(unsigned origInputNo, Value replacement);
416};
417```
418
419The `TypeConverter` provides several default utilities for signature conversion
420and legality checking:
421`convertSignatureArgs`/`convertBlockSignature`/`isLegal(Region *|Type)`.
422
423## Debugging
424
425To debug the execution of the dialect conversion framework,
426`-debug-only=dialect-conversion` may be used. This command line flag activates
427LLVM's debug logging infrastructure solely for the conversion framework. The
428output is formatted as a tree structure, mirroring the structure of the
429conversion process. This output contains all of the actions performed by the
430rewriter, how generated operations get legalized, and why they fail.
431
432Example output is shown below:
433
434```
435//===-------------------------------------------===//
436Legalizing operation : 'func.return'(0x608000002e20) {
437  "func.return"() : () -> ()
438
439  * Fold {
440  } -> FAILURE : unable to fold
441
442  * Pattern : 'func.return -> ()' {
443    ** Insert  : 'spirv.Return'(0x6070000453e0)
444    ** Replace : 'func.return'(0x608000002e20)
445
446    //===-------------------------------------------===//
447    Legalizing operation : 'spirv.Return'(0x6070000453e0) {
448      "spirv.Return"() : () -> ()
449
450    } -> SUCCESS : operation marked legal by the target
451    //===-------------------------------------------===//
452  } -> SUCCESS : pattern applied successfully
453} -> SUCCESS
454//===-------------------------------------------===//
455```
456
457This output is describing the legalization of an `func.return` operation. We
458first try to legalize by folding the operation, but that is unsuccessful for
459`func.return`. From there, a pattern is applied that replaces the `func.return`
460with a `spirv.Return`. The newly generated `spirv.Return` is then processed for
461legalization, but is found to already legal as per the target.
462