diff --git a/mlir/docs/Interfaces.md b/mlir/docs/Interfaces.md index c5800ca2171a..d1050e76de5b 100644 --- a/mlir/docs/Interfaces.md +++ b/mlir/docs/Interfaces.md @@ -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: diff --git a/mlir/docs/LangRef.md b/mlir/docs/LangRef.md index c001b0f3cf22..b3ee0c958c32 100644 --- a/mlir/docs/LangRef.md +++ b/mlir/docs/LangRef.md @@ -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. diff --git a/mlir/docs/OpDefinitions.md b/mlir/docs/OpDefinitions.md index 8c785f65ed97..ddabae2225e7 100644 --- a/mlir/docs/OpDefinitions.md +++ b/mlir/docs/OpDefinitions.md @@ -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, diff --git a/mlir/docs/Rationale/Rationale.md b/mlir/docs/Rationale/Rationale.md index 8d1a9023628c..a3c3e5ecc4bf 100644 --- a/mlir/docs/Rationale/Rationale.md +++ b/mlir/docs/Rationale/Rationale.md @@ -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 diff --git a/mlir/docs/Traits.md b/mlir/docs/Traits.md index 0281185abf91..f55d0a8250f6 100644 --- a/mlir/docs/Traits.md +++ b/mlir/docs/Traits.md @@ -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 diff --git a/mlir/docs/Tutorials/Toy/Ch-2.md b/mlir/docs/Tutorials/Toy/Ch-2.md index 71450c2397b7..796b489a6256 100755 --- a/mlir/docs/Tutorials/Toy/Ch-2.md +++ b/mlir/docs/Tutorials/Toy/Ch-2.md @@ -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) {