forked from OSchip/llvm-project
278 lines
12 KiB
Markdown
278 lines
12 KiB
Markdown
|
# 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`.
|