summaryrefslogtreecommitdiffstats
path: root/mlir/docs/DialectConversion.md
blob: e6b652f21913afb1c26923e564afc70ff4f5bd90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# Dialect Conversion

This document describes a framework in MLIR in which to perform operation
conversions between, and within dialects. This framework allows for transforming
illegal operations to those supported by a provided conversion target, via a set
of pattern-based operation rewriting patterns.

[TOC]

To utilize the framework, a few things must be provided:

*   A [Conversion Target](#conversion-target)
*   A set of [Rewrite Patterns](#rewrite-pattern-specification)
*   A [Type Converter](#type-conversion) (Optional)

## Modes of Conversion

When applying a conversion to a set of operations, there are several conversion
modes that can be selected from:

*   Partial Conversion

    -   A partial conversion will legalize as many operations to the target as
        possible, but will allow pre-existing operations that were not
        explicitly marked as `illegal` to remain unconverted. This allows for
        partially lowering parts of the module in the presence of unknown
        operations.
    -   A partial conversion can be applied via `applyPartialConversion`.

*   Full Conversion

    -   A full conversion is only successful if all operations are properly
        legalized to the given conversion target. This ensures that only known
        operations will exist after the conversion process.
    -   A full conversion can be applied via `applyFullConversion`.

*   Analysis Conversion

    -   An analysis conversion will analyze which operations are legalizable to
        the given conversion target if a conversion were to be applied. Note
        that no rewrites, or transformations, are actually applied to the input
        operations.
    -   An analysis conversion can be applied via `applyAnalysisConversion`.

## Conversion Target

The conversion target is the formal definition of what is considered to be legal
during the conversion process. The final operations generated by the conversion
framework must be marked as legal on the `ConversionTarget` for the rewrite to
be a success. Existing operations need not always be legal, though; see the
different conversion modes for why. Operations and dialects may be marked with
any of the provided legality actions below:

*   Legal

    -   This action signals that every instance of a given operation is legal,
        i.e. any combination of attributes, operands, types, etc. are valid.

*   Dynamic

    -   This action signals that only some instances of a given operation are
        legal. This allows for defining fine-tune constraints, e.g. saying that
        `addi` is only legal when operating on 32-bit integers.
    -   If a specific handler is not provided when setting the action, the
        target must override the `isDynamicallyLegal` hook provided by
        `ConversionTarget`.

*   Illegal

    -   This action signals that no instance of a given operation is legal.
        Operations marked as `illegal` must always be converted for the
        conversion to be successful. This action also allows for selectively
        marking specific operations as illegal in an otherwise legal dialect.

An example conversion target is shown below:

```c++
struct MyTarget : public ConversionTarget {
  MyTarget(MLIRContext &ctx) : ConversionTarget(ctx) {
    //--------------------------------------------------------------------------
    // Marking an operation as Legal:

    /// Mark all operations within the LLVM dialect are legal.
    addLegalDialects<LLVMDialect>();

    /// Mark `std.constant` op is always legal on this target.
    addLegalOps<ConstantOp>();

    //--------------------------------------------------------------------------
    // Marking an operation as dynamically legal.

    /// Mark all operations within Affine dialect have dynamic legality
    /// constraints.
    addDynamicallyLegalDialects<AffineDialect>();

    /// Mark `std.return` as dynamically legal.
    addDynamicallyLegalOp<ReturnOp>();

    /// Mark `std.return` as dynamically legal, but provide a specific legality
    /// callback.
    addDynamicallyLegalOp<ReturnOp>([](ReturnOp op) { ... });

    //--------------------------------------------------------------------------
    // Marking an operation as illegal.

    /// All operations within the GPU dialect are illegal.
    addIllegalDialect<GPUDialect>();

    /// Mark `std.br` and `std.cond_br` as illegal.
    addIllegalOp<BranchOp, CondBranchOp>();
  }

  /// Implement the default legalization handler to handle operations marked as
  /// dynamically legal that were not provided with an explicit handler.
  bool isDynamicallyLegal(Operation *op) override { ... }
};
```

### Recursive Legality

In some cases, it may be desirable to mark entire regions of operations as
legal. This provides an additional granularity of context to the concept of
"legal". The `ConversionTarget` supports marking operations, that were
previously added as `Legal` or `Dynamic`, as `recursively` legal. Recursive
legality means that if an operation instance is legal, either statically or
dynamically, all of the operations nested within are also considered legal. An
operation can be marked via `markOpRecursivelyLegal<>`:

```c++
ConversionTarget &target = ...;

/// The operation must first be marked as `Legal` or `Dynamic`.
target.addLegalOp<MyOp>(...);
target.addDynamicallyLegalOp<MySecondOp>(...);

/// Mark the operation as always recursively legal.
target.markOpRecursivelyLegal<MyOp>();
/// Mark optionally with a callback to allow selective marking.
target.markOpRecursivelyLegal<MyOp, MySecondOp>([](Operation *op) { ... });
/// Mark optionally with a callback to allow selective marking.
target.markOpRecursivelyLegal<MyOp>([](MyOp op) { ... });
```

## Rewrite Pattern Specification

After the conversion target has been defined, a set of legalization patterns
must be provided to transform illegal operations into legal ones. The patterns
supplied here, that do not [require type changes](#conversion-patterns), are the
same as those described in the
[quickstart rewrites guide](QuickstartRewrites.md#adding-patterns), but have a
few additional [restrictions](#restrictions). The patterns provided do not need
to generate operations that are directly legal on the target. The framework will
automatically build a graph of conversions to convert non-legal operations into
a set of legal ones.

As an example, say you define a target that supports one operation: `foo.add`.
When providing the following patterns: [`bar.add` -> `baz.add`, `baz.add` ->
`foo.add`], the framework will automatically detect that it can legalize
`baz.add` -> `foo.add` even though a direct conversion does not exist. This
means that you don’t have to define a direct legalization pattern for `bar.add`
-> `foo.add`.

### Restrictions

The framework processes operations in topological order, trying to legalize them
individually. As such, patterns used in the conversion framework have a few
additional restrictions:

1.  If a pattern matches, it must erase or replace the op it matched on.
    Operations can *not* be updated in place.
2.  Match criteria should not be based on the IR outside of the op itself. The
    preceding ops will already have been processed by the framework (although it
    may not update uses), and the subsequent IR will not yet be processed. This
    can create confusion if a pattern attempts to match against a sequence of
    ops (e.g. rewrite A + B -> C). That sort of rewrite should be performed in a
    separate pass.

## Type Conversion

It is sometimes necessary as part of a conversion to convert the set types of
being operated on. In these cases, a `TypeConverter` object may be defined that
details how types should be converted. The `TypeConverter` is used by patterns
and by the general conversion infrastructure to convert the signatures of blocks
and regions.

### Type Converter

As stated above, the `TypeConverter` contains several hooks for detailing how to
convert types. Several of these hooks are detailed below:

```c++
class TypeConverter {
 public:
  /// This hook allows for converting a type. This function should return
  /// failure if no valid conversion exists, success otherwise. If the new set
  /// of types is empty, the type is removed and any usages of the existing
  /// value are expected to be removed during conversion.
  virtual LogicalResult convertType(Type t, SmallVectorImpl<Type> &results);

  /// This hook simplifies defining 1-1 type conversions. This function returns
  /// the type to convert to on success, and a null type on failure.
  virtual Type convertType(Type t);

  /// This hook allows for materializing a conversion from a set of types into
  /// one result type by generating a cast operation of some kind. The generated
  /// operation should produce one result, of 'resultType', with the provided
  /// 'inputs' as operands. This hook must be overridden when a type conversion
  /// results in more than one type, or if a type conversion may persist after
  /// the conversion has finished.
  virtual Operation *materializeConversion(PatternRewriter &rewriter,
                                           Type resultType,
                                           ArrayRef<Value> inputs,
                                           Location loc);
};
```

### Conversion Patterns

When type conversion comes into play, the general Rewrite Patterns can no longer
be used. This is due to the fact that the operands of the operation being
matched will not correspond with the operands of the correct type as determined
by `TypeConverter`. The operation rewrites on type boundaries must thus use a
special pattern, the `ConversionPattern`. This pattern provides, as an
additional argument to the `matchAndRewrite` and `rewrite` methods, the set of
remapped operands corresponding to the desired type. These patterns also utilize
a special `PatternRewriter`, `ConversionPatternRewriter`, that provides special
hooks for use with the conversion infrastructure.

```c++
struct MyConversionPattern : public ConversionPattern {
  /// The `matchAndRewrite` hooks on ConversionPatterns take an additional
  /// `operands` parameter, containing the remapped operands of the original
  /// operation.
  virtual PatternMatchResult
  matchAndRewrite(Operation *op, ArrayRef<Value> operands,
                  ConversionPatternRewriter &rewriter) const;
};
```

These patterns have the same [restrictions](#restrictions) as the basic rewrite
patterns used in dialect conversion.

### Region Signature Conversion

From the perspective of type conversion, the entry block to a region is often
special. The types of the entry block arguments are often tied semantically to
details on the operation, e.g. FuncOp, AffineForOp, etc. Given this, the
conversion of the types for this block must be done explicitly via a conversion
pattern. To convert the signature of a region entry block, a custom hook on the
ConversionPatternRewriter must be invoked `applySignatureConversion`. A
signature conversion, `TypeConverter::SignatureConversion`, can be built
programmatically:

```c++
class SignatureConversion {
public:
    /// Remap an input of the original signature with a new set of types. The
    /// new types are appended to the new signature conversion.
    void addInputs(unsigned origInputNo, ArrayRef<Type> types);

    /// Append new input types to the signature conversion, this should only be
    /// used if the new types are not intended to remap an existing input.
    void addInputs(ArrayRef<Type> types);

    /// Remap an input of the original signature with a range of types in the
    /// new signature.
    void remapInput(unsigned origInputNo, unsigned newInputNo,
                    unsigned newInputCount = 1);

    /// Remap an input of the original signature to another `replacement`
    /// value. This drops the original argument.
    void remapInput(unsigned origInputNo, Value replacement);
};
```

The `TypeConverter` provides several default utilities for signature conversion:
`convertSignatureArg`/`convertBlockSignature`.
OpenPOWER on IntegriCloud