[MLIR][docs] Update tutorial language around Op and Operation* and 'opaque'

Differential Revision: https://reviews.llvm.org/D79146
This commit is contained in:
Stephen Neuendorffer 2020-04-29 16:27:36 -07:00
parent b9d50bdff2
commit 2e628d008c
6 changed files with 90 additions and 67 deletions

View File

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

View File

@ -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 regions exit points may not
the entry block of a Region. The successor to a Regions 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.

View File

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

View File

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

View File

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

View File

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