[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 # 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:

View File

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

View File

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

View File

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

View File

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

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