Skip to content

Commit 5a8d5a2

Browse files
committed
[mlir][Toy] Tidy up the first half of Chapter 2.
This performs a few rewordings, expands on a few parts, etc.
1 parent ee74860 commit 5a8d5a2

File tree

1 file changed

+92
-73
lines changed

1 file changed

+92
-73
lines changed

mlir/docs/Tutorials/Toy/Ch-2.md

Lines changed: 92 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pre-defined instructions (*operations* in MLIR terminology) or types.
2424

2525
## Interfacing with MLIR
2626

27-
[Language reference](../../LangRef.md)
27+
[Language Reference](../../LangRef.md)
2828

2929
MLIR is designed to be a completely extensible infrastructure; there is no
3030
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
115115

116116
### Opaque API
117117

118-
MLIR is designed to allow most IR elements, such as attributes,
119-
operations, and types, to be customized. At the same time, IR
120-
elements can always be reduced to the above fundamental concepts. This
121-
allows MLIR to parse, represent, and
122-
[round-trip](../../../getting_started/Glossary.md#round-trip) IR for
123-
*any* operation. For example, we could place our Toy operation from
124-
above into an `.mlir` file and round-trip through *mlir-opt* without
125-
registering any dialect:
118+
MLIR is designed to allow all IR elements, such as attributes, operations, and
119+
types, to be customized. At the same time, IR elements can always be reduced to
120+
the above fundamental concepts. This allows MLIR to parse, represent, and
121+
[round-trip](../../../getting_started/Glossary.md#round-trip) IR for *any*
122+
operation. For example, we could place our Toy operation from above into an
123+
`.mlir` file and round-trip through *mlir-opt* without registering any dialect:
126124

127125
```mlir
128126
func @toy_func(%tensor: tensor<2x3xf64>) -> tensor<3x2xf64> {
@@ -131,16 +129,15 @@ func @toy_func(%tensor: tensor<2x3xf64>) -> tensor<3x2xf64> {
131129
}
132130
```
133131

134-
In the cases of unregistered attributes, operations, and types, MLIR
135-
will enforce some structural constraints (SSA, block termination,
136-
etc.), but otherwise they are completely opaque. For instance, MLIR
137-
has little information about whether an unregistered operation can
138-
operate on particular datatypes, how many operands it can take, or how
139-
many results it produces. This flexibility can be useful for
140-
bootstrapping purposes, but it is generally advised against in mature
132+
In the cases of unregistered attributes, operations, and types, MLIR will
133+
enforce some structural constraints (e.g. dominance, etc.), but otherwise they
134+
are completely opaque. For instance, MLIR has little information about whether
135+
an unregistered operation can operate on particular data types, how many
136+
operands it can take, or how many results it produces. This flexibility can be
137+
useful for bootstrapping purposes, but it is generally advised against in mature
141138
systems. Unregistered operations must be treated conservatively by
142-
transformations and analyses, and they are much harder to construct
143-
and manipulate.
139+
transformations and analyses, and they are much harder to construct and
140+
manipulate.
144141

145142
This handling can be observed by crafting what should be an invalid IR for Toy
146143
and seeing it round-trip without tripping the verifier:
@@ -159,33 +156,34 @@ verifier, and add nicer APIs to manipulate our operations.
159156
## Defining a Toy Dialect
160157

161158
To effectively interface with MLIR, we will define a new Toy dialect. This
162-
dialect will model the structure of the Toy language, as well as
163-
provide an easy avenue for high-level analysis and transformation.
159+
dialect will model the structure of the Toy language, as well as provide an easy
160+
avenue for high-level analysis and transformation.
164161

165162
```c++
166163
/// This is the definition of the Toy dialect. A dialect inherits from
167-
/// mlir::Dialect and registers custom attributes, operations, and types (in its
168-
/// constructor). It can also override virtual methods to change some general
169-
/// behavior, which will be demonstrated in later chapters of the tutorial.
164+
/// mlir::Dialect and registers custom attributes, operations, and types. It can
165+
/// also override virtual methods to change some general behavior, which will be
166+
/// demonstrated in later chapters of the tutorial.
170167
class ToyDialect : public mlir::Dialect {
171168
public:
172169
explicit ToyDialect(mlir::MLIRContext *ctx);
173170

174-
/// Provide a utility accessor to the dialect namespace. This is used by
175-
/// several utilities.
171+
/// Provide a utility accessor to the dialect namespace.
176172
static llvm::StringRef getDialectNamespace() { return "toy"; }
177173

178174
/// An initializer called from the constructor of ToyDialect that is used to
179-
/// register operations, types, and more within the Toy dialect.
175+
/// register attributes, operations, types, and more within the Toy dialect.
180176
void initialize();
181177
};
182178
```
183179
184180
This is the C++ definition of a dialect, but MLIR also supports defining
185-
dialects declaratively via tablegen. Using the declarative specification is much
186-
cleaner as it removes the need for a large portion of the boilerplate when
187-
defining a new dialect. In the declarative format, the toy dialect would be
188-
specified as:
181+
dialects declaratively via
182+
[tablegen](https://llvm.org/docs/TableGen/ProgRef.html). Using the declarative
183+
specification is much cleaner as it removes the need for a large portion of the
184+
boilerplate when defining a new dialect. It also enables easy generation of
185+
dialect documentation, which can be described directly alongside the dialect. In
186+
this declarative format, the toy dialect would be specified as:
189187
190188
```tablegen
191189
// Provide a definition of the 'toy' dialect in the ODS framework so that we
@@ -195,6 +193,18 @@ def Toy_Dialect : Dialect {
195193
// provided in `ToyDialect::getDialectNamespace`.
196194
let name = "toy";
197195
196+
// A short one-line summary of our dialect.
197+
let summary = "A high-level dialect for analyzing and optimizing the "
198+
"Toy language";
199+
200+
// A much longer description of our dialect.
201+
let description = [{
202+
The Toy language is a tensor-based language that allows you to define
203+
functions, perform some math computation, and print results. This dialect
204+
provides a representation of the language that is amenable to analysis and
205+
optimization.
206+
}];
207+
198208
// The C++ namespace that the dialect class definition resides in.
199209
let cppNamespace = "toy";
200210
}
@@ -207,62 +217,74 @@ To see what this generates, we can run the `mlir-tblgen` command with the
207217
${build_root}/bin/mlir-tblgen -gen-dialect-decls ${mlir_src_root}/examples/toy/Ch2/include/toy/Ops.td -I ${mlir_src_root}/include/
208218
```
209219

210-
The dialect can now be loaded into an MLIRContext:
220+
After the dialect has been defined, it can now be loaded into an MLIRContext:
211221

212222
```c++
213223
context.loadDialect<ToyDialect>();
214224
```
215225

216-
Any new `MLIRContext` created from now on will contain an instance of the Toy
217-
dialect and invoke specific hooks for things like parsing attributes and types.
226+
By default, an `MLIRContext` only loads the
227+
[Builtin Dialect](../../Dialects/Builtin.md), which provides a few core IR
228+
components, meaning that other dialects, such as our `Toy` dialect, must be
229+
explicitly loaded.
218230

219231
## Defining Toy Operations
220232

221-
Now that we have a `Toy` dialect, we can start registering operations. This will
222-
allow for providing semantic information that the rest of the system can hook
223-
into. Let's walk through the creation of the `toy.constant` operation:
233+
Now that we have a `Toy` dialect, we can start defining the operations. This
234+
will allow for providing semantic information that the rest of the system can
235+
hook into. As an example, let's walk through the creation of a `toy.constant`
236+
operation. This operation will represent a constant value in the Toy language.
224237
225238
```mlir
226239
%4 = "toy.constant"() {value = dense<1.0> : tensor<2x3xf64>} : () -> tensor<2x3xf64>
227240
```
228241
229242
This operation takes zero operands, a
230243
[dense elements](../../LangRef.md#dense-elements-attribute) attribute named
231-
`value`, and returns a single result of
232-
[TensorType](../../LangRef.md#tensor-type). An operation inherits from the
244+
`value` to represent the constant value, and returns a single result of
245+
[TensorType](../../LangRef.md#tensor-type). An operation class inherits from the
233246
[CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)
234247
`mlir::Op` class which also takes some optional [*traits*](../../Traits.md) to
235-
customize its behavior. These traits may provide additional accessors,
236-
verification, etc.
248+
customize its behavior. `Traits` are a mechanism with which we can inject
249+
additional behavior into an Operation, such as additional accessors,
250+
verification, and more. Let's look below at a possible definition for the
251+
constant operation that we have described above:
237252

238253
```c++
239-
class ConstantOp : public mlir::Op<ConstantOp,
240-
/// The ConstantOp takes no inputs.
254+
class ConstantOp : public mlir::Op<
255+
/// `mlir::Op` is a CRTP class, meaning that we provide the
256+
/// derived class as a template parameter.
257+
ConstantOp,
258+
/// The ConstantOp takes zero input operands.
241259
mlir::OpTrait::ZeroOperands,
242260
/// The ConstantOp returns a single result.
243261
mlir::OpTrait::OneResult,
244-
/// The result of getType is `Type`.
245-
mlir::OpTraits::OneTypedResult<Type>::Impl> {
262+
/// We also provide a utility `getType` accessor that
263+
/// returns the TensorType of the single result.
264+
mlir::OpTraits::OneTypedResult<TensorType>::Impl> {
246265

247266
public:
248267
/// Inherit the constructors from the base Op class.
249268
using Op::Op;
250269

251270
/// Provide the unique name for this operation. MLIR will use this to register
252-
/// the operation and uniquely identify it throughout the system.
271+
/// the operation and uniquely identify it throughout the system. The name
272+
/// provided here must be prefixed by the parent dialect namespace followed
273+
/// by a `.`.
253274
static llvm::StringRef getOperationName() { return "toy.constant"; }
254275

255276
/// Return the value of the constant by fetching it from the attribute.
256277
mlir::DenseElementsAttr getValue();
257278

258-
/// Operations can provide additional verification beyond the traits they
259-
/// define. Here we will ensure that the specific invariants of the constant
260-
/// operation are upheld, for example the result type must be of TensorType.
279+
/// Operations may provide additional verification beyond what the attached
280+
/// traits provide. Here we will ensure that the specific invariants of the
281+
/// constant operation are upheld, for example the result type must be
282+
/// of TensorType and matches the type of the constant `value`.
261283
LogicalResult verify();
262284

263285
/// Provide an interface to build this operation from a set of input values.
264-
/// This interface is used by the builder to allow for easily generating
265-
/// instances of this operation:
286+
/// This interface is used by the `builder` classes to allow for easily
287+
/// generating instances of this operation:
266288
/// mlir::OpBuilder::create<ConstantOp>(...)
267289
/// This method populates the given `state` that MLIR uses to create
268290
/// operations. This state is a collection of all of the discrete elements
@@ -279,7 +301,7 @@ class ConstantOp : public mlir::Op<ConstantOp,
279301
};
280302
```
281303
282-
and we register this operation in the `ToyDialect` initializer:
304+
and we can register this operation in the `ToyDialect` initializer:
283305
284306
```c++
285307
void ToyDialect::initialize() {
@@ -289,28 +311,25 @@ void ToyDialect::initialize() {
289311

290312
### Op vs Operation: Using MLIR Operations
291313

292-
Now that we have defined an operation, we will want to access and
293-
transform it. In MLIR, there are two main classes related to
294-
operations: `Operation` and `Op`. The `Operation` class is used to
295-
generically model all operations. It is 'opaque', in the sense that
296-
it does not describe the properties of particular operations or types
297-
of operations. Instead, the 'Operation' class provides a general API
298-
into an operation instance. On the other hand, each specific type of
299-
operation is represented by an `Op` derived class. For instance
300-
`ConstantOp` represents a operation with zero inputs, and one output,
301-
which is always set to the same value. `Op` derived classes act as
302-
smart pointer wrapper around a `Operation*`, provide
303-
operation-specific accessor methods, and type-safe properties of
304-
operations. This means that when we define our Toy operations, we are
305-
simply defining a clean, semantically useful interface for building
306-
and interfacing with the `Operation` class. This is why our
307-
`ConstantOp` defines no class fields; all the data structures are
308-
stored in the referenced `Operation`. A side effect is that we always
309-
pass around `Op` derived classes by value, instead of by reference or
310-
pointer (*passing by value* is a common idiom and applies similarly to
311-
attributes, types, etc). Given a generic `Operation*` instance, we
312-
can always get a specific `Op` instance using LLVM's casting
313-
infrastructure:
314+
Now that we have defined an operation, we will want to access and transform it.
315+
In MLIR, there are two main classes related to operations: `Operation` and `Op`.
316+
The `Operation` class is used to generically model all operations. It is
317+
'opaque', in the sense that it does not describe the properties of particular
318+
operations or types of operations. Instead, the `Operation` class provides a
319+
general API into an operation instance. On the other hand, each specific type of
320+
operation is represented by an `Op` derived class. For instance `ConstantOp`
321+
represents a operation with zero inputs, and one output, which is always set to
322+
the same value. `Op` derived classes act as smart pointer wrapper around a
323+
`Operation*`, provide operation-specific accessor methods, and type-safe
324+
properties of operations. This means that when we define our Toy operations, we
325+
are simply defining a clean, semantically useful interface for building and
326+
interfacing with the `Operation` class. This is why our `ConstantOp` defines no
327+
class fields; all of the data for this operation is stored in the referenced
328+
`Operation`. A side effect of this design is that we always pass around `Op`
329+
derived classes "by-value", instead of by reference or pointer (*passing by
330+
value* is a common idiom in MLIR and applies similarly to attributes, types,
331+
etc). Given a generic `Operation*` instance, we can always get a specific `Op`
332+
instance using LLVM's casting infrastructure:
314333

315334
```c++
316335
void processConstantOp(mlir::Operation *operation) {

0 commit comments

Comments
 (0)