xref: /llvm-project/mlir/docs/DefiningDialects/_index.md (revision 02f42a716c79fceee289e347e0d6e5fa581dc35e)
1# Defining Dialects
2
3This document describes how to define [Dialects](../LangRef.md/#dialects).
4
5[TOC]
6
7## LangRef Refresher
8
9Before diving into how to define these constructs, below is a quick refresher
10from the [MLIR LangRef](../LangRef.md).
11
12Dialects are the mechanism by which to engage with and extend the MLIR
13ecosystem. They allow for defining new [attributes](../LangRef.md/#attributes),
14[operations](../LangRef.md/#operations), and [types](../LangRef.md/#type-system).
15Dialects are used to model a variety of different abstractions; from traditional
16[arithmetic](../Dialects/ArithOps.md) to
17[pattern rewrites](../Dialects/PDLOps.md); and is one of the most fundamental
18aspects of MLIR.
19
20## Defining a Dialect
21
22At the most fundamental level, defining a dialect in MLIR is as simple as
23specializing the
24[C++ `Dialect` class](https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/IR/Dialect.h).
25That being said, MLIR provides a powerful declaratively specification mechanism via
26[TableGen](https://llvm.org/docs/TableGen/index.html); a generic language with
27tooling to maintain records of domain-specific information; that simplifies the
28definition process by automatically generating all of the necessary boilerplate
29C++ code, significantly reduces maintainence burden when changing aspects of dialect
30definitions, and also provides additional tools on top (such as
31documentation generation). Given the above, the declarative specification is the
32expected mechanism for defining new dialects, and is the method detailed within
33this document. Before continuing, it is highly recommended that users review the
34[TableGen Programmer's Reference](https://llvm.org/docs/TableGen/ProgRef.html)
35for an introduction to its syntax and constructs.
36
37Below showcases an example simple Dialect definition. We generally recommend defining
38the Dialect class in a different `.td` file from the attributes, operations, types,
39and other sub-components of the dialect to establish a proper layering between
40the various different dialect components. It also prevents situations where you may
41inadvertantly generate multiple definitions for some constructs. This recommendation
42extends to all of the MLIR constructs, including [Interfaces](../Interfaces.md) for example.
43
44```tablegen
45// Include the definition of the necessary tablegen constructs for defining
46// our dialect.
47include "mlir/IR/DialectBase.td"
48
49// Here is a simple definition of a dialect.
50def MyDialect : Dialect {
51  let summary = "A short one line description of my dialect.";
52  let description = [{
53    My dialect is a very important dialect. This section contains a much more
54    detailed description that documents all of the important pieces of information
55    to know about the document.
56  }];
57
58  /// This is the namespace of the dialect. It is used to encapsulate the sub-components
59  /// of the dialect, such as operations ("my_dialect.foo").
60  let name = "my_dialect";
61
62  /// The C++ namespace that the dialect, and its sub-components, get placed in.
63  let cppNamespace = "::my_dialect";
64}
65```
66
67The above showcases a very simple description of a dialect, but dialects have lots
68of other capabilities that you may or may not need to utilize.
69
70### Initialization
71
72Every dialect must implement an initialization hook to add attributes, operations, types,
73attach any desired interfaces, or perform any other necessary initialization for the
74dialect that should happen on construction. This hook is declared for every dialect to
75define, and has the form:
76
77```c++
78void MyDialect::initialize() {
79  // Dialect initialization logic should be defined in here.
80}
81```
82
83### Documentation
84
85The `summary` and `description` fields allow for providing user documentation
86for the dialect. The `summary` field expects a simple single-line string, with the
87`description` field used for long and extensive documentation. This documentation can be
88used to generate markdown documentation for the dialect and is used by upstream
89[MLIR dialects](https://mlir.llvm.org/docs/Dialects/).
90
91### Class Name
92
93The name of the C++ class which gets generated is the same as the name of our TableGen
94dialect definition, but with any `_` characters stripped out. This means that if you name
95your dialect `Foo_Dialect`, the generated C++ class would be `FooDialect`. In the example
96above, we would get a C++ dialect named `MyDialect`.
97
98### C++ Namespace
99
100The namespace that the C++ class for our dialect, and all of its sub-components, is placed
101under is specified by the `cppNamespace` field. By default, uses the name of the dialect as
102the only namespace. To avoid placing in any namespace, use `""`. To specify nested namespaces,
103use `"::"` as the delimiter between namespace, e.g., given `"A::B"`, C++ classes will be placed
104within: `namespace A { namespace B { <classes> } }`.
105
106Note that this works in conjunction with the dialect's C++ code. Depending on how the generated files
107are included, you may want to specify a full namespace path or a partial one. In general, it's best
108to use full namespaces whenever you can. This makes it easier for dialects within different namespaces,
109and projects, to interact with each other.
110
111### C++ Accessor Generation
112
113When generating accessors for dialects and their components (attributes, operations, types, etc.),
114we prefix the name with `get` and `set` respectively, and transform `snake_style` names to camel
115case (`UpperCamel` when prefixed, and `lowerCamel` for individual variable names). For example, if an
116operation were defined as:
117
118```tablegen
119def MyOp : MyDialect<"op"> {
120  let arguments = (ins StrAttr:$value, StrAttr:$other_value);
121}
122```
123
124It would have accessors generated for the `value` and `other_value` attributes as follows:
125
126```c++
127StringAttr MyOp::getValue();
128void MyOp::setValue(StringAttr newValue);
129
130StringAttr MyOp::getOtherValue();
131void MyOp::setOtherValue(StringAttr newValue);
132```
133
134### Dependent Dialects
135
136MLIR has a very large ecosystem, and contains dialects that serve many different purposes. It
137is quite common, given the above, that dialects may want to reuse certain components from other
138dialects. This may mean generating operations from those dialects during canonicalization, reusing
139attributes or types, etc. When a dialect has a dependency on another, i.e. when it constructs and/or
140generally relies on the components of another dialect, a dialect dependency should be explicitly
141recorded. An explicitly dependency ensures that dependent dialects are loaded alongside the
142dialect. Dialect dependencies can be recorded using the `dependentDialects` dialects field:
143
144```tablegen
145def MyDialect : Dialect {
146  // Here we register the Arithmetic and Func dialect as dependencies of our `MyDialect`.
147  let dependentDialects = [
148    "arith::ArithDialect",
149    "func::FuncDialect"
150  ];
151}
152```
153
154### Extra declarations
155
156The declarative Dialect definitions try to auto-generate as much logic and methods
157as possible. With that said, there will always be long-tail cases that won't be covered.
158For such cases, `extraClassDeclaration` can be used. Code within the `extraClassDeclaration`
159field will be copied literally to the generated C++ Dialect class.
160
161Note that `extraClassDeclaration` is a mechanism intended for long-tail cases by
162power users; for not-yet-implemented widely-applicable cases, improving the
163infrastructure is preferable.
164
165### `hasConstantMaterializer`: Materializing Constants from Attributes
166
167This field is utilized to materialize a constant operation from an `Attribute` value and
168a `Type`. This is generally used when an operation within this dialect has been folded,
169and a constant operation should be generated. `hasConstantMaterializer` is used to enable
170materialization, and the `materializeConstant` hook is declared on the dialect. This
171hook takes in an `Attribute` value, generally returned by `fold`, and produces a
172"constant-like" operation that materializes that value. See the
173[documentation for canonicalization](../Canonicalization.md) for a more in-depth
174introduction to `folding` in MLIR.
175
176Constant materialization logic can then be defined in the source file:
177
178```c++
179/// Hook to materialize a single constant operation from a given attribute value
180/// with the desired resultant type. This method should use the provided builder
181/// to create the operation without changing the insertion position. The
182/// generated operation is expected to be constant-like. On success, this hook
183/// should return the operation generated to represent the constant value.
184/// Otherwise, it should return nullptr on failure.
185Operation *MyDialect::materializeConstant(OpBuilder &builder, Attribute value,
186                                          Type type, Location loc) {
187  ...
188}
189```
190
191### `hasNonDefaultDestructor`: Providing a custom destructor
192
193This field should be used when the Dialect class has a custom destructor, i.e.
194when the dialect has some special logic to be run in the `~MyDialect`. In this case,
195only the declaration of the destructor is generated for the Dialect class.
196
197### Discardable Attribute Verification
198
199As described by the [MLIR Language Reference](../LangRef.md/#attributes),
200*discardable attribute* are a type of attribute that has its semantics defined
201by the dialect whose name prefixes that of the attribute. For example, if an
202operation has an attribute named `gpu.contained_module`, the `gpu` dialect
203defines the semantics and invariants, such as when and where it is valid to use,
204of that attribute. To hook into this verification for attributes that are prefixed
205by our dialect, several hooks on the Dialect may be used:
206
207#### `hasOperationAttrVerify`
208
209This field generates the hook for verifying when a discardable attribute of this dialect
210has been used within the attribute dictionary of an operation. This hook has the form:
211
212```c++
213/// Verify the use of the given attribute, whose name is prefixed by the namespace of this
214/// dialect, that was used in `op`s dictionary.
215LogicalResult MyDialect::verifyOperationAttribute(Operation *op, NamedAttribute attribute);
216```
217
218#### `hasRegionArgAttrVerify`
219
220This field generates the hook for verifying when a discardable attribute of this dialect
221has been used within the attribute dictionary of a region entry block argument. Note that
222the block arguments of a region entry block do not themselves have attribute dictionaries,
223but some operations may provide special dictionary attributes that correspond to the arguments
224of a region. For example, operations that implement `FunctionOpInterface` may have attribute
225dictionaries on the operation that correspond to the arguments of entry block of the function.
226In these cases, those operations will invoke this hook on the dialect to ensure the attribute
227is verified. The hook necessary for the dialect to implement has the form:
228
229```c++
230/// Verify the use of the given attribute, whose name is prefixed by the namespace of this
231/// dialect, that was used on the attribute dictionary of a region entry block argument.
232/// Note: As described above, when a region entry block has a dictionary is up to the individual
233/// operation to define.
234LogicalResult MyDialect::verifyRegionArgAttribute(Operation *op, unsigned regionIndex,
235                                                  unsigned argIndex, NamedAttribute attribute);
236```
237
238#### `hasRegionResultAttrVerify`
239
240This field generates the hook for verifying when a discardable attribute of this dialect
241has been used within the attribute dictionary of a region result. Note that the results of a
242region do not themselves have attribute dictionaries, but some operations may provide special
243dictionary attributes that correspond to the results of a region. For example, operations that
244implement `FunctionOpInterface` may have attribute dictionaries on the operation that correspond
245to the results of the function. In these cases, those operations will invoke this hook on the
246dialect to ensure the attribute is verified. The hook necessary for the dialect to implement
247has the form:
248
249```c++
250/// Generate verification for the given attribute, whose name is prefixed by the namespace
251/// of this dialect, that was used on the attribute dictionary of a region result.
252/// Note: As described above, when a region entry block has a dictionary is up to the individual
253/// operation to define.
254LogicalResult MyDialect::verifyRegionResultAttribute(Operation *op, unsigned regionIndex,
255                                                     unsigned argIndex, NamedAttribute attribute);
256```
257
258### Operation Interface Fallback
259
260Some dialects have an open ecosystem and don't register all of the possible operations. In such
261cases it is still possible to provide support for implementing an `OpInterface` for these
262operations. When an operation isn't registered or does not provide an implementation for an
263interface, the query will fallback to the dialect itself. The `hasOperationInterfaceFallback`
264field may be used to declare this fallback for operations:
265
266```c++
267/// Return an interface model for the interface with the given `typeId` for the operation
268/// with the given name.
269void *MyDialect::getRegisteredInterfaceForOp(TypeID typeID, StringAttr opName);
270```
271
272For a more detail description of the expected usages of this hook, view the detailed
273[interface documentation](../Interfaces.md/#dialect-fallback-for-opinterface).
274
275### Default Attribute/Type Parsers and Printers
276
277When a dialect registers an Attribute or Type, it must also override the respective
278`Dialect::parseAttribute`/`Dialect::printAttribute` or
279`Dialect::parseType`/`Dialect::printType` methods. In these cases, the dialect must
280explicitly handle the parsing and printing of each individual attribute or type within
281the dialect. If all of the attributes and types of the dialect provide a mnemonic,
282however, these methods may be autogenerated by using the
283`useDefaultAttributePrinterParser` and `useDefaultTypePrinterParser` fields. By default,
284these fields are set to `1`(enabled), meaning that if a dialect needs to explicitly handle the
285parser and printer of its Attributes and Types it should set these to `0` as necessary.
286
287### Dialect-wide Canonicalization Patterns
288
289Generally, [canonicalization](../Canonicalization.md) patterns are specific to individual
290operations within a dialect. There are some cases, however, that prompt canonicalization
291patterns to be added to the dialect-level. For example, if a dialect defines a canonicalization
292pattern that operates on an interface or trait, it can be beneficial to only add this pattern
293once, instead of duplicating per-operation that implements that interface. To enable the
294generation of this hook, the `hasCanonicalizer` field may be used. This will declare
295the `getCanonicalizationPatterns` method on the dialect, which has the form:
296
297```c++
298/// Return the canonicalization patterns for this dialect:
299void MyDialect::getCanonicalizationPatterns(RewritePatternSet &results) const;
300```
301
302See the documentation for [Canonicalization in MLIR](../Canonicalization.md) for
303a more detailed description about canonicalization patterns.
304
305### Defining bytecode format for dialect attributes and types
306
307By default bytecode serialization of dialect attributes and types uses the
308regular textual format. Dialects can define a more compact bytecode format for
309the attributes and types in dialect by defining & attaching
310`BytecodeDialectInterface` to the dialect. Basic support for generating
311readers/writers for the bytecode dialect interface can be generated using ODS's
312`-gen-bytecode`. The rest of the section will show an example.
313
314One can define the printing and parsing for a type in dialect `Foo` as follow:
315
316```td
317include "mlir/IR/BytecodeBase.td"
318
319let cType = "MemRefType" in {
320// Written in pseudo code showing the lowered encoding:
321//   ///   MemRefType {
322//   ///     shape: svarint[],
323//   ///     elementType: Type,
324//   ///     layout: Attribute
325//   ///   }
326//   ///
327// and the enum value:
328//   kMemRefType = 1,
329//
330// The corresponding definition in the ODS generator:
331def MemRefType : DialectType<(type
332  Array<SignedVarInt>:$shape,
333  Type:$elementType,
334  MemRefLayout:$layout
335)> {
336  let printerPredicate = "!$_val.getMemorySpace()";
337}
338
339//   ///   MemRefTypeWithMemSpace {
340//   ///     memorySpace: Attribute,
341//   ///     shape: svarint[],
342//   ///     elementType: Type,
343//   ///     layout: Attribute
344//   ///   }
345//   /// Variant of MemRefType with non-default memory space.
346//   kMemRefTypeWithMemSpace = 2,
347def MemRefTypeWithMemSpace : DialectType<(type
348  Attribute:$memorySpace,
349  Array<SignedVarInt>:$shape,
350  Type:$elementType,
351  MemRefLayout:$layout
352)> {
353  let printerPredicate = "!!$_val.getMemorySpace()";
354  // Note: order of serialization does not match order of builder.
355  let cBuilder = "get<$_resultType>(context, shape, elementType, layout, memorySpace)";
356}
357}
358
359def FooDialectTypes : DialectTypes<"Foo"> {
360  let elems = [
361    ReservedOrDead,         // assigned index 0
362    MemRefType,             // assigned index 1
363    MemRefTypeWithMemSpace, // assigned index 2
364    ...
365  ];
366}
367...
368```
369
370Here we have:
371
372*   An outer most `cType` as we are representing encoding one C++ type using two
373    different variants.
374*   The different `DialectType` instances are differentiated in printing by the
375    printer predicate while parsing the different variant is already encoded and
376    different builder functions invoked.
377*   Custom `cBuilder` is specified as the way its laid out on disk in the
378    bytecode doesn't match the order of arguments to the build methods of the
379    type.
380*   Many of the common dialect bytecode reading and writing atoms (such as
381    `VarInt`, `SVarInt`, `Blob`) are defined in `BytecodeBase` while one can
382    also define custom forms or combine via `CompositeBytecode` instances.
383*   `ReservedOrDead` is a special keyword to indicate a skipped enum instance
384    for which no read/write or dispatch code is generated.
385*   `Array` is a helper method for which during printing a list is serialized
386    (e.g., a varint of number of items followed by said number of items) or
387    parsed.
388
389The generated code consists of a four standalone methods with which the
390following interface can define the bytecode dialect interface:
391
392```c++
393#include "mlir/Dialect/Foo/FooDialectBytecode.cpp.inc"
394
395struct FooDialectBytecodeInterface : public BytecodeDialectInterface {
396  FooDialectBytecodeInterface(Dialect *dialect)
397      : BytecodeDialectInterface(dialect) {}
398
399  //===--------------------------------------------------------------------===//
400  // Attributes
401
402  Attribute readAttribute(DialectBytecodeReader &reader) const override {
403    return ::readAttribute(getContext(), reader);
404  }
405
406  LogicalResult writeAttribute(Attribute attr,
407                               DialectBytecodeWriter &writer) const override {
408    return ::writeAttribute(attr, writer);
409  }
410
411  //===--------------------------------------------------------------------===//
412  // Types
413
414  Type readType(DialectBytecodeReader &reader) const override {
415    return ::readType(getContext(), reader);
416  }
417
418  LogicalResult writeType(Type type,
419                          DialectBytecodeWriter &writer) const override {
420    return ::writeType(type, writer);
421  }
422};
423```
424
425along with defining the corresponding build rules to invoke generator
426(`-gen-bytecode -bytecode-dialect="Quant"`).
427
428## Defining an Extensible dialect
429
430This section documents the design and API of the extensible dialects. Extensible
431dialects are dialects that can be extended with new operations and types defined
432at runtime. This allows for users to define dialects via with meta-programming,
433or from another language, without having to recompile C++ code.
434
435### Defining an extensible dialect
436
437Dialects defined in C++ can be extended with new operations, types, etc., at
438runtime by inheriting from `mlir::ExtensibleDialect` instead of `mlir::Dialect`
439(note that `ExtensibleDialect` inherits from `Dialect`). The `ExtensibleDialect`
440class contains the necessary fields and methods to extend the dialect at
441runtime.
442
443```c++
444class MyDialect : public mlir::ExtensibleDialect {
445    ...
446}
447```
448
449For dialects defined in TableGen, this is done by setting the `isExtensible`
450flag to `1`.
451
452```tablegen
453def Test_Dialect : Dialect {
454  let isExtensible = 1;
455  ...
456}
457```
458
459An extensible `Dialect` can be casted back to `ExtensibleDialect` using
460`llvm::dyn_cast`, or `llvm::cast`:
461
462```c++
463if (auto extensibleDialect = llvm::dyn_cast<ExtensibleDialect>(dialect)) {
464    ...
465}
466```
467
468### Defining a dynamic dialect
469
470Dynamic dialects are extensible dialects that can be defined at runtime. They
471are only populated with dynamic operations, types, and attributes. They can be
472registered in a `DialectRegistry` with `insertDynamic`.
473
474```c++
475auto populateDialect = [](MLIRContext *ctx, DynamicDialect* dialect) {
476  // Code that will be ran when the dynamic dialect is created and loaded.
477  // For instance, this is where we register the dynamic operations, types, and
478  // attributes of the dialect.
479  ...
480}
481
482registry.insertDynamic("dialectName", populateDialect);
483```
484
485Once a dynamic dialect is registered in the `MLIRContext`, it can be retrieved
486with `getOrLoadDialect`.
487
488```c++
489Dialect *dialect = ctx->getOrLoadDialect("dialectName");
490```
491
492### Defining an operation at runtime
493
494The `DynamicOpDefinition` class represents the definition of an operation
495defined at runtime. It is created using the `DynamicOpDefinition::get`
496functions. An operation defined at runtime must provide a name, a dialect in
497which the operation will be registered in, an operation verifier. It may also
498optionally define a custom parser and a printer, fold hook, and more.
499
500```c++
501// The operation name, without the dialect name prefix.
502StringRef name = "my_operation_name";
503
504// The dialect defining the operation.
505Dialect* dialect = ctx->getOrLoadDialect<MyDialect>();
506
507// Operation verifier definition.
508AbstractOperation::VerifyInvariantsFn verifyFn = [](Operation* op) {
509    // Logic for the operation verification.
510    ...
511}
512
513// Parser function definition.
514AbstractOperation::ParseAssemblyFn parseFn =
515    [](OpAsmParser &parser, OperationState &state) {
516        // Parse the operation, given that the name is already parsed.
517        ...
518};
519
520// Printer function
521auto printFn = [](Operation *op, OpAsmPrinter &printer) {
522        printer << op->getName();
523        // Print the operation, given that the name is already printed.
524        ...
525};
526
527// General folder implementation, see AbstractOperation::foldHook for more
528// information.
529auto foldHookFn = [](Operation * op, ArrayRef<Attribute> operands,
530                                   SmallVectorImpl<OpFoldResult> &result) {
531    ...
532};
533
534// Returns any canonicalization pattern rewrites that the operation
535// supports, for use by the canonicalization pass.
536auto getCanonicalizationPatterns =
537        [](RewritePatternSet &results, MLIRContext *context) {
538    ...
539}
540
541// Definition of the operation.
542std::unique_ptr<DynamicOpDefinition> opDef =
543    DynamicOpDefinition::get(name, dialect, std::move(verifyFn),
544        std::move(parseFn), std::move(printFn), std::move(foldHookFn),
545        std::move(getCanonicalizationPatterns));
546```
547
548Once the operation is defined, it can be registered by an `ExtensibleDialect`:
549
550```c++
551extensibleDialect->registerDynamicOperation(std::move(opDef));
552```
553
554Note that the `Dialect` given to the operation should be the one registering
555the operation.
556
557### Using an operation defined at runtime
558
559It is possible to match on an operation defined at runtime using their names:
560
561```c++
562if (op->getName().getStringRef() == "my_dialect.my_dynamic_op") {
563    ...
564}
565```
566
567An operation defined at runtime can be created by instantiating an
568`OperationState` with the operation name, and using it with a rewriter
569(for instance a `PatternRewriter`) to create the operation.
570
571```c++
572OperationState state(location, "my_dialect.my_dynamic_op",
573                     operands, resultTypes, attributes);
574
575rewriter.createOperation(state);
576```
577
578### Defining a type at runtime
579
580Contrary to types defined in C++ or in TableGen, types defined at runtime can
581only have as argument a list of `Attribute`.
582
583Similarily to operations, a type is defined at runtime using the class
584`DynamicTypeDefinition`, which is created using the `DynamicTypeDefinition::get`
585functions. A type definition requires a name, the dialect that will register the
586type, and a parameter verifier. It can also define optionally a custom parser
587and printer for the arguments (the type name is assumed to be already
588parsed/printed).
589
590```c++
591// The type name, without the dialect name prefix.
592StringRef name = "my_type_name";
593
594// The dialect defining the type.
595Dialect* dialect = ctx->getOrLoadDialect<MyDialect>();
596
597// The type verifier.
598// A type defined at runtime has a list of attributes as parameters.
599auto verifier = [](function_ref<InFlightDiagnostic()> emitError,
600                   ArrayRef<Attribute> args) {
601    ...
602};
603
604// The type parameters parser.
605auto parser = [](DialectAsmParser &parser,
606                 llvm::SmallVectorImpl<Attribute> &parsedParams) {
607    ...
608};
609
610// The type parameters printer.
611auto printer =[](DialectAsmPrinter &printer, ArrayRef<Attribute> params) {
612    ...
613};
614
615std::unique_ptr<DynamicTypeDefinition> typeDef =
616    DynamicTypeDefinition::get(std::move(name), std::move(dialect),
617                               std::move(verifier), std::move(printer),
618                               std::move(parser));
619```
620
621If the printer and the parser are ommited, a default parser and printer is
622generated with the format `!dialect.typename<arg1, arg2, ..., argN>`.
623
624The type can then be registered by the `ExtensibleDialect`:
625
626```c++
627dialect->registerDynamicType(std::move(typeDef));
628```
629
630### Parsing types defined at runtime in an extensible dialect
631
632`parseType` methods generated by TableGen can parse types defined at runtime,
633though overriden `parseType` methods need to add the necessary support for them.
634
635```c++
636Type MyDialect::parseType(DialectAsmParser &parser) const {
637    ...
638
639    // The type name.
640    StringRef typeTag;
641    if (failed(parser.parseKeyword(&typeTag)))
642        return Type();
643
644    // Try to parse a dynamic type with 'typeTag' name.
645    Type dynType;
646    auto parseResult = parseOptionalDynamicType(typeTag, parser, dynType);
647    if (parseResult.has_value()) {
648        if (succeeded(parseResult.getValue()))
649            return dynType;
650         return Type();
651    }
652
653    ...
654}
655```
656
657### Using a type defined at runtime
658
659Dynamic types are instances of `DynamicType`. It is possible to get a dynamic
660type with `DynamicType::get` and `ExtensibleDialect::lookupTypeDefinition`.
661
662```c++
663auto typeDef = extensibleDialect->lookupTypeDefinition("my_dynamic_type");
664ArrayRef<Attribute> params = ...;
665auto type = DynamicType::get(typeDef, params);
666```
667
668It is also possible to cast a `Type` known to be defined at runtime to a
669`DynamicType`.
670
671```c++
672auto dynType = type.cast<DynamicType>();
673auto typeDef = dynType.getTypeDef();
674auto args = dynType.getParams();
675```
676
677### Defining an attribute at runtime
678
679Similar to types defined at runtime, attributes defined at runtime can only have
680as argument a list of `Attribute`.
681
682Similarily to types, an attribute is defined at runtime using the class
683`DynamicAttrDefinition`, which is created using the `DynamicAttrDefinition::get`
684functions. An attribute definition requires a name, the dialect that will
685register the attribute, and a parameter verifier. It can also define optionally
686a custom parser and printer for the arguments (the attribute name is assumed to
687be already parsed/printed).
688
689```c++
690// The attribute name, without the dialect name prefix.
691StringRef name = "my_attribute_name";
692
693// The dialect defining the attribute.
694Dialect* dialect = ctx->getOrLoadDialect<MyDialect>();
695
696// The attribute verifier.
697// An attribute defined at runtime has a list of attributes as parameters.
698auto verifier = [](function_ref<InFlightDiagnostic()> emitError,
699                   ArrayRef<Attribute> args) {
700    ...
701};
702
703// The attribute parameters parser.
704auto parser = [](DialectAsmParser &parser,
705                 llvm::SmallVectorImpl<Attribute> &parsedParams) {
706    ...
707};
708
709// The attribute parameters printer.
710auto printer =[](DialectAsmPrinter &printer, ArrayRef<Attribute> params) {
711    ...
712};
713
714std::unique_ptr<DynamicAttrDefinition> attrDef =
715    DynamicAttrDefinition::get(std::move(name), std::move(dialect),
716                               std::move(verifier), std::move(printer),
717                               std::move(parser));
718```
719
720If the printer and the parser are ommited, a default parser and printer is
721generated with the format `!dialect.attrname<arg1, arg2, ..., argN>`.
722
723The attribute can then be registered by the `ExtensibleDialect`:
724
725```c++
726dialect->registerDynamicAttr(std::move(typeDef));
727```
728
729### Parsing attributes defined at runtime in an extensible dialect
730
731`parseAttribute` methods generated by TableGen can parse attributes defined at
732runtime, though overriden `parseAttribute` methods need to add the necessary
733support for them.
734
735```c++
736Attribute MyDialect::parseAttribute(DialectAsmParser &parser,
737                                    Type type) const override {
738    ...
739    // The attribute name.
740    StringRef attrTag;
741    if (failed(parser.parseKeyword(&attrTag)))
742        return Attribute();
743
744    // Try to parse a dynamic attribute with 'attrTag' name.
745    Attribute dynAttr;
746    auto parseResult = parseOptionalDynamicAttr(attrTag, parser, dynAttr);
747    if (parseResult.has_value()) {
748        if (succeeded(*parseResult))
749            return dynAttr;
750         return Attribute();
751    }
752```
753
754### Using an attribute defined at runtime
755
756Similar to types, attributes defined at runtime are instances of `DynamicAttr`.
757It is possible to get a dynamic attribute with `DynamicAttr::get` and
758`ExtensibleDialect::lookupAttrDefinition`.
759
760```c++
761auto attrDef = extensibleDialect->lookupAttrDefinition("my_dynamic_attr");
762ArrayRef<Attribute> params = ...;
763auto attr = DynamicAttr::get(attrDef, params);
764```
765
766It is also possible to cast an `Attribute` known to be defined at runtime to a
767`DynamicAttr`.
768
769```c++
770auto dynAttr = attr.cast<DynamicAttr>();
771auto attrDef = dynAttr.getAttrDef();
772auto args = dynAttr.getParams();
773```
774
775### Implementation Details of Extensible Dialects
776
777#### Extensible dialect
778
779The role of extensible dialects is to own the necessary data for defined
780operations and types. They also contain the necessary accessors to easily
781access them.
782
783In order to cast a `Dialect` back to an `ExtensibleDialect`, we implement the
784`IsExtensibleDialect` interface to all `ExtensibleDialect`. The casting is done
785by checking if the `Dialect` implements `IsExtensibleDialect` or not.
786
787#### Operation representation and registration
788
789Operations are represented in mlir using the `AbstractOperation` class. They are
790registered in dialects the same way operations defined in C++ are registered,
791which is by calling `AbstractOperation::insert`.
792
793The only difference is that a new `TypeID` needs to be created for each
794operation, since operations are not represented by a C++ class. This is done
795using a `TypeIDAllocator`, which can allocate a new unique `TypeID` at runtime.
796
797#### Type representation and registration
798
799Unlike operations, types need to define a C++ storage class that takes care of
800type parameters. They also need to define another C++ class to access that
801storage. `DynamicTypeStorage` defines the storage of types defined at runtime,
802and `DynamicType` gives access to the storage, as well as defining useful
803functions. A `DynamicTypeStorage` contains a list of `Attribute` type
804parameters, as well as a pointer to the type definition.
805
806Types are registered using the `Dialect::addType` method, which expect a
807`TypeID` that is generated using a `TypeIDAllocator`. The type uniquer also
808register the type with the given `TypeID`. This mean that we can reuse our
809single `DynamicType` with different `TypeID` to represent the different types
810defined at runtime.
811
812Since the different types defined at runtime have different `TypeID`, it is not
813possible to use `TypeID` to cast a `Type` into a `DynamicType`. Thus, similar to
814`Dialect`, all `DynamicType` define a `IsDynamicTypeTrait`, so casting a `Type`
815to a `DynamicType` boils down to querying the `IsDynamicTypeTrait` trait.
816