@@ -24,7 +24,7 @@ pre-defined instructions (*operations* in MLIR terminology) or types.
24
24
25
25
## Interfacing with MLIR
26
26
27
- [ Language reference ] ( ../../LangRef.md )
27
+ [ Language Reference ] ( ../../LangRef.md )
28
28
29
29
MLIR is designed to be a completely extensible infrastructure; there is no
30
30
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
115
115
116
116
### Opaque API
117
117
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:
126
124
127
125
``` mlir
128
126
func @toy_func(%tensor: tensor<2x3xf64>) -> tensor<3x2xf64> {
@@ -131,16 +129,15 @@ func @toy_func(%tensor: tensor<2x3xf64>) -> tensor<3x2xf64> {
131
129
}
132
130
```
133
131
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
141
138
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.
144
141
145
142
This handling can be observed by crafting what should be an invalid IR for Toy
146
143
and seeing it round-trip without tripping the verifier:
@@ -159,33 +156,34 @@ verifier, and add nicer APIs to manipulate our operations.
159
156
## Defining a Toy Dialect
160
157
161
158
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.
164
161
165
162
``` c++
166
163
// / 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.
170
167
class ToyDialect : public mlir ::Dialect {
171
168
public:
172
169
explicit ToyDialect(mlir::MLIRContext * ctx);
173
170
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.
176
172
static llvm::StringRef getDialectNamespace() { return "toy"; }
177
173
178
174
/// 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.
180
176
void initialize();
181
177
};
182
178
```
183
179
184
180
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:
189
187
190
188
```tablegen
191
189
// Provide a definition of the 'toy' dialect in the ODS framework so that we
@@ -195,6 +193,18 @@ def Toy_Dialect : Dialect {
195
193
// provided in `ToyDialect::getDialectNamespace`.
196
194
let name = "toy";
197
195
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
+
198
208
// The C++ namespace that the dialect class definition resides in.
199
209
let cppNamespace = "toy";
200
210
}
@@ -207,62 +217,74 @@ To see what this generates, we can run the `mlir-tblgen` command with the
207
217
${build_root} /bin/mlir-tblgen -gen-dialect-decls ${mlir_src_root} /examples/toy/Ch2/include/toy/Ops.td -I ${mlir_src_root} /include/
208
218
```
209
219
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:
211
221
212
222
``` c++
213
223
context.loadDialect<ToyDialect>();
214
224
```
215
225
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.
218
230
219
231
## Defining Toy Operations
220
232
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.
224
237
225
238
```mlir
226
239
%4 = "toy.constant"() {value = dense<1.0> : tensor<2x3xf64>} : () -> tensor<2x3xf64>
227
240
```
228
241
229
242
This operation takes zero operands, a
230
243
[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
233
246
[CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)
234
247
`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:
237
252
238
253
```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.
241
259
mlir::OpTrait::ZeroOperands,
242
260
/// The ConstantOp returns a single result.
243
261
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> {
246
265
247
266
public:
248
267
/// Inherit the constructors from the base Op class.
249
268
using Op::Op;
250
269
251
270
/// 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 ` . ` .
253
274
static llvm::StringRef getOperationName() { return "toy.constant"; }
254
275
255
276
/// Return the value of the constant by fetching it from the attribute.
256
277
mlir::DenseElementsAttr getValue();
257
278
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 ` .
261
283
LogicalResult verify();
262
284
263
285
/// 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:
266
288
/// mlir::OpBuilder::create<ConstantOp >(...)
267
289
/// This method populates the given ` state ` that MLIR uses to create
268
290
/// operations. This state is a collection of all of the discrete elements
@@ -279,7 +301,7 @@ class ConstantOp : public mlir::Op<ConstantOp,
279
301
};
280
302
```
281
303
282
- and we register this operation in the `ToyDialect` initializer:
304
+ and we can register this operation in the `ToyDialect` initializer:
283
305
284
306
```c++
285
307
void ToyDialect::initialize() {
@@ -289,28 +311,25 @@ void ToyDialect::initialize() {
289
311
290
312
### Op vs Operation: Using MLIR Operations
291
313
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:
314
333
315
334
``` c++
316
335
void processConstantOp (mlir::Operation * operation) {
0 commit comments