forked from OSchip/llvm-project
[MLIR][docs] Update tutorial language around Op and Operation* and 'opaque'
Differential Revision: https://reviews.llvm.org/D79146
This commit is contained in:
parent
b9d50bdff2
commit
2e628d008c
|
@ -1,12 +1,14 @@
|
|||
# Interfaces
|
||||
|
||||
MLIR is generic and very extensible; it allows for opaquely representing many
|
||||
different dialects that have their own operations, attributes, types, and so on.
|
||||
This allows for dialects to be very expressive in their semantics and for MLIR
|
||||
to capture many different levels of abstraction. The downside to this is that
|
||||
transformations and analyses must be extremely conservative about the operations
|
||||
that they encounter, and must special-case the different dialects that they
|
||||
support. To combat this, MLIR provides the concept of `interfaces`.
|
||||
MLIR is a generic and extensible framework, representing different
|
||||
dialects with their own operations, attributes, types, and so on.
|
||||
MLIR Dialects can express operations with a wide variety of semantics
|
||||
and different levels of abstraction. The downside to this is that MLIR
|
||||
transformations and analyses need to account for the semantics of
|
||||
every operation, or handle operations conservatively. Without care,
|
||||
this can result in code with special-cases for each supported
|
||||
operation type. To combat this, MLIR provides the concept of
|
||||
`interfaces`.
|
||||
|
||||
## Motivation
|
||||
|
||||
|
@ -19,8 +21,9 @@ transformations/analyses.
|
|||
|
||||
### Dialect Interfaces
|
||||
|
||||
Dialect interfaces are generally useful for transformation passes or analyses
|
||||
that want to opaquely operate on operations, even *across* dialects. These
|
||||
Dialect interfaces are generally useful for transformation passes or
|
||||
analyses that want to operate generically on a set of operations,
|
||||
which might even be defined in different dialects. These
|
||||
interfaces generally involve wide coverage over the entire dialect and are only
|
||||
used for a handful of transformations/analyses. In these cases, registering the
|
||||
interface directly on each operation is overly complex and cumbersome. The
|
||||
|
@ -68,8 +71,9 @@ AffineDialect::AffineDialect(MLIRContext *context) ... {
|
|||
}
|
||||
```
|
||||
|
||||
Once registered, these interfaces can be opaquely queried from the dialect by
|
||||
the transformation/analysis that wants to use them:
|
||||
Once registered, these interfaces can be queried from the dialect by
|
||||
the transformation/analysis that wants to use them, without
|
||||
determining the particular dialect subclass:
|
||||
|
||||
```c++
|
||||
Dialect *dialect = ...;
|
||||
|
@ -105,7 +109,7 @@ if(!interface.isLegalToInline(...))
|
|||
### Operation Interfaces
|
||||
|
||||
Operation interfaces, as the name suggests, are those registered at the
|
||||
Operation level. These interfaces provide an opaque view into derived operations
|
||||
Operation level. These interfaces provide access to derived operations
|
||||
by providing a virtual interface that must be implemented. As an example, the
|
||||
`Linalg` dialect may implement an interface that provides general queries about
|
||||
some of the dialects library operations. These queries may provide things like:
|
||||
|
|
|
@ -307,11 +307,11 @@ Example:
|
|||
module ::= `module` symbol-ref-id? (`attributes` attribute-dict)? region
|
||||
```
|
||||
|
||||
An MLIR module represents an opaque top-level container operation. It contains a
|
||||
single region containing a single block that is comprised of any operations.
|
||||
Operations within this region must not implicitly capture values defined above
|
||||
it. Modules have an optional symbol name that can be used to refer to them in
|
||||
operations.
|
||||
An MLIR Module represents a top-level container operation. It contains
|
||||
a single region containing a single block which can contain any
|
||||
operations. Operations within this region must not implicitly capture
|
||||
values defined outside the module. Modules have an optional symbol
|
||||
name that can be used to refer to them in operations.
|
||||
|
||||
### Functions
|
||||
|
||||
|
@ -509,16 +509,16 @@ where the control flow is transmitted next. It may, for example, enter a region
|
|||
of the same op, including the same region that returned the control flow.
|
||||
|
||||
The enclosing operation determines the way in which control is transmitted into
|
||||
the entry block of a Region. The successor to a region’s exit points may not
|
||||
the entry block of a Region. The successor to a Region’s exit points may not
|
||||
necessarily exist: for example a call to a function that does not return.
|
||||
Concurrent or asynchronous execution of regions is unspecified. Operations may
|
||||
Concurrent or asynchronous execution of Regions is unspecified. Operations may
|
||||
define specific rules of execution, e.g. sequential loops or switch cases.
|
||||
|
||||
A Region may also enter another region within the enclosing operation. If an
|
||||
operation has multiple regions, the semantics of the operation defines into
|
||||
which regions the control flows and in which order, if any. An operation may
|
||||
transmit control into regions that were specified in other operations, in
|
||||
particular those that defined the values the given operation uses. Thus such
|
||||
transmit control into Regions that were specified in other operations, in
|
||||
particular those that defined the values the given operation uses. Thus, such
|
||||
operations can be treated opaquely in the enclosing control flow graph,
|
||||
providing a level of control flow isolation similar to that of the call
|
||||
operation.
|
||||
|
@ -1465,10 +1465,11 @@ This attribute can only be held internally by
|
|||
attribute dictionary), i.e. no other attribute kinds such as Locations or
|
||||
extended attribute kinds.
|
||||
|
||||
**Rationale:** Given that MLIR models global accesses with symbol references, to
|
||||
enable efficient multi-threading, it becomes difficult to effectively reason
|
||||
about their uses. By restricting the places that can legally hold a symbol
|
||||
reference, we can always opaquely reason about a symbols usage characteristics.
|
||||
**Rationale:** Identifying accesses to global data is critical to
|
||||
enabling efficient multi-threaded compilation. Restricting global
|
||||
data access to occur through symbols and limiting the places that can
|
||||
legally hold a symbol reference simplifies reasoning about these data
|
||||
accesses.
|
||||
|
||||
See [`Symbols And SymbolTables`](SymbolsAndSymbolTables.md) for more
|
||||
information.
|
||||
|
|
|
@ -348,13 +348,14 @@ class. See [Constraints](#constraints) for more information.
|
|||
|
||||
### Operation interfaces
|
||||
|
||||
[Operation interfaces](Interfaces.md#operation-interfaces) are a mechanism by
|
||||
which to opaquely call methods and access information on an *Op instance*,
|
||||
without knowing the exact operation type. Operation interfaces defined in C++
|
||||
can be accessed in the ODS framework via the `OpInterfaceTrait` class. Aside
|
||||
from using pre-existing interfaces in the C++ API, the ODS framework also
|
||||
provides a simplified mechanism for defining such interfaces; that removes much
|
||||
of the boilerplate necessary.
|
||||
[Operation interfaces](Interfaces.md#operation-interfaces) allow
|
||||
operations to expose method calls without the
|
||||
caller needing to know the exact operation type. Operation interfaces
|
||||
defined in C++ can be accessed in the ODS framework via the
|
||||
`OpInterfaceTrait` class. Aside from using pre-existing interfaces in
|
||||
the C++ API, the ODS framework also provides a simplified mechanism
|
||||
for defining such interfaces which removes much of the boilerplate
|
||||
necessary.
|
||||
|
||||
Providing a definition of the `OpInterface` class will auto-generate the C++
|
||||
classes for the interface. An `OpInterface` includes a name, for the C++ class,
|
||||
|
|
|
@ -861,11 +861,12 @@ func @matmul(%A, %B, %C, %M, %N, %K) : (...) { // %M, N, K are symbols
|
|||
|
||||
### Affine Relations
|
||||
|
||||
The current MLIR spec includes affine maps and integer sets, but not affine
|
||||
relations. Affine relations are a natural way to model read and write access
|
||||
information, which can be very useful to capture the behavior of opaque external
|
||||
library calls, high-performance vendor libraries, or user-provided / user-tuned
|
||||
routines.
|
||||
The current MLIR spec includes affine maps and integer sets, but not
|
||||
affine relations. Affine relations are a natural way to model read and
|
||||
write access information, which can be very useful to capture the
|
||||
behavior of external library calls where no implementation is
|
||||
available, high-performance vendor libraries, or user-provided /
|
||||
user-tuned routines.
|
||||
|
||||
An affine relation is a relation between input and output dimension identifiers
|
||||
while being symbolic on a list of symbolic identifiers and with affine
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
MLIR allows for a truly open operation ecosystem, as any dialect may define
|
||||
operations that suit a specific level of abstraction. `Traits` are a mechanism
|
||||
in which to abstract implementation details and properties that are common
|
||||
which abstracts implementation details and properties that are common
|
||||
across many different operations. `Traits` may be used to specify special
|
||||
properties and constraints of the operation, including whether the operation has
|
||||
side effects or whether its output has the same type as the input. Some examples
|
||||
|
|
|
@ -83,10 +83,10 @@ Let's break down the anatomy of this MLIR operation:
|
|||
* This is the location in the source code from which this operation
|
||||
originated.
|
||||
|
||||
Shown here is the general form of an operation. As described above, the set of
|
||||
operations in MLIR is extensible. This means that the infrastructure must be
|
||||
able to opaquely reason about the structure of an operation. This is done by
|
||||
boiling down the composition of an operation into discrete pieces:
|
||||
Shown here is the general form of an operation. As described above,
|
||||
the set of operations in MLIR is extensible. Operations are modeled
|
||||
using a small set of concepts, enabling operations to be reasoned
|
||||
about and manipulated generically. These concepts are:
|
||||
|
||||
- A name for the operation.
|
||||
- A list of SSA operand values.
|
||||
|
@ -115,12 +115,14 @@ compiler passes - does not include locations in the output by default. The
|
|||
|
||||
### Opaque API
|
||||
|
||||
MLIR is designed to be a completely extensible system, and as such, the
|
||||
infrastructure has the capability to opaquely represent all of its core
|
||||
components: attributes, operations, types, etc. This allows MLIR to parse,
|
||||
represent, and [round-trip](../../../getting_started/Glossary.md#round-trip) any valid IR. For
|
||||
example, we could place our Toy operation from above into an `.mlir` file and
|
||||
round-trip through *mlir-opt* without registering any dialect:
|
||||
MLIR is designed to allow most IR elements, such as attributes,
|
||||
operations, and types, to be customized. At the same time, IR
|
||||
elements can always be reduced to the above fundmental concepts. This
|
||||
allows MLIR to parse, represent, and
|
||||
[round-trip](../../../getting_started/Glossary.md#round-trip) IR for
|
||||
*any* operation. For example, we could place our Toy operation from
|
||||
above into an `.mlir` file and round-trip through *mlir-opt* without
|
||||
registering any dialect:
|
||||
|
||||
```mlir
|
||||
func @toy_func(%tensor: tensor<2x3xf64>) -> tensor<3x2xf64> {
|
||||
|
@ -129,12 +131,16 @@ func @toy_func(%tensor: tensor<2x3xf64>) -> tensor<3x2xf64> {
|
|||
}
|
||||
```
|
||||
|
||||
In the cases of unregistered attributes, operations, and types, MLIR will
|
||||
enforce some structural constraints (SSA, block termination, etc.), but
|
||||
otherwise they are completely opaque. This can be useful for bootstrapping
|
||||
purposes, but it is generally advised against. Opaque operations must be treated
|
||||
conservatively by transformations and analyses, and they are much harder to
|
||||
construct and manipulate.
|
||||
In the cases of unregistered attributes, operations, and types, MLIR
|
||||
will enforce some structural constraints (SSA, block termination,
|
||||
etc.), but otherwise they are completely opaque. For instance, MLIR
|
||||
has little information about whether an unregisted operation can
|
||||
operate on particular datatypes, how many operands it can take, or how
|
||||
many results it produces. This flexibility can be useful for
|
||||
bootstrapping purposes, but it is generally advised against in mature
|
||||
systems. Unregistered operations must be treated conservatively by
|
||||
transformations and analyses, and they are much harder to construct
|
||||
and manipulate.
|
||||
|
||||
This handling can be observed by crafting what should be an invalid IR for Toy
|
||||
and seeing it round-trip without tripping the verifier:
|
||||
|
@ -155,7 +161,7 @@ verifier, and add nicer APIs to manipulate our operations.
|
|||
## Defining a Toy Dialect
|
||||
|
||||
To effectively interface with MLIR, we will define a new Toy dialect. This
|
||||
dialect will properly model the semantics of the Toy language, as well as
|
||||
dialect will model the structure of the Toy language, as well as
|
||||
provide an easy avenue for high-level analysis and transformation.
|
||||
|
||||
```c++
|
||||
|
@ -254,18 +260,28 @@ ToyDialect::ToyDialect(mlir::MLIRContext *ctx)
|
|||
|
||||
### Op vs Operation: Using MLIR Operations
|
||||
|
||||
Now that we have defined an operation, we will want to access and transform it.
|
||||
In MLIR, there are two main classes related to operations: `Operation` and `Op`.
|
||||
Operation is the actual opaque instance of the operation, and represents the
|
||||
general API into an operation instance. An `Op` is the base class of a derived
|
||||
operation, like `ConstantOp`, and acts as smart pointer wrapper around a
|
||||
`Operation*`. This means that when we define our Toy operations, we are actually
|
||||
providing a clean interface for building and interfacing with the `Operation`
|
||||
class; this is why our `ConstantOp` defines no class fields. Therefore, we
|
||||
always pass these classes around by value, instead of by reference or pointer
|
||||
(*passing by value* is a common idiom and applies similarly to attributes,
|
||||
types, etc). We can always get an instance of our toy operation by using LLVM's
|
||||
casting infrastructure:
|
||||
Now that we have defined an operation, we will want to access and
|
||||
transform it. In MLIR, there are two main classes related to
|
||||
operations: `Operation` and `Op`. The `Operation` class is used to
|
||||
generically model all operations. It is 'opaque', in the sense that
|
||||
it does not describe the properties of particular operations or types
|
||||
of operations. Instead, the 'Operation' class provides a general API
|
||||
into an operation instance. On the other hand, each specific type of
|
||||
operation is represented by an `Op` derived class. For instance
|
||||
`ConstantOp` represents a operation with zero inputs, and one output,
|
||||
which is always set to the same value. `Op` derived classes act as
|
||||
smart pointer wrapper around a `Operation*`, provide
|
||||
operation-specific accessor methods, and type-safe properties of
|
||||
operations. This means that when we define our Toy operations, we are
|
||||
simply defining a clean, semantically useful interface for building
|
||||
and interfacing with the `Operation` class. This is why our
|
||||
`ConstantOp` defines no class fields; all the data structures are
|
||||
stored in the referenced `Operation`. A side effect is that we always
|
||||
pass around `Op` derived classes by value, instead of by reference or
|
||||
pointer (*passing by value* is a common idiom and applies similarly to
|
||||
attributes, types, etc). Given a generic `Operation*` instance, we
|
||||
can always get a specific `Op` instance using LLVM's casting
|
||||
infrastructure:
|
||||
|
||||
```c++
|
||||
void processConstantOp(mlir::Operation *operation) {
|
||||
|
|
Loading…
Reference in New Issue