forked from OSchip/llvm-project
[mlir][doc] move transform dialect docs to .md, NFC
The description of the Transform dialect has become quite lengthy to be kept as a Tablegen string literal. Move it to a proper Markdown file.
This commit is contained in:
parent
ad7aa09672
commit
209843e050
|
@ -1,7 +1,335 @@
|
|||
# Transform Dialect
|
||||
|
||||
Fine-grain transformation control dialect.
|
||||
|
||||
[TOC]
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**This dialect is actively developed and may change frequently.**
|
||||
|
||||
To decrease the maintenance burden and churn, please post a description of
|
||||
the intended use case on the MLIR forum. A few in-tree use cases are
|
||||
currently supported:
|
||||
|
||||
- high-level transformations on "structured ops" (i.e. ops that operate on
|
||||
chunks of data in a way that can be decomposed into operations on
|
||||
smaller chunks of data and control flow) in Linalg, Tensor and Vector
|
||||
dialects;
|
||||
- loop transformations in the SCF dialect.
|
||||
|
||||
## 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 operations 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:
|
||||
|
||||
```mlir
|
||||
%0 = transform.loop.find { size > 42 } : !transform.interface<tileable>
|
||||
%1:2 = transform.loop.tile %0 { tile_sizes = [2,3,4] }
|
||||
: (!transform.interface<tileable>)
|
||||
-> (!transform.op<loop>, !transform.op<loop>)
|
||||
transform.loop.unroll %1#1 : !transform.op<loop>
|
||||
```
|
||||
|
||||
The values used in the Transform dialect, also referred to as *handles*,
|
||||
correspond to (groups of) operations in the payload IR. In the example
|
||||
above, `%0` corresponds to the set of loops found in the payload IR that
|
||||
satisfy the condition, and `%1` correspond to groups of outer and inner
|
||||
loops, respectively, produced by the tiling transformation.
|
||||
|
||||
A transform 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. Most Transform IR ops support operand values that are mapped to
|
||||
multiple operations. They usually apply the respective transformation for
|
||||
every mapped op ("batched execution"). Deviations from this convention are
|
||||
described in the documentation of Transform IR ops.
|
||||
|
||||
The handle values have transform IR types. These types describe properties
|
||||
of payload IR operations associated with the value that are known to the
|
||||
transform dialect, for example, all associated payload operations implement
|
||||
a "TileableOp" interface, or have a specific "loop" kind. 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 IR 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,
|
||||
TransformOpInterface transform,
|
||||
const TransformOptions &options);
|
||||
```
|
||||
|
||||
that applies the transformations specified by the top-level `transform` to
|
||||
payload IR contained in `payloadRoot`.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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. A transform IR operation
|
||||
that consumes a handle automatically _invalidates_ all the other handles
|
||||
associated with the same payload IR operations, or with any of their
|
||||
descendants, as the consumed handle. Note that the _entire_ handle is
|
||||
invalidated, even if some of the payload IR operations associated with it
|
||||
or their ancestors were not associated with the consumed handle. Any use of
|
||||
the invalidated handle results in undefined behavior since the payload IR
|
||||
operations associated with it are likely to have been mutated or erased. The
|
||||
mere fact of the handle being invalidated does _not_ trigger undefined
|
||||
behavior, only its appearance as an operand does.
|
||||
|
||||
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.
|
||||
|
||||
## 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"]
|
||||
|
||||
## Bufferization Transform Operations
|
||||
|
|
|
@ -22,7 +22,7 @@ add_public_tablegen_target(MLIRTransformDialectEnumIncGen)
|
|||
add_dependencies(mlir-headers MLIRTransformDialectEnumIncGen)
|
||||
|
||||
add_mlir_dialect(TransformOps transform)
|
||||
add_mlir_doc(TransformOps TransformOps Dialects/ -gen-dialect-doc -dialect=transform)
|
||||
add_mlir_doc(TransformOps TransformOps Dialects/ -gen-op-doc -dialect=transform)
|
||||
|
||||
# Contrary to what the name claims, this only produces the _op_ interface.
|
||||
add_mlir_interface(TransformInterfaces)
|
||||
|
|
|
@ -13,331 +13,7 @@ include "mlir/IR/OpBase.td"
|
|||
|
||||
def Transform_Dialect : Dialect {
|
||||
let summary = "Fine-grain transformation control dialect";
|
||||
let description = [{
|
||||
## Disclaimer
|
||||
|
||||
**This dialect is actively developed and may change frequently.**
|
||||
|
||||
To decrease the maintenance burden and churn, please post a description of
|
||||
the intended use case on the MLIR forum. A few in-tree use cases are
|
||||
currently supported:
|
||||
|
||||
- high-level transformations on "structured ops" (i.e. ops that operate on
|
||||
chunks of data in a way that can be decomposed into operations on
|
||||
smaller chunks of data and control flow) in Linalg, Tensor and Vector
|
||||
dialects;
|
||||
- loop transformations in the SCF dialect.
|
||||
|
||||
## 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 operations 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:
|
||||
|
||||
```mlir
|
||||
%0 = transform.loop.find { size > 42 } : !transform.interface<tileable>
|
||||
%1:2 = transform.loop.tile %0 { tile_sizes = [2,3,4] }
|
||||
: (!transform.interface<tileable>)
|
||||
-> (!transform.op<loop>, !transform.op<loop>)
|
||||
transform.loop.unroll %1#1 : !transform.op<loop>
|
||||
```
|
||||
|
||||
The values used in the Transform dialect, also referred to as *handles*,
|
||||
correspond to (groups of) operations in the payload IR. In the example
|
||||
above, `%0` corresponds to the set of loops found in the payload IR that
|
||||
satisfy the condition, and `%1` correspond to groups of outer and inner
|
||||
loops, respectively, produced by the tiling transformation.
|
||||
|
||||
A transform 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. Most Transform IR ops support operand values that are mapped to
|
||||
multiple operations. They usually apply the respective transformation for
|
||||
every mapped op ("batched execution"). Deviations from this convention are
|
||||
described in the documentation of Transform IR ops.
|
||||
|
||||
The handle values have transform IR types. These types describe properties
|
||||
of payload IR operations associated with the value that are known to the
|
||||
transform dialect, for example, all associated payload operations implement
|
||||
a "TileableOp" interface, or have a specific "loop" kind. 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 IR 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,
|
||||
TransformOpInterface transform,
|
||||
const TransformOptions &options);
|
||||
```
|
||||
|
||||
that applies the transformations specified by the top-level `transform` to
|
||||
payload IR contained in `payloadRoot`.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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. A transform IR operation
|
||||
that consumes a handle automatically _invalidates_ all the other handles
|
||||
associated with the same payload IR operations, or with any of their
|
||||
descendants, as the consumed handle. Note that the _entire_ handle is
|
||||
invalidated, even if some of the payload IR operations associated with it
|
||||
or their ancestors were not associated with the consumed handle. Any use of
|
||||
the invalidated handle results in undefined behavior since the payload IR
|
||||
operations associated with it are likely to have been mutated or erased. The
|
||||
mere fact of the handle being invalidated does _not_ trigger undefined
|
||||
behavior, only its appearance as an operand does.
|
||||
|
||||
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.
|
||||
|
||||
## 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"]
|
||||
}];
|
||||
// For description, see docs/Dialects/Transform.md.
|
||||
|
||||
let name = "transform";
|
||||
let cppNamespace = "::mlir::transform";
|
||||
|
|
Loading…
Reference in New Issue