References to headings need to be preceded with a slash. Also, references to headings on the same page do not need to contain the name of the document (omitting the document name means if the name changes the links will still be valid). I double checked the links by building [the website](https://github.com/llvm/mlir-www): ```shell ./mlir-www-helper.sh --install-docs ../llvm-project website cd website && hugo serve ```
473 lines
22 KiB
Markdown
473 lines
22 KiB
Markdown
# Transform Dialect
|
|
|
|
Fine-grain transformation control dialect. See [tutorial](../Tutorials/transform) for more introductory information.
|
|
|
|
[TOC]
|
|
|
|
## Overview
|
|
|
|
This dialect provides operations that can be used to control transformation
|
|
of the IR using a different portion of the IR. It refers to the IR being
|
|
transformed as payload IR, and to the IR guiding the transformation as
|
|
transform IR.
|
|
|
|
The main use case for this dialect is orchestrating fine-grain transformations
|
|
on individual IR objects (operations or values) or sets thereof. For example, it
|
|
may involve finding loop-like operations with specific properties (e.g., large
|
|
size) in the payload IR, applying loop tiling to those and only those
|
|
operations, and then applying loop unrolling to the inner loops produced by the
|
|
previous transformations. As such, it is not intended as a replacement for the
|
|
pass infrastructure, nor for the pattern rewriting infrastructure. In the most
|
|
common case, the transform IR will be processed and applied to the payload IR by
|
|
a pass. Transformations expressed by the Transform dialect may be implemented
|
|
using the pattern infrastructure or any other relevant MLIR component.
|
|
|
|
The following IR gives a rough idea of what the operations in this dialect
|
|
may look like without using actually existing operations:
|
|
|
|
```mlir
|
|
%0 = transform.loop.find { size > 42 } : !transform.interface<tileable>
|
|
%1 = transform.compute_trailing_tile_size %0 : !transform.param<index>
|
|
%2:2 = transform.loop.tile %0 tile_sizes(1, 4, %1)
|
|
: (!transform.interface<tileable>)
|
|
-> (!transform.op<loop>, !transform.op<loop>)
|
|
%3 = transform.get_op_result [0] %2#0 : !transform.any_value
|
|
transform.assign_to_fast_memory %3
|
|
transform.loop.unroll %1#1 : !transform.op<loop>
|
|
```
|
|
|
|
The values used in the Transform dialect may correspond to:
|
|
|
|
* sets of operations in the payload IR;
|
|
|
|
* sets of values in the payload IR;
|
|
|
|
* sets of parameters (attributes) known at the execution time of the
|
|
transform dialect.
|
|
|
|
The former two kinds of values are also referred to as operation and value
|
|
*handles*, respectively. In the example above, `%0` corresponds to the set of
|
|
loops found in the payload IR that satisfy the condition, and `%2` correspond to
|
|
groups of outer and inner loops, respectively, produced by the tiling
|
|
transformation. `%3` corresponds to a set of values that are produced by the
|
|
outer loops after tiling. `%1` corresponds to a list of tile sizes selected for
|
|
each of the operations that `%0` corresponds to.
|
|
|
|
An operation handle such as `%0` may be associated with multiple payload
|
|
operations. This is conceptually a set of operations and no assumptions should
|
|
be made about the order of ops unless specified otherwise by the operation.
|
|
Similarly, a value handle such as `%3` may be associated with a set of payload
|
|
IR values. Transform dialect operations may take as operands and produce an
|
|
arbitrary combination of values representing handles and parameters. Most
|
|
Transform IR ops support operand values that are mapped to multiple payload
|
|
objects. They usually apply the respective transformation for every mapped
|
|
object ("batched execution"). Deviations from this convention are described in
|
|
the documentation of Transform IR ops.
|
|
|
|
Parameters, such as `%1` in the above example, have two logical roles in
|
|
transform IR. In parameter based control, they carry the values needed to
|
|
execute the explicit control defined by the transforms, for example:
|
|
|
|
```mlir
|
|
%0 = transform.match.structured.rank %linalg_op_handle : !transform.param<index>
|
|
%1 = transform.param.constant 3 : i32 -> !transform.param<index>
|
|
transform.execute_if_cmpi eq %0, %1 : !transform.param<index>, !transform.param<index>
|
|
// Some nested body of transform ops
|
|
```
|
|
|
|
Alternatively, parameters can associate with the payload IR where the specific
|
|
value at execution time has no bearing on the execution of the transform IR. In
|
|
other words, parameters can either associate with the transform IR or the
|
|
payload IR. Note that it is generally discouraged to use parameters containing
|
|
arbitrary attributes within transform control. Parameter based control should
|
|
try to be explicitly typed when possible.
|
|
|
|
The transform IR values have transform IR types, which should implement exactly one of:
|
|
|
|
* [TransformHandleTypeInterface](#transformhandletypeinterface-transformhandletypeinterface),
|
|
|
|
* [TransformValueHandleTypeInterface](#transformvaluehandletypeinterface-transformvaluehandletypeinterface),
|
|
|
|
* [TransformParamTypeInterface](#transformparamtypeinterface-transformparamtypeinterface).
|
|
|
|
The goal of these type interfaces, beyond providing a common base for accepted
|
|
types, is to verify the properties of the associated objects. For example, a
|
|
handle type interface implementation may check whether all associated payload IR
|
|
operations implement the "TileableOp" interface or have a specific "loop" kind.
|
|
Similarly, a value handle type interface implementation may check if the
|
|
associated payload IR values are block arguments or have a specific type, or a
|
|
parameter type interface may check whether the associated attributes contain
|
|
non-negative integer values. These properties are used to statically indicate
|
|
pre- and post-conditions of a transformation connected to a Transform dialect
|
|
operation. The conditions are verified when payload objects operations are first
|
|
associated with a transform handle. By convention, Transform dialect operations
|
|
are expected to indicate narrow preconditions for their operands by enforcing
|
|
operand type constraints in the their definitions and verifiers. On the
|
|
contrary, operations are expected to have few constraints on their results.
|
|
Specific instances of a transform operation can then be created with a more
|
|
restricted result type than the constraint in the operation (e.g., the "find"
|
|
operation only constrains the result type to be a transform IR type while its
|
|
concrete instance can have a type with stricter constraints such as implementing
|
|
the "tilable" interface). The verification will then happen at transform
|
|
execution time. This approach allows one to capture payload IR operation
|
|
properties in the transform IR without resorting to excessive use of type casts
|
|
or coupling dialect extensions between themselves. It is a trade-off between
|
|
verbosity/complexity and static hardening, which can be revised in the future.
|
|
|
|
Overall, Transform IR ops are expected to be contained in a single top-level
|
|
op. Such top-level ops specify how to apply the transformations described
|
|
by the operations they contain, e.g., `transform.sequence` executes
|
|
transformations one by one and fails if any of them fails. Such ops are
|
|
expected to have the `PossibleTopLevelTransformOpTrait` and may be used
|
|
without arguments.
|
|
|
|
A program transformation expressed using the Transform dialect can be
|
|
programmatically triggered by calling:
|
|
|
|
```c++
|
|
LogicalResult transform::applyTransforms(
|
|
Operation *payloadRoot,
|
|
const RaggedArray<transform::MappedValue> &extraMappings,
|
|
TransformOpInterface transform,
|
|
const TransformOptions &options);
|
|
```
|
|
|
|
that applies the transformations specified by the top-level `transform` to
|
|
payload IR contained in `payloadRoot`. The payload root operation will be
|
|
associated with the first argument of the entry block of the top-level transform
|
|
op. This block may have additional arguments, handles or parameters. They will
|
|
be associated with values provided as `extraMappings`. The call will report an
|
|
error and return if the wrong number of mappings is provided.
|
|
|
|
## Dialect Extension Mechanism
|
|
|
|
This dialect is designed to be extensible, that is, clients of this dialect
|
|
are allowed to inject additional operations into this dialect using the
|
|
`TransformDialectExtension` mechanism. This allows the dialect to avoid a
|
|
dependency on the implementation of the transformation as well as to avoid
|
|
introducing dialect-specific transform dialects. In the example above,
|
|
the operations may have been injected by a notional `loop` dialect rather
|
|
than defined in this dialect, hence the common prefix.
|
|
|
|
It is recommended to prefix injected operations with one or several
|
|
dot-separated words that indicate which extension adds them. For
|
|
dialect-specific transformations, the prefix is naturally the name of the
|
|
dialect, e.g., `transform.affine.reschedule`. For dialect-agnostic
|
|
transformations (typically implemented using interfaces), the prefix may
|
|
be derived from the interface name or from a common concept, e.g.,
|
|
`transform.loop.tile` may apply to any loop-like operation that implements
|
|
`TileableOpInterface`. The C++ classes for the dialect extension should
|
|
include the prefix in their name, e.g., `AffineTransformDialectExtension` or
|
|
`LoopTransformDialectExtension` in the cases above. Unprefixed operation
|
|
names are reserved for ops defined directly in the Transform dialect.
|
|
|
|
Operations injected into the dialect must:
|
|
|
|
* Implement the `TransformOpInterface` to execute the corresponding
|
|
transformation on the payload IR.
|
|
|
|
* Implement the `MemoryEffectsOpInterface` to annotate the effects of
|
|
the transform IR operation on the payload IR as well as on the mapping
|
|
between transform IR values and payload IR operations. See below for
|
|
the description of available effects.
|
|
|
|
The presence of interface implementations is checked at runtime when the
|
|
dialect is loaded to allow for those implementations to be supplied by
|
|
separate dialect extensions if desired.
|
|
|
|
Similarly to operations, additional types can be injected into the dialect using
|
|
the same extension mechanism. The types must:
|
|
|
|
* Implement exactly one of `TransformHandleTypeInterface`,
|
|
`TransformValueHandleTypeInterface`, `TransformParamTypeInterface`.
|
|
|
|
## Side Effects
|
|
|
|
The Transform dialect relies on MLIR side effect modelling to enable
|
|
optimization of the transform IR. More specifically, it provides several
|
|
side effect resource objects and expects operations to describe their
|
|
effects on these resources.
|
|
|
|
* `TransformMappingResource` - side effect resource corresponding to the
|
|
mapping between transform IR values and payload IR operations.
|
|
|
|
- An `Allocate` effect from this resource means creating a new mapping
|
|
entry, it is always accompanied by a `Write` effect.
|
|
|
|
- A `Read` effect from this resource means accessing the mapping.
|
|
|
|
- A `Free` effect on this resource indicates the removal of the mapping
|
|
entry, typically after a transformation that modifies the payload IR
|
|
operations associated with one of the transform IR operation's
|
|
operands. It is always accompanied by a `Read` effect.
|
|
|
|
* `PayloadIRResource` - side effect resource corresponding to the payload
|
|
IR itself.
|
|
|
|
- A `Read` effect from this resource means accessing the payload IR.
|
|
|
|
- A `Write` effect on this resource means mutating the payload IR. It is
|
|
almost always accompanied by a `Read`.
|
|
|
|
The typical flow of values in the transform IR is as follows. Most
|
|
operations produce new transform IR values and immediately associate them
|
|
with a list of payload IR operations. This corresponds to `Allocate` and
|
|
`Write` effects on the `TransformMappingResource`, and often requires at
|
|
least a `Read` effect on the `PayloadIRResource`. Transform operations that
|
|
only inspect the payload IR to produce new handles are usually limited to
|
|
these effects on their operands. Transform operations that mutate the
|
|
payload IR are thought to _consume_ the handles provided as operands, that
|
|
is have the `Read` and `Free` effects on them. As with the usual memory
|
|
effects, using a value after it was freed is incorrect. In case of the
|
|
transform IR, this value is likely associated with payload IR operations
|
|
that were modified or even removed by the transformation, so it is
|
|
meaningless to refer to them. When further transformations are desired, the
|
|
transform operations can return _new_ handles that can be read or consumed
|
|
by subsequent operations.
|
|
|
|
## Execution Model
|
|
|
|
The transformation starts at the user-specified top-level transform IR
|
|
operation and applies to some user-specified payload IR scope, identified by
|
|
the payload IR op that contains the IR to transform. It is the
|
|
responsibility of the user to properly select the scope and/or to avoid the
|
|
transformations to modify the IR outside of the given scope. The top-level
|
|
transform IR operation may contain further transform operations and execute
|
|
them in the desired order.
|
|
|
|
Transformation application functions produce a tri-state status:
|
|
|
|
- success;
|
|
- recoverable (silenceable) failure;
|
|
- irrecoverable failure.
|
|
|
|
Transformation container operations may intercept recoverable failures and
|
|
perform the required recovery steps thus succeeding themselves. On
|
|
the other hand, they must propagate irrecoverable failures. For such
|
|
failures, the diagnostics are emitted immediately whereas their emission is
|
|
postponed for recoverable failures. Transformation container operations may
|
|
also fail to recover from a theoretically recoverable failure, in which case
|
|
they can either propagate it to their parent or emit the diagnostic and turn
|
|
the failure into an irrecoverable one. A recoverable failure produced by
|
|
applying the top-level transform IR operation is considered irrecoverable.
|
|
|
|
Transformation container operations are allowed to "step over" some nested
|
|
operations if the application of some previous operation produced a failure.
|
|
This can be conceptually thought of as having a global "recoverable error
|
|
register" that is read/write accessed by each transform operation as a side
|
|
effect. The transformation is skipped if the register already contains an
|
|
error description, and the control flow proceeds to the following operation.
|
|
|
|
Note that a silenceable failure, if emitted, is a compiler _error_ rather
|
|
than a warning. Transformations are expected to produce silenceable failures
|
|
if they haven't yet modified the payload IR, i.e. when reporting a
|
|
precondition failure, and an irrecoverable failure when they modified the IR
|
|
in a way that is contrary to the semantics of the transform operation or
|
|
would fail a postcondition. Some "navigation" operations that identify
|
|
payload IR targets for the following transformation may have a conceptual
|
|
"failure to match" that is considered a successful execution in the
|
|
execution model but results in handles associated with empty payload IR
|
|
operation lists.
|
|
|
|
## Handle Invalidation
|
|
|
|
The execution model of the Transform dialect allows a payload IR operation to be
|
|
associated with _multiple_ handles as well as nested payload IR operations to be
|
|
associated with different handles. Similarly, a payload IR value may be
|
|
associated with multiple transform IR value handles. When a transform IR
|
|
operation consumes a handle, it usually indicates that the corresponding payload
|
|
IR object was destroyed and should no longer be referenced. Transform IR handles
|
|
that _may_ be pointing to an erased payload IR object are _invalidated_. The
|
|
mere presence of an invalidated handle in the transform IR is not a problem, but
|
|
_using_ it results in undefined behavior. Invalidated handles can be thought of
|
|
as dangling pointers. Note that the _entire_ handle is invalidated, even if some
|
|
of the payload IR objects associated with it remain live.
|
|
|
|
The following handle invalidation rules apply.
|
|
|
|
* When an operation handle is consumed, are invalidated:
|
|
|
|
- operation handles associated with one of the payload operations that the
|
|
consumed handle is associated with;
|
|
|
|
- operation handles associated with one of the operations _nested_ in the
|
|
payload operations described above;
|
|
|
|
- value handles associated with any result of any operation described above;
|
|
|
|
- value handles associated with any argument of a block contained in a
|
|
region attached to any operation described above.
|
|
|
|
* When a value handle is consumed, are invalidated:
|
|
|
|
- operation handles associated with payload operations that produce as
|
|
result any value associated with the consumed handle (when the associated
|
|
is an operation result);
|
|
|
|
- operation handles associated with payload operations _nested_ in the
|
|
payload operations described above;
|
|
|
|
- operation handles associated with payload operations (recursively)
|
|
_contained_ in the block that defines as argument any value associated
|
|
with the consumed handle (when the associated value is a block argument);
|
|
note that the adjacent blocks are not affected;
|
|
|
|
- value handles associated with any result of any operation described above,
|
|
including all results of the operation defining as result the value
|
|
associated with the consumed handle;
|
|
|
|
- value handles associated with any argument of a block contained in a
|
|
region attached to any operation described above.
|
|
|
|
More intuitively, consuming a handle invalidates any handle that may be pointing
|
|
to an object defined or contained in the payload IR subtree rooted at the
|
|
closest operation or block.
|
|
|
|
The Transform dialect infrastructure has the capability of checking whether
|
|
the transform IR op operand is invalidated before applying the
|
|
transformation. However, such a check is computationally expensive and
|
|
must be enabled explicitly through `TransformOptions`. Additionally, the
|
|
`transform-dialect-check-uses` pass emits warnings when a handle may be used
|
|
after it has been consumed, but does so abstractly, without processing the
|
|
payload IR.
|
|
|
|
Values associated with parameters (non-handles) cannot be invalidated.
|
|
|
|
## Intended Use and Integrations
|
|
|
|
The transformation control infrastructure provided by this dialect is
|
|
positioned roughly between rewrite patterns and passes. A transformation
|
|
that is executed by a transform operation is likely to be sufficiently
|
|
complex to require at least a set of patterns to be implemented. It is also
|
|
expected to be more focused than a pass: a pass typically applies identical
|
|
transformations everywhere in the IR, a transform dialect-controlled
|
|
transformation would apply to a small subset of operations selected, e.g.,
|
|
by a pattern-matching operation or generated by a previous transformation.
|
|
It is discouraged, although technically possible, to run a pass pipeline as
|
|
part of the transform op implementation.
|
|
|
|
One of the main scenarios for using this dialect is fine-grain chaining of
|
|
transformations. For example, a loop-like operation may see its iteration
|
|
domain split into two parts, implemented as separate loops (transformation
|
|
known as index-set splitting), each of which is then transformed differently
|
|
(e.g., the first loop is tiled and the second unrolled) with the necessary
|
|
enabling and cleanup patterns around the main transformation:
|
|
|
|
```mlir
|
|
// <generate %loop, e.g., by pattern-matching>
|
|
// ...
|
|
%parts:2 = transform.loop.split %loop { upper_bound_divisible_by = 8 }
|
|
transform.loop.tile %parts#0 { tile_sizes = [8] }
|
|
transform.loop.unroll %parts#1 { full }
|
|
```
|
|
|
|
This composition would have been difficult to implement as separate passes
|
|
since the hypothetical "tiling" and "unrolling" pass would need to somehow
|
|
differentiate between the parts of the loop produced by the previous pass
|
|
(both are the same operation, and it is likely undesirable to pollute the
|
|
operation with pass-specific information). Implementing passes that run the
|
|
combined transformation would have run into the combinatorial explosion
|
|
issue due to multiple possible transform compositions or into the need for
|
|
deep pass parameterization, the ultimate form of which is an ad-hoc dialect
|
|
to specify which transformations the pass should run. The transform dialect
|
|
provides a uniform, extensible mechanism for controlling transformations in
|
|
such cases.
|
|
|
|
The Transform dialect is supposed to be consumed by an "interpreter" pass
|
|
that drives the application of transformations. To ensure extensibility and
|
|
composability, this pass is not expected to actually perform the
|
|
transformations specified by the ops. Instead, the transformations are
|
|
implemented by the transform ops themselves via `TransformOpInterface`. The
|
|
pass serves as the entry point, handles the flow of transform operations and
|
|
takes care of bookkeeping. As such, the Transform dialect does not provide
|
|
the interpreter pass. Instead, it provides a set of utilities that can be
|
|
used by clients to define their own interpreter passes or as part of a more
|
|
complex pass. For example, the mapping between values in the transform IR
|
|
and operations in the payload IR, or the function that applies the
|
|
transformations specified by ops in the given block sequentially. Note that
|
|
a transform op may have regions with further transform ops in them, with
|
|
the op itself guiding how to dispatch the transformation control flow to
|
|
those regions. This approach allows clients to decide on the relative
|
|
location of the transform IR in their input (e.g., nested modules, separate
|
|
modules, optional regions to certain operations, etc.), register additional
|
|
transform operations and perform client-specific bookkeeping.
|
|
|
|
## Effects on the Infrastructure
|
|
|
|
Although scoped to a single dialect, this functionality conceptually belongs
|
|
to the MLIR infrastructure. It aims to be minimally intrusive and opt-in.
|
|
|
|
Some infrastructural components may grow extra functionality to support the
|
|
transform dialect. In particular, the pattern infrastructure may add extra
|
|
hooks to identify the "main results" of a transformation or to notify
|
|
external observers about changes made to certain operations. These are not
|
|
expected to affect the existing uses of the infrastructure.
|
|
|
|
For the sake of reusability, transformations should be implemented as
|
|
utility functions that are called from the interface methods of transform
|
|
ops rather than having the methods directly act on the payload IR.
|
|
|
|
## Type Definitions
|
|
|
|
[include "Dialects/TransformTypes.md"]
|
|
|
|
## Core Operations
|
|
|
|
[include "Dialects/TransformOps.md"]
|
|
|
|
## Affine Transform Operations
|
|
|
|
[include "Dialects/AffineLoopTransformOps.md"]
|
|
|
|
## Bufferization Transform Operations
|
|
|
|
[include "Dialects/BufferizationTransformOps.md"]
|
|
|
|
## Debug Transform Operations
|
|
|
|
[include "Dialects/DebugExtensionOps.md"]
|
|
|
|
## Func Transform Operations
|
|
|
|
[include "Dialects/FuncTransformOps.md"]
|
|
|
|
## GPU Transform Operations
|
|
|
|
[include "Dialects/GPUTransformOps.md"]
|
|
|
|
## Loop (extension) Transform Operations
|
|
|
|
[include "Dialects/LoopExtensionOps.md"]
|
|
|
|
## Loop (SCF) Transform Operations
|
|
|
|
[include "Dialects/SCFLoopTransformOps.md"]
|
|
|
|
## MemRef Transform Operations
|
|
|
|
[include "Dialects/MemRefTransformOps.md"]
|
|
|
|
## PDL (extension) Transform Operations
|
|
|
|
[include "Dialects/PDLExtensionOps.md"]
|
|
|
|
## Structured (Linalg) Match Operations
|
|
|
|
[include "Dialects/LinalgStructuredMatchOps.md"]
|
|
|
|
## Structured (Linalg) Transform Operations
|
|
|
|
[include "Dialects/LinalgStructuredTransformOps.md"]
|
|
|
|
## Tensor Transform Operations
|
|
|
|
[include "Dialects/TensorTransformOps.md"]
|
|
|
|
## Vector Transform Operations
|
|
|
|
[include "Dialects/VectorTransformOps.md"]
|
|
|
|
[include "Dialects/TransformTypeInterfaces.md"]
|
|
|
|
[include "Dialects/TransformOpInterfaces.md"]
|