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