[Mlir-commits] [mlir] 5a8d5a2 - [mlir][Toy] Tidy up the first half of Chapter 2.
River Riddle
llvmlistbot at llvm.org
Wed Mar 17 17:43:01 PDT 2021
Author: River Riddle
Date: 2021-03-17T17:37:28-07:00
New Revision: 5a8d5a2859d9bb056083b343588a2d87622e76a2
URL: https://github.com/llvm/llvm-project/commit/5a8d5a2859d9bb056083b343588a2d87622e76a2
DIFF: https://github.com/llvm/llvm-project/commit/5a8d5a2859d9bb056083b343588a2d87622e76a2.diff
LOG: [mlir][Toy] Tidy up the first half of Chapter 2.
This performs a few rewordings, expands on a few parts, etc.
Added:
Modified:
mlir/docs/Tutorials/Toy/Ch-2.md
Removed:
################################################################################
diff --git a/mlir/docs/Tutorials/Toy/Ch-2.md b/mlir/docs/Tutorials/Toy/Ch-2.md
index db42021a19f32..8386804da8c62 100644
--- a/mlir/docs/Tutorials/Toy/Ch-2.md
+++ b/mlir/docs/Tutorials/Toy/Ch-2.md
@@ -24,7 +24,7 @@ pre-defined instructions (*operations* in MLIR terminology) or types.
## Interfacing with MLIR
-[Language reference](../../LangRef.md)
+[Language Reference](../../LangRef.md)
MLIR is designed to be a completely extensible infrastructure; there is no
closed set of attributes (think: constant metadata), operations, or types. MLIR
@@ -115,14 +115,12 @@ compiler passes - does not include locations in the output by default. The
### Opaque API
-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 fundamental 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 is designed to allow all IR elements, such as attributes, operations, and
+types, to be customized. At the same time, IR elements can always be reduced to
+the above fundamental 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> {
@@ -131,16 +129,15 @@ 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. For instance, MLIR
-has little information about whether an unregistered 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
+In the cases of unregistered attributes, operations, and types, MLIR will
+enforce some structural constraints (e.g. dominance, etc.), but otherwise they
+are completely opaque. For instance, MLIR has little information about whether
+an unregistered operation can operate on particular data types, 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.
+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:
@@ -159,33 +156,34 @@ 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 model the structure of the Toy language, as well as
-provide an easy avenue for high-level analysis and transformation.
+dialect will model the structure of the Toy language, as well as provide an easy
+avenue for high-level analysis and transformation.
```c++
/// This is the definition of the Toy dialect. A dialect inherits from
-/// mlir::Dialect and registers custom attributes, operations, and types (in its
-/// constructor). It can also override virtual methods to change some general
-/// behavior, which will be demonstrated in later chapters of the tutorial.
+/// mlir::Dialect and registers custom attributes, operations, and types. It can
+/// also override virtual methods to change some general behavior, which will be
+/// demonstrated in later chapters of the tutorial.
class ToyDialect : public mlir::Dialect {
public:
explicit ToyDialect(mlir::MLIRContext *ctx);
- /// Provide a utility accessor to the dialect namespace. This is used by
- /// several utilities.
+ /// Provide a utility accessor to the dialect namespace.
static llvm::StringRef getDialectNamespace() { return "toy"; }
/// An initializer called from the constructor of ToyDialect that is used to
- /// register operations, types, and more within the Toy dialect.
+ /// register attributes, operations, types, and more within the Toy dialect.
void initialize();
};
```
This is the C++ definition of a dialect, but MLIR also supports defining
-dialects declaratively via tablegen. Using the declarative specification is much
-cleaner as it removes the need for a large portion of the boilerplate when
-defining a new dialect. In the declarative format, the toy dialect would be
-specified as:
+dialects declaratively via
+[tablegen](https://llvm.org/docs/TableGen/ProgRef.html). Using the declarative
+specification is much cleaner as it removes the need for a large portion of the
+boilerplate when defining a new dialect. It also enables easy generation of
+dialect documentation, which can be described directly alongside the dialect. In
+this declarative format, the toy dialect would be specified as:
```tablegen
// Provide a definition of the 'toy' dialect in the ODS framework so that we
@@ -195,6 +193,18 @@ def Toy_Dialect : Dialect {
// provided in `ToyDialect::getDialectNamespace`.
let name = "toy";
+ // A short one-line summary of our dialect.
+ let summary = "A high-level dialect for analyzing and optimizing the "
+ "Toy language";
+
+ // A much longer description of our dialect.
+ let description = [{
+ The Toy language is a tensor-based language that allows you to define
+ functions, perform some math computation, and print results. This dialect
+ provides a representation of the language that is amenable to analysis and
+ optimization.
+ }];
+
// The C++ namespace that the dialect class definition resides in.
let cppNamespace = "toy";
}
@@ -207,20 +217,23 @@ To see what this generates, we can run the `mlir-tblgen` command with the
${build_root}/bin/mlir-tblgen -gen-dialect-decls ${mlir_src_root}/examples/toy/Ch2/include/toy/Ops.td -I ${mlir_src_root}/include/
```
-The dialect can now be loaded into an MLIRContext:
+After the dialect has been defined, it can now be loaded into an MLIRContext:
```c++
context.loadDialect<ToyDialect>();
```
-Any new `MLIRContext` created from now on will contain an instance of the Toy
-dialect and invoke specific hooks for things like parsing attributes and types.
+By default, an `MLIRContext` only loads the
+[Builtin Dialect](../../Dialects/Builtin.md), which provides a few core IR
+components, meaning that other dialects, such as our `Toy` dialect, must be
+explicitly loaded.
## Defining Toy Operations
-Now that we have a `Toy` dialect, we can start registering operations. This will
-allow for providing semantic information that the rest of the system can hook
-into. Let's walk through the creation of the `toy.constant` operation:
+Now that we have a `Toy` dialect, we can start defining the operations. This
+will allow for providing semantic information that the rest of the system can
+hook into. As an example, let's walk through the creation of a `toy.constant`
+operation. This operation will represent a constant value in the Toy language.
```mlir
%4 = "toy.constant"() {value = dense<1.0> : tensor<2x3xf64>} : () -> tensor<2x3xf64>
@@ -228,41 +241,50 @@ into. Let's walk through the creation of the `toy.constant` operation:
This operation takes zero operands, a
[dense elements](../../LangRef.md#dense-elements-attribute) attribute named
-`value`, and returns a single result of
-[TensorType](../../LangRef.md#tensor-type). An operation inherits from the
+`value` to represent the constant value, and returns a single result of
+[TensorType](../../LangRef.md#tensor-type). An operation class inherits from the
[CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)
`mlir::Op` class which also takes some optional [*traits*](../../Traits.md) to
-customize its behavior. These traits may provide additional accessors,
-verification, etc.
+customize its behavior. `Traits` are a mechanism with which we can inject
+additional behavior into an Operation, such as additional accessors,
+verification, and more. Let's look below at a possible definition for the
+constant operation that we have described above:
```c++
-class ConstantOp : public mlir::Op<ConstantOp,
- /// The ConstantOp takes no inputs.
+class ConstantOp : public mlir::Op<
+ /// `mlir::Op` is a CRTP class, meaning that we provide the
+ /// derived class as a template parameter.
+ ConstantOp,
+ /// The ConstantOp takes zero input operands.
mlir::OpTrait::ZeroOperands,
/// The ConstantOp returns a single result.
mlir::OpTrait::OneResult,
- /// The result of getType is `Type`.
- mlir::OpTraits::OneTypedResult<Type>::Impl> {
+ /// We also provide a utility `getType` accessor that
+ /// returns the TensorType of the single result.
+ mlir::OpTraits::OneTypedResult<TensorType>::Impl> {
public:
/// Inherit the constructors from the base Op class.
using Op::Op;
/// Provide the unique name for this operation. MLIR will use this to register
- /// the operation and uniquely identify it throughout the system.
+ /// the operation and uniquely identify it throughout the system. The name
+ /// provided here must be prefixed by the parent dialect namespace followed
+ /// by a `.`.
static llvm::StringRef getOperationName() { return "toy.constant"; }
/// Return the value of the constant by fetching it from the attribute.
mlir::DenseElementsAttr getValue();
- /// Operations can provide additional verification beyond the traits they
- /// define. Here we will ensure that the specific invariants of the constant
- /// operation are upheld, for example the result type must be of TensorType.
+ /// Operations may provide additional verification beyond what the attached
+ /// traits provide. Here we will ensure that the specific invariants of the
+ /// constant operation are upheld, for example the result type must be
+ /// of TensorType and matches the type of the constant `value`.
LogicalResult verify();
/// Provide an interface to build this operation from a set of input values.
- /// This interface is used by the builder to allow for easily generating
- /// instances of this operation:
+ /// This interface is used by the `builder` classes to allow for easily
+ /// generating instances of this operation:
/// mlir::OpBuilder::create<ConstantOp>(...)
/// This method populates the given `state` that MLIR uses to create
/// operations. This state is a collection of all of the discrete elements
@@ -279,7 +301,7 @@ class ConstantOp : public mlir::Op<ConstantOp,
};
```
-and we register this operation in the `ToyDialect` initializer:
+and we can register this operation in the `ToyDialect` initializer:
```c++
void ToyDialect::initialize() {
@@ -289,28 +311,25 @@ void ToyDialect::initialize() {
### 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`. 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:
+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 of the data for this operation is stored in the referenced
+`Operation`. A side effect of this design is that we always pass around `Op`
+derived classes "by-value", instead of by reference or pointer (*passing by
+value* is a common idiom in MLIR 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) {
More information about the Mlir-commits
mailing list