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