From 5a8d5a2859d9bb056083b343588a2d87622e76a2 Mon Sep 17 00:00:00 2001 From: River Riddle Date: Wed, 17 Mar 2021 17:36:42 -0700 Subject: [PATCH] [mlir][Toy] Tidy up the first half of Chapter 2. This performs a few rewordings, expands on a few parts, etc. --- mlir/docs/Tutorials/Toy/Ch-2.md | 165 ++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 73 deletions(-) diff --git a/mlir/docs/Tutorials/Toy/Ch-2.md b/mlir/docs/Tutorials/Toy/Ch-2.md index db42021a19f3..8386804da8c6 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(); ``` -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::Impl> { + /// We also provide a utility `getType` accessor that + /// returns the TensorType of the single result. + mlir::OpTraits::OneTypedResult::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(...) /// 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