[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:
Alex Zinenko 2022-10-12 11:02:01 +00:00
parent ad7aa09672
commit 209843e050
3 changed files with 330 additions and 326 deletions

View File

@ -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

View File

@ -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)

View File

@ -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";