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/ArithmeticOps.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### Dependent Dialects
112
113MLIR has a very large ecosystem, and contains dialects that server many different purposes. It
114is quite common, given the above, that dialects may want to reuse certain components from other
115dialects. This may mean generating operations from those dialects during canonicalization, reusing
116attributes or types, etc. When a dialect has a dependency on another, i.e. when it constructs and/or
117generally relies on the components of another dialect, a dialect dependency should be explicitly
118recorded. An explicitly dependency ensures that dependent dialects are loaded alongside the
119dialect. Dialect dependencies can be recorded using the `dependentDialects` dialects field:
120
121```tablegen
122def MyDialect : Dialect {
123  // Here we register the Arithmetic and Func dialect as dependencies of our `MyDialect`.
124  let dependentDialects = [
125    "arith::ArithmeticDialect",
126    "func::FuncDialect"
127  ];
128}
129```
130
131### Extra declarations
132
133The declarative Dialect definitions try to auto-generate as much logic and methods
134as possible. With that said, there will always be long-tail cases that won't be covered.
135For such cases, `extraClassDeclaration` can be used. Code within the `extraClassDeclaration`
136field will be copied literally to the generated C++ Dialect class.
137
138Note that `extraClassDeclaration` is a mechanism intended for long-tail cases by
139power users; for not-yet-implemented widely-applicable cases, improving the
140infrastructure is preferable.
141
142### `hasConstantMaterializer`: Materializing Constants from Attributes
143
144This field is utilized to materialize a constant operation from an `Attribute` value and
145a `Type`. This is generally used when an operation within this dialect has been folded,
146and a constant operation should be generated. `hasConstantMaterializer` is used to enable
147materialization, and the `materializeConstant` hook is declared on the dialect. This
148hook takes in an `Attribute` value, generally returned by `fold`, and produces a
149"constant-like" operation that materializes that value. See the
150[documentation for canonicalization](Canonicalization.md) for a more in-depth
151introduction to `folding` in MLIR.
152
153Constant materialization logic can then be defined in the source file:
154
155```c++
156/// Hook to materialize a single constant operation from a given attribute value
157/// with the desired resultant type. This method should use the provided builder
158/// to create the operation without changing the insertion position. The
159/// generated operation is expected to be constant-like. On success, this hook
160/// should return the operation generated to represent the constant value.
161/// Otherwise, it should return nullptr on failure.
162Operation *MyDialect::materializeConstant(OpBuilder &builder, Attribute value,
163                                          Type type, Location loc) {
164  ...
165}
166```
167
168### `hasNonDefaultDestructor`: Providing a custom destructor
169
170This field should be used when the Dialect class has a custom destructor, i.e.
171when the dialect has some special logic to be run in the `~MyDialect`. In this case,
172only the declaration of the destructor is generated for the Dialect class.
173
174### Discardable Attribute Verification
175
176As described by the [MLIR Language Reference](LangRef.md#attributes),
177*discardable attribute* are a type of attribute that has its semantics defined
178by the dialect whose name prefixes that of the attribute. For example, if an
179operation has an attribute named `gpu.contained_module`, the `gpu` dialect
180defines the semantics and invariants, such as when and where it is valid to use,
181of that attribute. To hook into this verification for attributes that are prefixed
182by our dialect, several hooks on the Dialect may be used:
183
184#### `hasOperationAttrVerify`
185
186This field generates the hook for verifying when a discardable attribute of this dialect
187has been used within the attribute dictionary of an operation. This hook has the form:
188
189```c++
190/// Verify the use of the given attribute, whose name is prefixed by the namespace of this
191/// dialect, that was used in `op`s dictionary.
192LogicalResult MyDialect::verifyOperationAttribute(Operation *op, NamedAttribute attribute);
193```
194
195#### `hasRegionArgAttrVerify`
196
197This field generates the hook for verifying when a discardable attribute of this dialect
198has been used within the attribute dictionary of a region entry block argument. Note that
199the block arguments of a region entry block do not themselves have attribute dictionaries,
200but some operations may provide special dictionary attributes that correspond to the arguments
201of a region. For example, operations that implement `FunctionOpInterface` may have attribute
202dictionaries on the operation that correspond to the arguments of entry block of the function.
203In these cases, those operations will invoke this hook on the dialect to ensure the attribute
204is verified. The hook necessary for the dialect to implement has the form:
205
206```c++
207/// Verify the use of the given attribute, whose name is prefixed by the namespace of this
208/// dialect, that was used on the attribute dictionary of a region entry block argument.
209/// Note: As described above, when a region entry block has a dictionary is up to the individual
210/// operation to define.
211LogicalResult MyDialect::verifyRegionArgAttribute(Operation *op, unsigned regionIndex,
212                                                  unsigned argIndex, NamedAttribute attribute);
213```
214
215#### `hasRegionResultAttrVerify`
216
217This field generates the hook for verifying when a discardable attribute of this dialect
218has been used within the attribute dictionary of a region result. Note that the results of a
219region do not themselves have attribute dictionaries, but some operations may provide special
220dictionary attributes that correspond to the results of a region. For example, operations that
221implement `FunctionOpInterface` may have attribute dictionaries on the operation that correspond
222to the results of the function. In these cases, those operations will invoke this hook on the
223dialect to ensure the attribute is verified. The hook necessary for the dialect to implement
224has the form:
225
226```c++
227/// Generate verification for the given attribute, whose name is prefixed by the namespace
228/// of this dialect, that was used on the attribute dictionary of a region result.
229/// Note: As described above, when a region entry block has a dictionary is up to the individual
230/// operation to define.
231LogicalResult MyDialect::verifyRegionResultAttribute(Operation *op, unsigned regionIndex,
232                                                     unsigned argIndex, NamedAttribute attribute);
233```
234
235### Operation Interface Fallback
236
237Some dialects have an open ecosystem and don't register all of the possible operations. In such
238cases it is still possible to provide support for implementing an `OpInterface` for these
239operations. When an operation isn't registered or does not provide an implementation for an
240interface, the query will fallback to the dialect itself. The `hasOperationInterfaceFallback`
241field may be used to declare this fallback for operations:
242
243```c++
244/// Return an interface model for the interface with the given `typeId` for the operation
245/// with the given name.
246void *MyDialect::getRegisteredInterfaceForOp(TypeID typeID, StringAttr opName);
247```
248
249For a more detail description of the expected usages of this hook, view the detailed
250[interface documentation](Interfaces.md#dialect-fallback-for-opinterface).
251
252### Default Attribute/Type Parsers and Printers
253
254When a dialect registers an Attribute or Type, it must also override the respective
255`Dialect::parseAttribute`/`Dialect::printAttribute` or
256`Dialect::parseType`/`Dialect::printType` methods. In these cases, the dialect must
257explicitly handle the parsing and printing of each individual attribute or type within
258the dialect. If all of the attributes and types of the dialect provide a mnemonic,
259however, these methods may be autogenerated by using the
260`useDefaultAttributePrinterParser` and `useDefaultTypePrinterParser` fields. By default,
261these fields are set to `1`(enabled), meaning that if a dialect needs to explicitly handle the
262parser and printer of its Attributes and Types it should set these to `0` as necessary.
263
264### Dialect-wide Canonicalization Patterns
265
266Generally, [canonicalization](Canonicalization.md) patterns are specific to individual
267operations within a dialect. There are some cases, however, that prompt canonicalization
268patterns to be added to the dialect-level. For example, if a dialect defines a canonicalization
269pattern that operates on an interface or trait, it can be beneficial to only add this pattern
270once, instead of duplicating per-operation that implements that interface. To enable the
271generation of this hook, the `hasCanonicalizer` field may be used. This will declare
272the `getCanonicalizationPatterns` method on the dialect, which has the form:
273
274```c++
275/// Return the canonicalization patterns for this dialect:
276void MyDialect::getCanonicalizationPatterns(RewritePatternSet &results) const;
277```
278
279See the documentation for [Canonicalization in MLIR](Canonicalization.md) for a much more
280detailed description about canonicalization patterns.
281
282### C++ Accessor Prefix
283
284Historically, MLIR has generated accessors for operation components (such as attribute, operands,
285results) using the tablegen definition name verbatim. This means that if an operation was defined
286as:
287
288```tablegen
289def MyOp : MyDialect<"op"> {
290  let arguments = (ins StrAttr:$value, StrAttr:$other_value);
291}
292```
293
294It would have accessors generated for the `value` and `other_value` attributes as follows:
295
296```c++
297StringAttr MyOp::value();
298void MyOp::value(StringAttr newValue);
299
300StringAttr MyOp::other_value();
301void MyOp::other_value(StringAttr newValue);
302```
303
304Since then, we have decided to move accessors over to a style that matches the rest of the
305code base. More specifically, this means that we prefix accessors with `get` and `set`
306respectively, and transform `snake_style` names to camel case (`UpperCamel` when prefixed,
307and `lowerCamel` for individual variable names). If we look at the same example as above, this
308would produce:
309
310```c++
311StringAttr MyOp::getValue();
312void MyOp::setValue(StringAttr newValue);
313
314StringAttr MyOp::getOtherValue();
315void MyOp::setOtherValue(StringAttr newValue);
316```
317
318The form in which accessors are generated is controlled by the `emitAccessorPrefix` field.
319This field may any of the following values:
320
321* `kEmitAccessorPrefix_Raw`
322  - Don't emit any `get`/`set` prefix.
323
324* `kEmitAccessorPrefix_Prefixed`
325  - Only emit with `get`/`set` prefix.
326
327* `kEmitAccessorPrefix_Both`
328  - Emit with **and** without prefix.
329
330All new dialects are strongly encouraged to use the `kEmitAccessorPrefix_Prefixed` value, as
331the `Raw` form is deprecated and in the process of being removed.
332
333Note: Remove this section when all dialects have been switched to the new accessor form.
334
335## Defining an Extensible dialect
336
337This section documents the design and API of the extensible dialects. Extensible
338dialects are dialects that can be extended with new operations and types defined
339at runtime. This allows for users to define dialects via with meta-programming,
340or from another language, without having to recompile C++ code.
341
342### Defining an extensible dialect
343
344Dialects defined in C++ can be extended with new operations, types, etc., at
345runtime by inheriting from `mlir::ExtensibleDialect` instead of `mlir::Dialect`
346(note that `ExtensibleDialect` inherits from `Dialect`). The `ExtensibleDialect`
347class contains the necessary fields and methods to extend the dialect at
348runtime.
349
350```c++
351class MyDialect : public mlir::ExtensibleDialect {
352    ...
353}
354```
355
356For dialects defined in TableGen, this is done by setting the `isExtensible`
357flag to `1`.
358
359```tablegen
360def Test_Dialect : Dialect {
361  let isExtensible = 1;
362  ...
363}
364```
365
366An extensible `Dialect` can be casted back to `ExtensibleDialect` using
367`llvm::dyn_cast`, or `llvm::cast`:
368
369```c++
370if (auto extensibleDialect = llvm::dyn_cast<ExtensibleDialect>(dialect)) {
371    ...
372}
373```
374
375### Defining an operation at runtime
376
377The `DynamicOpDefinition` class represents the definition of an operation
378defined at runtime. It is created using the `DynamicOpDefinition::get`
379functions. An operation defined at runtime must provide a name, a dialect in
380which the operation will be registered in, an operation verifier. It may also
381optionally define a custom parser and a printer, fold hook, and more.
382
383```c++
384// The operation name, without the dialect name prefix.
385StringRef name = "my_operation_name";
386
387// The dialect defining the operation.
388Dialect* dialect = ctx->getOrLoadDialect<MyDialect>();
389
390// Operation verifier definition.
391AbstractOperation::VerifyInvariantsFn verifyFn = [](Operation* op) {
392    // Logic for the operation verification.
393    ...
394}
395
396// Parser function definition.
397AbstractOperation::ParseAssemblyFn parseFn =
398    [](OpAsmParser &parser, OperationState &state) {
399        // Parse the operation, given that the name is already parsed.
400        ...
401};
402
403// Printer function
404auto printFn = [](Operation *op, OpAsmPrinter &printer) {
405        printer << op->getName();
406        // Print the operation, given that the name is already printed.
407        ...
408};
409
410// General folder implementation, see AbstractOperation::foldHook for more
411// information.
412auto foldHookFn = [](Operation * op, ArrayRef<Attribute> operands,
413                                   SmallVectorImpl<OpFoldResult> &result) {
414    ...
415};
416
417// Returns any canonicalization pattern rewrites that the operation
418// supports, for use by the canonicalization pass.
419auto getCanonicalizationPatterns =
420        [](RewritePatternSet &results, MLIRContext *context) {
421    ...
422}
423
424// Definition of the operation.
425std::unique_ptr<DynamicOpDefinition> opDef =
426    DynamicOpDefinition::get(name, dialect, std::move(verifyFn),
427        std::move(parseFn), std::move(printFn), std::move(foldHookFn),
428        std::move(getCanonicalizationPatterns));
429```
430
431Once the operation is defined, it can be registered by an `ExtensibleDialect`:
432
433```c++
434extensibleDialect->registerDynamicOperation(std::move(opDef));
435```
436
437Note that the `Dialect` given to the operation should be the one registering
438the operation.
439
440### Using an operation defined at runtime
441
442It is possible to match on an operation defined at runtime using their names:
443
444```c++
445if (op->getName().getStringRef() == "my_dialect.my_dynamic_op") {
446    ...
447}
448```
449
450An operation defined at runtime can be created by instantiating an
451`OperationState` with the operation name, and using it with a rewriter
452(for instance a `PatternRewriter`) to create the operation.
453
454```c++
455OperationState state(location, "my_dialect.my_dynamic_op",
456                     operands, resultTypes, attributes);
457
458rewriter.createOperation(state);
459```
460
461
462### Defining a type at runtime
463
464Contrary to types defined in C++ or in TableGen, types defined at runtime can
465only have as argument a list of `Attribute`.
466
467Similarily to operations, a type is defined at runtime using the class
468`DynamicTypeDefinition`, which is created using the `DynamicTypeDefinition::get`
469functions. A type definition requires a name, the dialect that will register the
470type, and a parameter verifier. It can also define optionally a custom parser
471and printer for the arguments (the type name is assumed to be already
472parsed/printed).
473
474```c++
475// The type name, without the dialect name prefix.
476StringRef name = "my_type_name";
477
478// The dialect defining the type.
479Dialect* dialect = ctx->getOrLoadDialect<MyDialect>();
480
481// The type verifier.
482// A type defined at runtime has a list of attributes as parameters.
483auto verifier = [](function_ref<InFlightDiagnostic()> emitError,
484                   ArrayRef<Attribute> args) {
485    ...
486};
487
488// The type parameters parser.
489auto parser = [](DialectAsmParser &parser,
490                 llvm::SmallVectorImpl<Attribute> &parsedParams) {
491    ...
492};
493
494// The type parameters printer.
495auto printer =[](DialectAsmPrinter &printer, ArrayRef<Attribute> params) {
496    ...
497};
498
499std::unique_ptr<DynamicTypeDefinition> typeDef =
500    DynamicTypeDefinition::get(std::move(name), std::move(dialect),
501                               std::move(verifier), std::move(printer),
502                               std::move(parser));
503```
504
505If the printer and the parser are ommited, a default parser and printer is
506generated with the format `!dialect.typename<arg1, arg2, ..., argN>`.
507
508The type can then be registered by the `ExtensibleDialect`:
509
510```c++
511dialect->registerDynamicType(std::move(typeDef));
512```
513
514### Parsing types defined at runtime in an extensible dialect
515
516`parseType` methods generated by TableGen can parse types defined at runtime,
517though overriden `parseType` methods need to add the necessary support for them.
518
519```c++
520Type MyDialect::parseType(DialectAsmParser &parser) const {
521    ...
522
523    // The type name.
524    StringRef typeTag;
525    if (failed(parser.parseKeyword(&typeTag)))
526        return Type();
527
528    // Try to parse a dynamic type with 'typeTag' name.
529    Type dynType;
530    auto parseResult = parseOptionalDynamicType(typeTag, parser, dynType);
531    if (parseResult.hasValue()) {
532        if (succeeded(parseResult.getValue()))
533            return dynType;
534         return Type();
535    }
536
537    ...
538}
539```
540
541### Using a type defined at runtime
542
543Dynamic types are instances of `DynamicType`. It is possible to get a dynamic
544type with `DynamicType::get` and `ExtensibleDialect::lookupTypeDefinition`.
545
546```c++
547auto typeDef = extensibleDialect->lookupTypeDefinition("my_dynamic_type");
548ArrayRef<Attribute> params = ...;
549auto type = DynamicType::get(typeDef, params);
550```
551
552It is also possible to cast a `Type` known to be defined at runtime to a
553`DynamicType`.
554
555```c++
556auto dynType = type.cast<DynamicType>();
557auto typeDef = dynType.getTypeDef();
558auto args = dynType.getParams();
559```
560
561### Defining an attribute at runtime
562
563Similar to types defined at runtime, attributes defined at runtime can only have
564as argument a list of `Attribute`.
565
566Similarily to types, an attribute is defined at runtime using the class
567`DynamicAttrDefinition`, which is created using the `DynamicAttrDefinition::get`
568functions. An attribute definition requires a name, the dialect that will
569register the attribute, and a parameter verifier. It can also define optionally
570a custom parser and printer for the arguments (the attribute name is assumed to
571be already parsed/printed).
572
573```c++
574// The attribute name, without the dialect name prefix.
575StringRef name = "my_attribute_name";
576
577// The dialect defining the attribute.
578Dialect* dialect = ctx->getOrLoadDialect<MyDialect>();
579
580// The attribute verifier.
581// An attribute defined at runtime has a list of attributes as parameters.
582auto verifier = [](function_ref<InFlightDiagnostic()> emitError,
583                   ArrayRef<Attribute> args) {
584    ...
585};
586
587// The attribute parameters parser.
588auto parser = [](DialectAsmParser &parser,
589                 llvm::SmallVectorImpl<Attribute> &parsedParams) {
590    ...
591};
592
593// The attribute parameters printer.
594auto printer =[](DialectAsmPrinter &printer, ArrayRef<Attribute> params) {
595    ...
596};
597
598std::unique_ptr<DynamicAttrDefinition> attrDef =
599    DynamicAttrDefinition::get(std::move(name), std::move(dialect),
600                               std::move(verifier), std::move(printer),
601                               std::move(parser));
602```
603
604If the printer and the parser are ommited, a default parser and printer is
605generated with the format `!dialect.attrname<arg1, arg2, ..., argN>`.
606
607The attribute can then be registered by the `ExtensibleDialect`:
608
609```c++
610dialect->registerDynamicAttr(std::move(typeDef));
611```
612
613### Parsing attributes defined at runtime in an extensible dialect
614
615`parseAttribute` methods generated by TableGen can parse attributes defined at
616runtime, though overriden `parseAttribute` methods need to add the necessary
617support for them.
618
619```c++
620Attribute MyDialect::parseAttribute(DialectAsmParser &parser,
621                                    Type type) const override {
622    ...
623    // The attribute name.
624    StringRef attrTag;
625    if (failed(parser.parseKeyword(&attrTag)))
626        return Attribute();
627
628    // Try to parse a dynamic attribute with 'attrTag' name.
629    Attribute dynAttr;
630    auto parseResult = parseOptionalDynamicAttr(attrTag, parser, dynAttr);
631    if (parseResult.hasValue()) {
632        if (succeeded(parseResult.getValue()))
633            return dynAttr;
634         return Attribute();
635    }
636```
637
638### Using an attribute defined at runtime
639
640Similar to types, attributes defined at runtime are instances of `DynamicAttr`.
641It is possible to get a dynamic attribute with `DynamicAttr::get` and
642`ExtensibleDialect::lookupAttrDefinition`.
643
644```c++
645auto attrDef = extensibleDialect->lookupAttrDefinition("my_dynamic_attr");
646ArrayRef<Attribute> params = ...;
647auto attr = DynamicAttr::get(attrDef, params);
648```
649
650It is also possible to cast an `Attribute` known to be defined at runtime to a
651`DynamicAttr`.
652
653```c++
654auto dynAttr = attr.cast<DynamicAttr>();
655auto attrDef = dynAttr.getAttrDef();
656auto args = dynAttr.getParams();
657```
658
659### Implementation Details of Extensible Dialects
660
661#### Extensible dialect
662
663The role of extensible dialects is to own the necessary data for defined
664operations and types. They also contain the necessary accessors to easily
665access them.
666
667In order to cast a `Dialect` back to an `ExtensibleDialect`, we implement the
668`IsExtensibleDialect` interface to all `ExtensibleDialect`. The casting is done
669by checking if the `Dialect` implements `IsExtensibleDialect` or not.
670
671#### Operation representation and registration
672
673Operations are represented in mlir using the `AbstractOperation` class. They are
674registered in dialects the same way operations defined in C++ are registered,
675which is by calling `AbstractOperation::insert`.
676
677The only difference is that a new `TypeID` needs to be created for each
678operation, since operations are not represented by a C++ class. This is done
679using a `TypeIDAllocator`, which can allocate a new unique `TypeID` at runtime.
680
681#### Type representation and registration
682
683Unlike operations, types need to define a C++ storage class that takes care of
684type parameters. They also need to define another C++ class to access that
685storage. `DynamicTypeStorage` defines the storage of types defined at runtime,
686and `DynamicType` gives access to the storage, as well as defining useful
687functions. A `DynamicTypeStorage` contains a list of `Attribute` type
688parameters, as well as a pointer to the type definition.
689
690Types are registered using the `Dialect::addType` method, which expect a
691`TypeID` that is generated using a `TypeIDAllocator`. The type uniquer also
692register the type with the given `TypeID`. This mean that we can reuse our
693single `DynamicType` with different `TypeID` to represent the different types
694defined at runtime.
695
696Since the different types defined at runtime have different `TypeID`, it is not
697possible to use `TypeID` to cast a `Type` into a `DynamicType`. Thus, similar to
698`Dialect`, all `DynamicType` define a `IsDynamicTypeTrait`, so casting a `Type`
699to a `DynamicType` boils down to querying the `IsDynamicTypeTrait` trait.
700