1//===- ExtensibleDialect.cpp - 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#include "mlir/IR/ExtensibleDialect.h"
10#include "mlir/IR/AttributeSupport.h"
11#include "mlir/IR/DialectImplementation.h"
12#include "mlir/IR/OperationSupport.h"
13#include "mlir/IR/StorageUniquerSupport.h"
14#include "mlir/Support/LogicalResult.h"
15
16using namespace mlir;
17
18//===----------------------------------------------------------------------===//
19// Dynamic types and attributes shared functions
20//===----------------------------------------------------------------------===//
21
22/// Default parser for dynamic attribute or type parameters.
23/// Parse in the format '(<>)?' or '<attr (,attr)*>'.
24static LogicalResult
25typeOrAttrParser(AsmParser &parser, SmallVectorImpl<Attribute> &parsedParams) {
26 // No parameters
27 if (parser.parseOptionalLess() || !parser.parseOptionalGreater())
28 return success();
29
30 Attribute attr;
31 if (parser.parseAttribute(result&: attr))
32 return failure();
33 parsedParams.push_back(Elt: attr);
34
35 while (parser.parseOptionalGreater()) {
36 Attribute attr;
37 if (parser.parseComma() || parser.parseAttribute(result&: attr))
38 return failure();
39 parsedParams.push_back(Elt: attr);
40 }
41
42 return success();
43}
44
45/// Default printer for dynamic attribute or type parameters.
46/// Print in the format '(<>)?' or '<attr (,attr)*>'.
47static void typeOrAttrPrinter(AsmPrinter &printer, ArrayRef<Attribute> params) {
48 if (params.empty())
49 return;
50
51 printer << "<";
52 interleaveComma(c: params, os&: printer.getStream());
53 printer << ">";
54}
55
56//===----------------------------------------------------------------------===//
57// Dynamic type
58//===----------------------------------------------------------------------===//
59
60std::unique_ptr<DynamicTypeDefinition>
61DynamicTypeDefinition::get(StringRef name, ExtensibleDialect *dialect,
62 VerifierFn &&verifier) {
63 return DynamicTypeDefinition::get(name, dialect, verifier: std::move(verifier),
64 parser: typeOrAttrParser, printer: typeOrAttrPrinter);
65}
66
67std::unique_ptr<DynamicTypeDefinition>
68DynamicTypeDefinition::get(StringRef name, ExtensibleDialect *dialect,
69 VerifierFn &&verifier, ParserFn &&parser,
70 PrinterFn &&printer) {
71 return std::unique_ptr<DynamicTypeDefinition>(
72 new DynamicTypeDefinition(name, dialect, std::move(verifier),
73 std::move(parser), std::move(printer)));
74}
75
76DynamicTypeDefinition::DynamicTypeDefinition(StringRef nameRef,
77 ExtensibleDialect *dialect,
78 VerifierFn &&verifier,
79 ParserFn &&parser,
80 PrinterFn &&printer)
81 : name(nameRef), dialect(dialect), verifier(std::move(verifier)),
82 parser(std::move(parser)), printer(std::move(printer)),
83 ctx(dialect->getContext()) {}
84
85DynamicTypeDefinition::DynamicTypeDefinition(ExtensibleDialect *dialect,
86 StringRef nameRef)
87 : name(nameRef), dialect(dialect), ctx(dialect->getContext()) {}
88
89void DynamicTypeDefinition::registerInTypeUniquer() {
90 detail::TypeUniquer::registerType<DynamicType>(ctx: &getContext(), typeID: getTypeID());
91}
92
93namespace mlir {
94namespace detail {
95/// Storage of DynamicType.
96/// Contains a pointer to the type definition and type parameters.
97struct DynamicTypeStorage : public TypeStorage {
98
99 using KeyTy = std::pair<DynamicTypeDefinition *, ArrayRef<Attribute>>;
100
101 explicit DynamicTypeStorage(DynamicTypeDefinition *typeDef,
102 ArrayRef<Attribute> params)
103 : typeDef(typeDef), params(params) {}
104
105 bool operator==(const KeyTy &key) const {
106 return typeDef == key.first && params == key.second;
107 }
108
109 static llvm::hash_code hashKey(const KeyTy &key) {
110 return llvm::hash_value(arg: key);
111 }
112
113 static DynamicTypeStorage *construct(TypeStorageAllocator &alloc,
114 const KeyTy &key) {
115 return new (alloc.allocate<DynamicTypeStorage>())
116 DynamicTypeStorage(key.first, alloc.copyInto(elements: key.second));
117 }
118
119 /// Definition of the type.
120 DynamicTypeDefinition *typeDef;
121
122 /// The type parameters.
123 ArrayRef<Attribute> params;
124};
125} // namespace detail
126} // namespace mlir
127
128DynamicType DynamicType::get(DynamicTypeDefinition *typeDef,
129 ArrayRef<Attribute> params) {
130 auto &ctx = typeDef->getContext();
131 auto emitError = detail::getDefaultDiagnosticEmitFn(ctx: &ctx);
132 assert(succeeded(typeDef->verify(emitError, params)));
133 return detail::TypeUniquer::getWithTypeID<DynamicType>(
134 ctx: &ctx, typeID: typeDef->getTypeID(), args&: typeDef, args&: params);
135}
136
137DynamicType
138DynamicType::getChecked(function_ref<InFlightDiagnostic()> emitError,
139 DynamicTypeDefinition *typeDef,
140 ArrayRef<Attribute> params) {
141 if (failed(result: typeDef->verify(emitError, params)))
142 return {};
143 auto &ctx = typeDef->getContext();
144 return detail::TypeUniquer::getWithTypeID<DynamicType>(
145 ctx: &ctx, typeID: typeDef->getTypeID(), args&: typeDef, args&: params);
146}
147
148DynamicTypeDefinition *DynamicType::getTypeDef() { return getImpl()->typeDef; }
149
150ArrayRef<Attribute> DynamicType::getParams() { return getImpl()->params; }
151
152bool DynamicType::classof(Type type) {
153 return type.hasTrait<TypeTrait::IsDynamicType>();
154}
155
156ParseResult DynamicType::parse(AsmParser &parser,
157 DynamicTypeDefinition *typeDef,
158 DynamicType &parsedType) {
159 SmallVector<Attribute> params;
160 if (failed(result: typeDef->parser(parser, params)))
161 return failure();
162 parsedType = parser.getChecked<DynamicType>(params&: typeDef, params);
163 if (!parsedType)
164 return failure();
165 return success();
166}
167
168void DynamicType::print(AsmPrinter &printer) {
169 printer << getTypeDef()->getName();
170 getTypeDef()->printer(printer, getParams());
171}
172
173//===----------------------------------------------------------------------===//
174// Dynamic attribute
175//===----------------------------------------------------------------------===//
176
177std::unique_ptr<DynamicAttrDefinition>
178DynamicAttrDefinition::get(StringRef name, ExtensibleDialect *dialect,
179 VerifierFn &&verifier) {
180 return DynamicAttrDefinition::get(name, dialect, verifier: std::move(verifier),
181 parser: typeOrAttrParser, printer: typeOrAttrPrinter);
182}
183
184std::unique_ptr<DynamicAttrDefinition>
185DynamicAttrDefinition::get(StringRef name, ExtensibleDialect *dialect,
186 VerifierFn &&verifier, ParserFn &&parser,
187 PrinterFn &&printer) {
188 return std::unique_ptr<DynamicAttrDefinition>(
189 new DynamicAttrDefinition(name, dialect, std::move(verifier),
190 std::move(parser), std::move(printer)));
191}
192
193DynamicAttrDefinition::DynamicAttrDefinition(StringRef nameRef,
194 ExtensibleDialect *dialect,
195 VerifierFn &&verifier,
196 ParserFn &&parser,
197 PrinterFn &&printer)
198 : name(nameRef), dialect(dialect), verifier(std::move(verifier)),
199 parser(std::move(parser)), printer(std::move(printer)),
200 ctx(dialect->getContext()) {}
201
202DynamicAttrDefinition::DynamicAttrDefinition(ExtensibleDialect *dialect,
203 StringRef nameRef)
204 : name(nameRef), dialect(dialect), ctx(dialect->getContext()) {}
205
206void DynamicAttrDefinition::registerInAttrUniquer() {
207 detail::AttributeUniquer::registerAttribute<DynamicAttr>(ctx: &getContext(),
208 typeID: getTypeID());
209}
210
211namespace mlir {
212namespace detail {
213/// Storage of DynamicAttr.
214/// Contains a pointer to the attribute definition and attribute parameters.
215struct DynamicAttrStorage : public AttributeStorage {
216 using KeyTy = std::pair<DynamicAttrDefinition *, ArrayRef<Attribute>>;
217
218 explicit DynamicAttrStorage(DynamicAttrDefinition *attrDef,
219 ArrayRef<Attribute> params)
220 : attrDef(attrDef), params(params) {}
221
222 bool operator==(const KeyTy &key) const {
223 return attrDef == key.first && params == key.second;
224 }
225
226 static llvm::hash_code hashKey(const KeyTy &key) {
227 return llvm::hash_value(arg: key);
228 }
229
230 static DynamicAttrStorage *construct(AttributeStorageAllocator &alloc,
231 const KeyTy &key) {
232 return new (alloc.allocate<DynamicAttrStorage>())
233 DynamicAttrStorage(key.first, alloc.copyInto(elements: key.second));
234 }
235
236 /// Definition of the type.
237 DynamicAttrDefinition *attrDef;
238
239 /// The type parameters.
240 ArrayRef<Attribute> params;
241};
242} // namespace detail
243} // namespace mlir
244
245DynamicAttr DynamicAttr::get(DynamicAttrDefinition *attrDef,
246 ArrayRef<Attribute> params) {
247 auto &ctx = attrDef->getContext();
248 return detail::AttributeUniquer::getWithTypeID<DynamicAttr>(
249 ctx: &ctx, typeID: attrDef->getTypeID(), args&: attrDef, args&: params);
250}
251
252DynamicAttr
253DynamicAttr::getChecked(function_ref<InFlightDiagnostic()> emitError,
254 DynamicAttrDefinition *attrDef,
255 ArrayRef<Attribute> params) {
256 if (failed(result: attrDef->verify(emitError, params)))
257 return {};
258 return get(attrDef, params);
259}
260
261DynamicAttrDefinition *DynamicAttr::getAttrDef() { return getImpl()->attrDef; }
262
263ArrayRef<Attribute> DynamicAttr::getParams() { return getImpl()->params; }
264
265bool DynamicAttr::classof(Attribute attr) {
266 return attr.hasTrait<AttributeTrait::IsDynamicAttr>();
267}
268
269ParseResult DynamicAttr::parse(AsmParser &parser,
270 DynamicAttrDefinition *attrDef,
271 DynamicAttr &parsedAttr) {
272 SmallVector<Attribute> params;
273 if (failed(result: attrDef->parser(parser, params)))
274 return failure();
275 parsedAttr = parser.getChecked<DynamicAttr>(params&: attrDef, params);
276 if (!parsedAttr)
277 return failure();
278 return success();
279}
280
281void DynamicAttr::print(AsmPrinter &printer) {
282 printer << getAttrDef()->getName();
283 getAttrDef()->printer(printer, getParams());
284}
285
286//===----------------------------------------------------------------------===//
287// Dynamic operation
288//===----------------------------------------------------------------------===//
289
290DynamicOpDefinition::DynamicOpDefinition(
291 StringRef name, ExtensibleDialect *dialect,
292 OperationName::VerifyInvariantsFn &&verifyFn,
293 OperationName::VerifyRegionInvariantsFn &&verifyRegionFn,
294 OperationName::ParseAssemblyFn &&parseFn,
295 OperationName::PrintAssemblyFn &&printFn,
296 OperationName::FoldHookFn &&foldHookFn,
297 GetCanonicalizationPatternsFn &&getCanonicalizationPatternsFn,
298 OperationName::PopulateDefaultAttrsFn &&populateDefaultAttrsFn)
299 : Impl(StringAttr::get(dialect->getContext(),
300 (dialect->getNamespace() + "." + name).str()),
301 dialect, dialect->allocateTypeID(),
302 /*interfaceMap=*/detail::InterfaceMap()),
303 verifyFn(std::move(verifyFn)), verifyRegionFn(std::move(verifyRegionFn)),
304 parseFn(std::move(parseFn)), printFn(std::move(printFn)),
305 foldHookFn(std::move(foldHookFn)),
306 getCanonicalizationPatternsFn(std::move(getCanonicalizationPatternsFn)),
307 populateDefaultAttrsFn(std::move(populateDefaultAttrsFn)) {
308 typeID = dialect->allocateTypeID();
309}
310
311std::unique_ptr<DynamicOpDefinition> DynamicOpDefinition::get(
312 StringRef name, ExtensibleDialect *dialect,
313 OperationName::VerifyInvariantsFn &&verifyFn,
314 OperationName::VerifyRegionInvariantsFn &&verifyRegionFn) {
315 auto parseFn = [](OpAsmParser &parser, OperationState &result) {
316 return parser.emitError(
317 loc: parser.getCurrentLocation(),
318 message: "dynamic operation do not define any parser function");
319 };
320
321 auto printFn = [](Operation *op, OpAsmPrinter &printer, StringRef) {
322 printer.printGenericOp(op);
323 };
324
325 return DynamicOpDefinition::get(name, dialect, verifyFn: std::move(verifyFn),
326 verifyRegionFn: std::move(verifyRegionFn), parseFn: std::move(parseFn),
327 printFn: std::move(printFn));
328}
329
330std::unique_ptr<DynamicOpDefinition> DynamicOpDefinition::get(
331 StringRef name, ExtensibleDialect *dialect,
332 OperationName::VerifyInvariantsFn &&verifyFn,
333 OperationName::VerifyRegionInvariantsFn &&verifyRegionFn,
334 OperationName::ParseAssemblyFn &&parseFn,
335 OperationName::PrintAssemblyFn &&printFn) {
336 auto foldHookFn = [](Operation *op, ArrayRef<Attribute> operands,
337 SmallVectorImpl<OpFoldResult> &results) {
338 return failure();
339 };
340
341 auto getCanonicalizationPatternsFn = [](RewritePatternSet &, MLIRContext *) {
342 };
343
344 auto populateDefaultAttrsFn = [](const OperationName &, NamedAttrList &) {};
345
346 return DynamicOpDefinition::get(name, dialect, verifyFn: std::move(verifyFn),
347 verifyRegionFn: std::move(verifyRegionFn), parseFn: std::move(parseFn),
348 printFn: std::move(printFn), foldHookFn: std::move(foldHookFn),
349 getCanonicalizationPatternsFn: std::move(getCanonicalizationPatternsFn),
350 populateDefaultAttrsFn: std::move(populateDefaultAttrsFn));
351}
352
353std::unique_ptr<DynamicOpDefinition> DynamicOpDefinition::get(
354 StringRef name, ExtensibleDialect *dialect,
355 OperationName::VerifyInvariantsFn &&verifyFn,
356 OperationName::VerifyInvariantsFn &&verifyRegionFn,
357 OperationName::ParseAssemblyFn &&parseFn,
358 OperationName::PrintAssemblyFn &&printFn,
359 OperationName::FoldHookFn &&foldHookFn,
360 GetCanonicalizationPatternsFn &&getCanonicalizationPatternsFn,
361 OperationName::PopulateDefaultAttrsFn &&populateDefaultAttrsFn) {
362 return std::unique_ptr<DynamicOpDefinition>(new DynamicOpDefinition(
363 name, dialect, std::move(verifyFn), std::move(verifyRegionFn),
364 std::move(parseFn), std::move(printFn), std::move(foldHookFn),
365 std::move(getCanonicalizationPatternsFn),
366 std::move(populateDefaultAttrsFn)));
367}
368
369//===----------------------------------------------------------------------===//
370// Extensible dialect
371//===----------------------------------------------------------------------===//
372
373namespace {
374/// Interface that can only be implemented by extensible dialects.
375/// The interface is used to check if a dialect is extensible or not.
376class IsExtensibleDialect : public DialectInterface::Base<IsExtensibleDialect> {
377public:
378 IsExtensibleDialect(Dialect *dialect) : Base(dialect) {}
379
380 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(IsExtensibleDialect)
381};
382} // namespace
383
384ExtensibleDialect::ExtensibleDialect(StringRef name, MLIRContext *ctx,
385 TypeID typeID)
386 : Dialect(name, ctx, typeID) {
387 addInterfaces<IsExtensibleDialect>();
388}
389
390void ExtensibleDialect::registerDynamicType(
391 std::unique_ptr<DynamicTypeDefinition> &&type) {
392 DynamicTypeDefinition *typePtr = type.get();
393 TypeID typeID = type->getTypeID();
394 StringRef name = type->getName();
395 ExtensibleDialect *dialect = type->getDialect();
396
397 assert(dialect == this &&
398 "trying to register a dynamic type in the wrong dialect");
399
400 // If a type with the same name is already defined, fail.
401 auto registered = dynTypes.try_emplace(Key: typeID, Args: std::move(type)).second;
402 (void)registered;
403 assert(registered && "type TypeID was not unique");
404
405 registered = nameToDynTypes.insert(KV: {name, typePtr}).second;
406 (void)registered;
407 assert(registered &&
408 "Trying to create a new dynamic type with an existing name");
409
410 // The StringAttr allocates the type name StringRef for the duration of the
411 // MLIR context.
412 MLIRContext *ctx = getContext();
413 auto nameAttr =
414 StringAttr::get(ctx, getNamespace() + "." + typePtr->getName());
415
416 auto abstractType = AbstractType::get(
417 *dialect, DynamicAttr::getInterfaceMap(), DynamicType::getHasTraitFn(),
418 DynamicType::getWalkImmediateSubElementsFn(),
419 DynamicType::getReplaceImmediateSubElementsFn(), typeID, nameAttr);
420
421 /// Add the type to the dialect and the type uniquer.
422 addType(typeID, std::move(abstractType));
423 typePtr->registerInTypeUniquer();
424}
425
426void ExtensibleDialect::registerDynamicAttr(
427 std::unique_ptr<DynamicAttrDefinition> &&attr) {
428 auto *attrPtr = attr.get();
429 auto typeID = attr->getTypeID();
430 auto name = attr->getName();
431 auto *dialect = attr->getDialect();
432
433 assert(dialect == this &&
434 "trying to register a dynamic attribute in the wrong dialect");
435
436 // If an attribute with the same name is already defined, fail.
437 auto registered = dynAttrs.try_emplace(Key: typeID, Args: std::move(attr)).second;
438 (void)registered;
439 assert(registered && "attribute TypeID was not unique");
440
441 registered = nameToDynAttrs.insert(KV: {name, attrPtr}).second;
442 (void)registered;
443 assert(registered &&
444 "Trying to create a new dynamic attribute with an existing name");
445
446 // The StringAttr allocates the attribute name StringRef for the duration of
447 // the MLIR context.
448 MLIRContext *ctx = getContext();
449 auto nameAttr =
450 StringAttr::get(ctx, getNamespace() + "." + attrPtr->getName());
451
452 auto abstractAttr = AbstractAttribute::get(
453 *dialect, DynamicAttr::getInterfaceMap(), DynamicAttr::getHasTraitFn(),
454 DynamicAttr::getWalkImmediateSubElementsFn(),
455 DynamicAttr::getReplaceImmediateSubElementsFn(), typeID, nameAttr);
456
457 /// Add the type to the dialect and the type uniquer.
458 addAttribute(typeID, std::move(abstractAttr));
459 attrPtr->registerInAttrUniquer();
460}
461
462void ExtensibleDialect::registerDynamicOp(
463 std::unique_ptr<DynamicOpDefinition> &&op) {
464 assert(op->dialect == this &&
465 "trying to register a dynamic op in the wrong dialect");
466 RegisteredOperationName::insert(std::move(op), /*attrNames=*/{});
467}
468
469bool ExtensibleDialect::classof(const Dialect *dialect) {
470 return const_cast<Dialect *>(dialect)
471 ->getRegisteredInterface<IsExtensibleDialect>();
472}
473
474OptionalParseResult ExtensibleDialect::parseOptionalDynamicType(
475 StringRef typeName, AsmParser &parser, Type &resultType) const {
476 DynamicTypeDefinition *typeDef = lookupTypeDefinition(name: typeName);
477 if (!typeDef)
478 return std::nullopt;
479
480 DynamicType dynType;
481 if (DynamicType::parse(parser, typeDef, parsedType&: dynType))
482 return failure();
483 resultType = dynType;
484 return success();
485}
486
487LogicalResult ExtensibleDialect::printIfDynamicType(Type type,
488 AsmPrinter &printer) {
489 if (auto dynType = llvm::dyn_cast<DynamicType>(Val&: type)) {
490 dynType.print(printer);
491 return success();
492 }
493 return failure();
494}
495
496OptionalParseResult ExtensibleDialect::parseOptionalDynamicAttr(
497 StringRef attrName, AsmParser &parser, Attribute &resultAttr) const {
498 DynamicAttrDefinition *attrDef = lookupAttrDefinition(name: attrName);
499 if (!attrDef)
500 return std::nullopt;
501
502 DynamicAttr dynAttr;
503 if (DynamicAttr::parse(parser, attrDef, parsedAttr&: dynAttr))
504 return failure();
505 resultAttr = dynAttr;
506 return success();
507}
508
509LogicalResult ExtensibleDialect::printIfDynamicAttr(Attribute attribute,
510 AsmPrinter &printer) {
511 if (auto dynAttr = llvm::dyn_cast<DynamicAttr>(Val&: attribute)) {
512 dynAttr.print(printer);
513 return success();
514 }
515 return failure();
516}
517
518//===----------------------------------------------------------------------===//
519// Dynamic dialect
520//===----------------------------------------------------------------------===//
521
522namespace {
523/// Interface that can only be implemented by extensible dialects.
524/// The interface is used to check if a dialect is extensible or not.
525class IsDynamicDialect : public DialectInterface::Base<IsDynamicDialect> {
526public:
527 IsDynamicDialect(Dialect *dialect) : Base(dialect) {}
528
529 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(IsDynamicDialect)
530};
531} // namespace
532
533DynamicDialect::DynamicDialect(StringRef name, MLIRContext *ctx)
534 : SelfOwningTypeID(),
535 ExtensibleDialect(name, ctx, SelfOwningTypeID::getTypeID()) {
536 addInterfaces<IsDynamicDialect>();
537}
538
539bool DynamicDialect::classof(const Dialect *dialect) {
540 return const_cast<Dialect *>(dialect)
541 ->getRegisteredInterface<IsDynamicDialect>();
542}
543
544Type DynamicDialect::parseType(DialectAsmParser &parser) const {
545 auto loc = parser.getCurrentLocation();
546 StringRef typeTag;
547 if (failed(result: parser.parseKeyword(keyword: &typeTag)))
548 return Type();
549
550 {
551 Type dynType;
552 auto parseResult = parseOptionalDynamicType(typeName: typeTag, parser, resultType&: dynType);
553 if (parseResult.has_value()) {
554 if (succeeded(result: parseResult.value()))
555 return dynType;
556 return Type();
557 }
558 }
559
560 parser.emitError(loc, message: "expected dynamic type");
561 return Type();
562}
563
564void DynamicDialect::printType(Type type, DialectAsmPrinter &printer) const {
565 auto wasDynamic = printIfDynamicType(type, printer);
566 (void)wasDynamic;
567 assert(succeeded(wasDynamic) &&
568 "non-dynamic type defined in dynamic dialect");
569}
570
571Attribute DynamicDialect::parseAttribute(DialectAsmParser &parser,
572 Type type) const {
573 auto loc = parser.getCurrentLocation();
574 StringRef typeTag;
575 if (failed(result: parser.parseKeyword(keyword: &typeTag)))
576 return Attribute();
577
578 {
579 Attribute dynAttr;
580 auto parseResult = parseOptionalDynamicAttr(attrName: typeTag, parser, resultAttr&: dynAttr);
581 if (parseResult.has_value()) {
582 if (succeeded(result: parseResult.value()))
583 return dynAttr;
584 return Attribute();
585 }
586 }
587
588 parser.emitError(loc, message: "expected dynamic attribute");
589 return Attribute();
590}
591void DynamicDialect::printAttribute(Attribute attr,
592 DialectAsmPrinter &printer) const {
593 auto wasDynamic = printIfDynamicAttr(attribute: attr, printer);
594 (void)wasDynamic;
595 assert(succeeded(wasDynamic) &&
596 "non-dynamic attribute defined in dynamic dialect");
597}
598

source code of mlir/lib/IR/ExtensibleDialect.cpp