1 //===- ExtensibleDialect.h - Extensible dialect -----------------*- C++ -*-===// 2 // 3 // This file is licensed under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file defines the DynamicOpDefinition class, the DynamicTypeDefinition 10 // class, and the DynamicAttrDefinition class, which represent respectively 11 // operations, types, and attributes that can be defined at runtime. They can 12 // be registered at runtime to an extensible dialect, using the 13 // ExtensibleDialect class defined in this file. 14 // 15 // For a more complete documentation, see 16 // https://mlir.llvm.org/docs/ExtensibleDialects/ . 17 // 18 //===----------------------------------------------------------------------===// 19 20 #ifndef MLIR_IR_EXTENSIBLEDIALECT_H 21 #define MLIR_IR_EXTENSIBLEDIALECT_H 22 23 #include "mlir/IR/Dialect.h" 24 #include "mlir/IR/DialectInterface.h" 25 #include "mlir/IR/MLIRContext.h" 26 #include "mlir/IR/OpDefinition.h" 27 #include "mlir/Support/TypeID.h" 28 #include "llvm/ADT/StringMap.h" 29 30 namespace mlir { 31 class AsmParser; 32 class AsmPrinter; 33 class DynamicAttr; 34 class DynamicType; 35 class ExtensibleDialect; 36 class MLIRContext; 37 class OptionalParseResult; 38 class ParseResult; 39 40 namespace detail { 41 struct DynamicAttrStorage; 42 struct DynamicTypeStorage; 43 } // namespace detail 44 45 //===----------------------------------------------------------------------===// 46 // Dynamic attribute 47 //===----------------------------------------------------------------------===// 48 49 /// The definition of a dynamic attribute. A dynamic attribute is an attribute 50 /// that is defined at runtime, and that can be registered at runtime by an 51 /// extensible dialect (a dialect inheriting ExtensibleDialect). This class 52 /// stores the parser, the printer, and the verifier of the attribute. Each 53 /// dynamic attribute definition refers to one instance of this class. 54 class DynamicAttrDefinition : SelfOwningTypeID { 55 public: 56 using VerifierFn = llvm::unique_function<LogicalResult( 57 function_ref<InFlightDiagnostic()>, ArrayRef<Attribute>) const>; 58 using ParserFn = llvm::unique_function<ParseResult( 59 AsmParser &parser, llvm::SmallVectorImpl<Attribute> &parsedAttributes) 60 const>; 61 using PrinterFn = llvm::unique_function<void( 62 AsmPrinter &printer, ArrayRef<Attribute> params) const>; 63 64 /// Create a new attribute definition at runtime. The attribute is registered 65 /// only after passing it to the dialect using registerDynamicAttr. 66 static std::unique_ptr<DynamicAttrDefinition> 67 get(StringRef name, ExtensibleDialect *dialect, VerifierFn &&verifier); 68 static std::unique_ptr<DynamicAttrDefinition> 69 get(StringRef name, ExtensibleDialect *dialect, VerifierFn &&verifier, 70 ParserFn &&parser, PrinterFn &&printer); 71 72 /// Check that the attribute parameters are valid. verify(function_ref<InFlightDiagnostic ()> emitError,ArrayRef<Attribute> params)73 LogicalResult verify(function_ref<InFlightDiagnostic()> emitError, 74 ArrayRef<Attribute> params) const { 75 return verifier(emitError, params); 76 } 77 78 /// Return the MLIRContext in which the dynamic attributes are uniqued. getContext()79 MLIRContext &getContext() const { return *ctx; } 80 81 /// Return the name of the attribute, in the format 'attrname' and 82 /// not 'dialectname.attrname'. getName()83 StringRef getName() const { return name; } 84 85 /// Return the dialect defining the attribute. getDialect()86 ExtensibleDialect *getDialect() const { return dialect; } 87 88 private: 89 DynamicAttrDefinition(StringRef name, ExtensibleDialect *dialect, 90 VerifierFn &&verifier, ParserFn &&parser, 91 PrinterFn &&printer); 92 93 /// This constructor should only be used when we need a pointer to 94 /// the DynamicAttrDefinition in the verifier, the parser, or the printer. 95 /// The verifier, parser, and printer need thus to be initialized after the 96 /// constructor. 97 DynamicAttrDefinition(ExtensibleDialect *dialect, StringRef name); 98 99 /// Register the concrete attribute in the attribute Uniquer. 100 void registerInAttrUniquer(); 101 102 /// The name should be prefixed with the dialect name followed by '.'. 103 std::string name; 104 105 /// Dialect in which this attribute is defined. 106 ExtensibleDialect *dialect; 107 108 /// The attribute verifier. It checks that the attribute parameters satisfy 109 /// the invariants. 110 VerifierFn verifier; 111 112 /// The attribute parameters parser. It parses only the parameters, and 113 /// expects the attribute name to have already been parsed. 114 ParserFn parser; 115 116 /// The attribute parameters printer. It prints only the parameters, and 117 /// expects the attribute name to have already been printed. 118 PrinterFn printer; 119 120 /// Context in which the concrete attributes are uniqued. 121 MLIRContext *ctx; 122 123 friend ExtensibleDialect; 124 friend DynamicAttr; 125 }; 126 127 /// This trait is used to determine if an attribute is a dynamic attribute or 128 /// not; it should only be implemented by dynamic attributes. 129 /// Note: This is only required because dynamic attributes do not have a 130 /// static/single TypeID. 131 namespace AttributeTrait { 132 template <typename ConcreteType> 133 class IsDynamicAttr : public TraitBase<ConcreteType, IsDynamicAttr> {}; 134 } // namespace AttributeTrait 135 136 /// A dynamic attribute instance. This is an attribute whose definition is 137 /// defined at runtime. 138 /// It is possible to check if an attribute is a dynamic attribute using 139 /// `my_attr.isa<DynamicAttr>()`, and getting the attribute definition of a 140 /// dynamic attribute using the `DynamicAttr::getAttrDef` method. 141 /// All dynamic attributes have the same storage, which is an array of 142 /// attributes. 143 144 class DynamicAttr : public Attribute::AttrBase<DynamicAttr, Attribute, 145 detail::DynamicAttrStorage, 146 AttributeTrait::IsDynamicAttr> { 147 public: 148 // Inherit Base constructors. 149 using Base::Base; 150 151 /// Return an instance of a dynamic attribute given a dynamic attribute 152 /// definition and attribute parameters. 153 /// This asserts that the attribute verifier succeeded. 154 static DynamicAttr get(DynamicAttrDefinition *attrDef, 155 ArrayRef<Attribute> params = {}); 156 157 /// Return an instance of a dynamic attribute given a dynamic attribute 158 /// definition and attribute parameters. If the parameters provided are 159 /// invalid, errors are emitted using the provided location and a null object 160 /// is returned. 161 static DynamicAttr getChecked(function_ref<InFlightDiagnostic()> emitError, 162 DynamicAttrDefinition *attrDef, 163 ArrayRef<Attribute> params = {}); 164 165 /// Return the attribute definition of the concrete attribute. 166 DynamicAttrDefinition *getAttrDef(); 167 168 /// Return the attribute parameters. 169 ArrayRef<Attribute> getParams(); 170 171 /// Check if an attribute is a specific dynamic attribute. isa(Attribute attr,DynamicAttrDefinition * attrDef)172 static bool isa(Attribute attr, DynamicAttrDefinition *attrDef) { 173 return attr.getTypeID() == attrDef->getTypeID(); 174 } 175 176 /// Check if an attribute is a dynamic attribute. 177 static bool classof(Attribute attr); 178 179 /// Parse the dynamic attribute parameters and construct the attribute. 180 /// The parameters are either empty, and nothing is parsed, 181 /// or they are in the format '<>' or '<attr (,attr)*>'. 182 static ParseResult parse(AsmParser &parser, DynamicAttrDefinition *attrDef, 183 DynamicAttr &parsedAttr); 184 185 /// Print the dynamic attribute with the format 'attrname' if there is no 186 /// parameters, or 'attrname<attr (,attr)*>'. 187 void print(AsmPrinter &printer); 188 }; 189 190 //===----------------------------------------------------------------------===// 191 // Dynamic type 192 //===----------------------------------------------------------------------===// 193 194 /// The definition of a dynamic type. A dynamic type is a type that is 195 /// defined at runtime, and that can be registered at runtime by an 196 /// extensible dialect (a dialect inheriting ExtensibleDialect). This class 197 /// stores the parser, the printer, and the verifier of the type. Each dynamic 198 /// type definition refers to one instance of this class. 199 class DynamicTypeDefinition : SelfOwningTypeID { 200 public: 201 using VerifierFn = llvm::unique_function<LogicalResult( 202 function_ref<InFlightDiagnostic()>, ArrayRef<Attribute>) const>; 203 using ParserFn = llvm::unique_function<ParseResult( 204 AsmParser &parser, llvm::SmallVectorImpl<Attribute> &parsedAttributes) 205 const>; 206 using PrinterFn = llvm::unique_function<void( 207 AsmPrinter &printer, ArrayRef<Attribute> params) const>; 208 209 /// Create a new dynamic type definition. The type is registered only after 210 /// passing it to the dialect using registerDynamicType. 211 static std::unique_ptr<DynamicTypeDefinition> 212 get(StringRef name, ExtensibleDialect *dialect, VerifierFn &&verifier); 213 static std::unique_ptr<DynamicTypeDefinition> 214 get(StringRef name, ExtensibleDialect *dialect, VerifierFn &&verifier, 215 ParserFn &&parser, PrinterFn &&printer); 216 217 /// Check that the type parameters are valid. verify(function_ref<InFlightDiagnostic ()> emitError,ArrayRef<Attribute> params)218 LogicalResult verify(function_ref<InFlightDiagnostic()> emitError, 219 ArrayRef<Attribute> params) const { 220 return verifier(emitError, params); 221 } 222 223 /// Return the MLIRContext in which the dynamic types is uniqued. getContext()224 MLIRContext &getContext() const { return *ctx; } 225 226 /// Return the name of the type, in the format 'typename' and 227 /// not 'dialectname.typename'. getName()228 StringRef getName() const { return name; } 229 230 /// Return the dialect defining the type. getDialect()231 ExtensibleDialect *getDialect() const { return dialect; } 232 233 private: 234 DynamicTypeDefinition(StringRef name, ExtensibleDialect *dialect, 235 VerifierFn &&verifier, ParserFn &&parser, 236 PrinterFn &&printer); 237 238 /// This constructor should only be used when we need a pointer to 239 /// the DynamicTypeDefinition in the verifier, the parser, or the printer. 240 /// The verifier, parser, and printer need thus to be initialized after the 241 /// constructor. 242 DynamicTypeDefinition(ExtensibleDialect *dialect, StringRef name); 243 244 /// Register the concrete type in the type Uniquer. 245 void registerInTypeUniquer(); 246 247 /// The name should be prefixed with the dialect name followed by '.'. 248 std::string name; 249 250 /// Dialect in which this type is defined. 251 ExtensibleDialect *dialect; 252 253 /// The type verifier. It checks that the type parameters satisfy the 254 /// invariants. 255 VerifierFn verifier; 256 257 /// The type parameters parser. It parses only the parameters, and expects the 258 /// type name to have already been parsed. 259 ParserFn parser; 260 261 /// The type parameters printer. It prints only the parameters, and expects 262 /// the type name to have already been printed. 263 PrinterFn printer; 264 265 /// Context in which the concrete types are uniqued. 266 MLIRContext *ctx; 267 268 friend ExtensibleDialect; 269 friend DynamicType; 270 }; 271 272 /// This trait is used to determine if a type is a dynamic type or not; 273 /// it should only be implemented by dynamic types. 274 /// Note: This is only required because dynamic type do not have a 275 /// static/single TypeID. 276 namespace TypeTrait { 277 template <typename ConcreteType> 278 class IsDynamicType : public TypeTrait::TraitBase<ConcreteType, IsDynamicType> { 279 }; 280 } // namespace TypeTrait 281 282 /// A dynamic type instance. This is a type whose definition is defined at 283 /// runtime. 284 /// It is possible to check if a type is a dynamic type using 285 /// `my_type.isa<DynamicType>()`, and getting the type definition of a dynamic 286 /// type using the `DynamicType::getTypeDef` method. 287 /// All dynamic types have the same storage, which is an array of attributes. 288 class DynamicType 289 : public Type::TypeBase<DynamicType, Type, detail::DynamicTypeStorage, 290 TypeTrait::IsDynamicType> { 291 public: 292 // Inherit Base constructors. 293 using Base::Base; 294 295 /// Return an instance of a dynamic type given a dynamic type definition and 296 /// type parameters. 297 /// This asserts that the type verifier succeeded. 298 static DynamicType get(DynamicTypeDefinition *typeDef, 299 ArrayRef<Attribute> params = {}); 300 301 /// Return an instance of a dynamic type given a dynamic type definition and 302 /// type parameters. If the parameters provided are invalid, errors are 303 /// emitted using the provided location and a null object is returned. 304 static DynamicType getChecked(function_ref<InFlightDiagnostic()> emitError, 305 DynamicTypeDefinition *typeDef, 306 ArrayRef<Attribute> params = {}); 307 308 /// Return the type definition of the concrete type. 309 DynamicTypeDefinition *getTypeDef(); 310 311 /// Return the type parameters. 312 ArrayRef<Attribute> getParams(); 313 314 /// Check if a type is a specific dynamic type. isa(Type type,DynamicTypeDefinition * typeDef)315 static bool isa(Type type, DynamicTypeDefinition *typeDef) { 316 return type.getTypeID() == typeDef->getTypeID(); 317 } 318 319 /// Check if a type is a dynamic type. 320 static bool classof(Type type); 321 322 /// Parse the dynamic type parameters and construct the type. 323 /// The parameters are either empty, and nothing is parsed, 324 /// or they are in the format '<>' or '<attr (,attr)*>'. 325 static ParseResult parse(AsmParser &parser, DynamicTypeDefinition *typeDef, 326 DynamicType &parsedType); 327 328 /// Print the dynamic type with the format 329 /// 'type' or 'type<>' if there is no parameters, or 'type<attr (,attr)*>'. 330 void print(AsmPrinter &printer); 331 }; 332 333 //===----------------------------------------------------------------------===// 334 // Dynamic operation 335 //===----------------------------------------------------------------------===// 336 337 /// The definition of a dynamic op. A dynamic op is an op that is defined at 338 /// runtime, and that can be registered at runtime by an extensible dialect (a 339 /// dialect inheriting ExtensibleDialect). This class stores the functions that 340 /// are in the OperationName class, and in addition defines the TypeID of the op 341 /// that will be defined. 342 /// Each dynamic operation definition refers to one instance of this class. 343 class DynamicOpDefinition { 344 public: 345 /// Create a new op at runtime. The op is registered only after passing it to 346 /// the dialect using registerDynamicOp. 347 static std::unique_ptr<DynamicOpDefinition> 348 get(StringRef name, ExtensibleDialect *dialect, 349 OperationName::VerifyInvariantsFn &&verifyFn, 350 OperationName::VerifyRegionInvariantsFn &&verifyRegionFn); 351 static std::unique_ptr<DynamicOpDefinition> 352 get(StringRef name, ExtensibleDialect *dialect, 353 OperationName::VerifyInvariantsFn &&verifyFn, 354 OperationName::VerifyRegionInvariantsFn &&verifyRegionFn, 355 OperationName::ParseAssemblyFn &&parseFn, 356 OperationName::PrintAssemblyFn &&printFn); 357 static std::unique_ptr<DynamicOpDefinition> 358 get(StringRef name, ExtensibleDialect *dialect, 359 OperationName::VerifyInvariantsFn &&verifyFn, 360 OperationName::VerifyRegionInvariantsFn &&verifyRegionFn, 361 OperationName::ParseAssemblyFn &&parseFn, 362 OperationName::PrintAssemblyFn &&printFn, 363 OperationName::FoldHookFn &&foldHookFn, 364 OperationName::GetCanonicalizationPatternsFn 365 &&getCanonicalizationPatternsFn, 366 OperationName::PopulateDefaultAttrsFn &&populateDefaultAttrsFn); 367 368 /// Returns the op typeID. getTypeID()369 TypeID getTypeID() { return typeID; } 370 371 /// Sets the verifier function for this operation. It should emits an error 372 /// message and returns failure if a problem is detected, or returns success 373 /// if everything is ok. setVerifyFn(OperationName::VerifyInvariantsFn && verify)374 void setVerifyFn(OperationName::VerifyInvariantsFn &&verify) { 375 verifyFn = std::move(verify); 376 } 377 378 /// Sets the region verifier function for this operation. It should emits an 379 /// error message and returns failure if a problem is detected, or returns 380 /// success if everything is ok. setVerifyRegionFn(OperationName::VerifyRegionInvariantsFn && verify)381 void setVerifyRegionFn(OperationName::VerifyRegionInvariantsFn &&verify) { 382 verifyRegionFn = std::move(verify); 383 } 384 385 /// Sets the static hook for parsing this op assembly. setParseFn(OperationName::ParseAssemblyFn && parse)386 void setParseFn(OperationName::ParseAssemblyFn &&parse) { 387 parseFn = std::move(parse); 388 } 389 390 /// Sets the static hook for printing this op assembly. setPrintFn(OperationName::PrintAssemblyFn && print)391 void setPrintFn(OperationName::PrintAssemblyFn &&print) { 392 printFn = std::move(print); 393 } 394 395 /// Sets the hook implementing a generalized folder for the op. See 396 /// `RegisteredOperationName::foldHook` for more details setFoldHookFn(OperationName::FoldHookFn && foldHook)397 void setFoldHookFn(OperationName::FoldHookFn &&foldHook) { 398 foldHookFn = std::move(foldHook); 399 } 400 401 /// Set the hook returning any canonicalization pattern rewrites that the op 402 /// supports, for use by the canonicalization pass. 403 void setGetCanonicalizationPatternsFn(OperationName::GetCanonicalizationPatternsFn && getCanonicalizationPatterns)404 setGetCanonicalizationPatternsFn(OperationName::GetCanonicalizationPatternsFn 405 &&getCanonicalizationPatterns) { 406 getCanonicalizationPatternsFn = std::move(getCanonicalizationPatterns); 407 } 408 409 /// Set the hook populating default attributes. setPopulateDefaultAttrsFn(OperationName::PopulateDefaultAttrsFn && populateDefaultAttrs)410 void setPopulateDefaultAttrsFn( 411 OperationName::PopulateDefaultAttrsFn &&populateDefaultAttrs) { 412 populateDefaultAttrsFn = std::move(populateDefaultAttrs); 413 } 414 415 private: 416 DynamicOpDefinition( 417 StringRef name, ExtensibleDialect *dialect, 418 OperationName::VerifyInvariantsFn &&verifyFn, 419 OperationName::VerifyRegionInvariantsFn &&verifyRegionFn, 420 OperationName::ParseAssemblyFn &&parseFn, 421 OperationName::PrintAssemblyFn &&printFn, 422 OperationName::FoldHookFn &&foldHookFn, 423 OperationName::GetCanonicalizationPatternsFn 424 &&getCanonicalizationPatternsFn, 425 OperationName::PopulateDefaultAttrsFn &&populateDefaultAttrsFn); 426 427 /// Unique identifier for this operation. 428 TypeID typeID; 429 430 /// Name of the operation. 431 /// The name is prefixed with the dialect name. 432 std::string name; 433 434 /// Dialect defining this operation. 435 ExtensibleDialect *dialect; 436 437 OperationName::VerifyInvariantsFn verifyFn; 438 OperationName::VerifyRegionInvariantsFn verifyRegionFn; 439 OperationName::ParseAssemblyFn parseFn; 440 OperationName::PrintAssemblyFn printFn; 441 OperationName::FoldHookFn foldHookFn; 442 OperationName::GetCanonicalizationPatternsFn getCanonicalizationPatternsFn; 443 OperationName::PopulateDefaultAttrsFn populateDefaultAttrsFn; 444 445 friend ExtensibleDialect; 446 }; 447 448 //===----------------------------------------------------------------------===// 449 // Extensible dialect 450 //===----------------------------------------------------------------------===// 451 452 /// A dialect that can be extended with new operations/types/attributes at 453 /// runtime. 454 class ExtensibleDialect : public mlir::Dialect { 455 public: 456 ExtensibleDialect(StringRef name, MLIRContext *ctx, TypeID typeID); 457 458 /// Add a new type defined at runtime to the dialect. 459 void registerDynamicType(std::unique_ptr<DynamicTypeDefinition> &&type); 460 461 /// Add a new attribute defined at runtime to the dialect. 462 void registerDynamicAttr(std::unique_ptr<DynamicAttrDefinition> &&attr); 463 464 /// Add a new operation defined at runtime to the dialect. 465 void registerDynamicOp(std::unique_ptr<DynamicOpDefinition> &&type); 466 467 /// Check if the dialect is an extensible dialect. 468 static bool classof(const Dialect *dialect); 469 470 /// Returns nullptr if the definition was not found. lookupTypeDefinition(StringRef name)471 DynamicTypeDefinition *lookupTypeDefinition(StringRef name) const { 472 auto it = nameToDynTypes.find(name); 473 if (it == nameToDynTypes.end()) 474 return nullptr; 475 return it->second; 476 } 477 478 /// Returns nullptr if the definition was not found. lookupTypeDefinition(TypeID id)479 DynamicTypeDefinition *lookupTypeDefinition(TypeID id) const { 480 auto it = dynTypes.find(id); 481 if (it == dynTypes.end()) 482 return nullptr; 483 return it->second.get(); 484 } 485 486 /// Returns nullptr if the definition was not found. lookupAttrDefinition(StringRef name)487 DynamicAttrDefinition *lookupAttrDefinition(StringRef name) const { 488 auto it = nameToDynAttrs.find(name); 489 if (it == nameToDynAttrs.end()) 490 return nullptr; 491 return it->second; 492 } 493 494 /// Returns nullptr if the definition was not found. lookupAttrDefinition(TypeID id)495 DynamicAttrDefinition *lookupAttrDefinition(TypeID id) const { 496 auto it = dynAttrs.find(id); 497 if (it == dynAttrs.end()) 498 return nullptr; 499 return it->second.get(); 500 } 501 502 protected: 503 /// Parse the dynamic type 'typeName' in the dialect 'dialect'. 504 /// typename should not be prefixed with the dialect name. 505 /// If the dynamic type does not exist, return no value. 506 /// Otherwise, parse it, and return the parse result. 507 /// If the parsing succeed, put the resulting type in 'resultType'. 508 OptionalParseResult parseOptionalDynamicType(StringRef typeName, 509 AsmParser &parser, 510 Type &resultType) const; 511 512 /// If 'type' is a dynamic type, print it. 513 /// Returns success if the type was printed, and failure if the type was not a 514 /// dynamic type. 515 static LogicalResult printIfDynamicType(Type type, AsmPrinter &printer); 516 517 /// Parse the dynamic attribute 'attrName' in the dialect 'dialect'. 518 /// attrname should not be prefixed with the dialect name. 519 /// If the dynamic attribute does not exist, return no value. 520 /// Otherwise, parse it, and return the parse result. 521 /// If the parsing succeed, put the resulting attribute in 'resultAttr'. 522 OptionalParseResult parseOptionalDynamicAttr(StringRef attrName, 523 AsmParser &parser, 524 Attribute &resultAttr) const; 525 526 /// If 'attr' is a dynamic attribute, print it. 527 /// Returns success if the attribute was printed, and failure if the 528 /// attribute was not a dynamic attribute. 529 static LogicalResult printIfDynamicAttr(Attribute attr, AsmPrinter &printer); 530 531 private: 532 /// The set of all dynamic types registered. 533 DenseMap<TypeID, std::unique_ptr<DynamicTypeDefinition>> dynTypes; 534 535 /// This structure allows to get in O(1) a dynamic type given its name. 536 llvm::StringMap<DynamicTypeDefinition *> nameToDynTypes; 537 538 /// The set of all dynamic attributes registered. 539 DenseMap<TypeID, std::unique_ptr<DynamicAttrDefinition>> dynAttrs; 540 541 /// This structure allows to get in O(1) a dynamic attribute given its name. 542 llvm::StringMap<DynamicAttrDefinition *> nameToDynAttrs; 543 544 /// Give DynamicOpDefinition access to allocateTypeID. 545 friend DynamicOpDefinition; 546 547 /// Allocates a type ID to uniquify operations. allocateTypeID()548 TypeID allocateTypeID() { return typeIDAllocator.allocate(); } 549 550 /// Owns the TypeID generated at runtime for operations. 551 TypeIDAllocator typeIDAllocator; 552 }; 553 } // namespace mlir 554 555 namespace llvm { 556 /// Provide isa functionality for ExtensibleDialect. 557 /// This is to override the isa functionality for Dialect. 558 template <> 559 struct isa_impl<mlir::ExtensibleDialect, mlir::Dialect> { 560 static inline bool doit(const ::mlir::Dialect &dialect) { 561 return mlir::ExtensibleDialect::classof(&dialect); 562 } 563 }; 564 } // namespace llvm 565 566 #endif // MLIR_IR_EXTENSIBLEDIALECT_H 567