MLIR  22.0.0git
DebugImporter.cpp
Go to the documentation of this file.
1 //===- DebugImporter.cpp - LLVM to MLIR Debug conversion ------------------===//
2 //
3 // Part of the LLVM Project, 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 "DebugImporter.h"
11 #include "mlir/IR/Attributes.h"
13 #include "mlir/IR/Location.h"
14 #include "llvm/ADT/STLExtras.h"
15 #include "llvm/ADT/TypeSwitch.h"
16 #include "llvm/BinaryFormat/Dwarf.h"
17 #include "llvm/IR/Constants.h"
18 #include "llvm/IR/DebugInfoMetadata.h"
19 #include "llvm/IR/Metadata.h"
20 
21 using namespace mlir;
22 using namespace mlir::LLVM;
23 using namespace mlir::LLVM::detail;
24 
25 DebugImporter::DebugImporter(ModuleOp mlirModule,
26  bool dropDICompositeTypeElements)
27  : cache([&](llvm::DINode *node) { return createRecSelf(node); }),
28  context(mlirModule.getContext()), mlirModule(mlirModule),
29  dropDICompositeTypeElements(dropDICompositeTypeElements) {}
30 
32  llvm::DISubprogram *subprogram = func->getSubprogram();
33  if (!subprogram)
34  return UnknownLoc::get(context);
35 
36  // Add a fused location to link the subprogram information.
37  StringAttr funcName = StringAttr::get(context, subprogram->getName());
38  StringAttr fileName = StringAttr::get(context, subprogram->getFilename());
40  {NameLoc::get(funcName),
41  FileLineColLoc::get(fileName, subprogram->getLine(), /*column=*/0)},
42  translate(subprogram), context);
43 }
44 
45 //===----------------------------------------------------------------------===//
46 // Attributes
47 //===----------------------------------------------------------------------===//
48 
49 DIBasicTypeAttr DebugImporter::translateImpl(llvm::DIBasicType *node) {
50  return DIBasicTypeAttr::get(context, node->getTag(), node->getName(),
51  node->getSizeInBits(), node->getEncoding());
52 }
53 
54 DICompileUnitAttr DebugImporter::translateImpl(llvm::DICompileUnit *node) {
55  std::optional<DIEmissionKind> emissionKind =
56  symbolizeDIEmissionKind(node->getEmissionKind());
57  std::optional<DINameTableKind> nameTableKind = symbolizeDINameTableKind(
58  static_cast<
59  std::underlying_type_t<llvm::DICompileUnit::DebugNameTableKind>>(
60  node->getNameTableKind()));
62  context, getOrCreateDistinctID(node),
63  node->getSourceLanguage().getUnversionedName(),
64  translate(node->getFile()), getStringAttrOrNull(node->getRawProducer()),
65  node->isOptimized(), emissionKind.value(), nameTableKind.value(),
66  getStringAttrOrNull(node->getRawSplitDebugFilename()));
67 }
68 
69 DICompositeTypeAttr DebugImporter::translateImpl(llvm::DICompositeType *node) {
70  std::optional<DIFlags> flags = symbolizeDIFlags(node->getFlags());
71  SmallVector<DINodeAttr> elements;
72 
73  // A vector always requires an element.
74  bool isVectorType = flags && bitEnumContainsAll(*flags, DIFlags::Vector);
75  if (isVectorType || !dropDICompositeTypeElements) {
76  for (llvm::DINode *element : node->getElements()) {
77  assert(element && "expected a non-null element type");
78  elements.push_back(translate(element));
79  }
80  }
81  // Drop the elements parameter if any of the elements are invalid.
82  if (llvm::is_contained(elements, nullptr))
83  elements.clear();
84  DITypeAttr baseType = translate(node->getBaseType());
85  // Arrays require a base type, otherwise the debug metadata is considered to
86  // be malformed.
87  if (node->getTag() == llvm::dwarf::DW_TAG_array_type && !baseType)
88  return nullptr;
90  context, node->getTag(), getStringAttrOrNull(node->getRawName()),
91  translate(node->getFile()), node->getLine(), translate(node->getScope()),
92  baseType, flags.value_or(DIFlags::Zero), node->getSizeInBits(),
93  node->getAlignInBits(), translateExpression(node->getDataLocationExp()),
94  translateExpression(node->getRankExp()),
95  translateExpression(node->getAllocatedExp()),
96  translateExpression(node->getAssociatedExp()), elements);
97 }
98 
99 DIDerivedTypeAttr DebugImporter::translateImpl(llvm::DIDerivedType *node) {
100  // Return nullptr if the base type is invalid.
101  DITypeAttr baseType = translate(node->getBaseType());
102  if (node->getBaseType() && !baseType)
103  return nullptr;
104  DINodeAttr extraData =
105  translate(dyn_cast_or_null<llvm::DINode>(node->getExtraData()));
106  return DIDerivedTypeAttr::get(
107  context, node->getTag(), getStringAttrOrNull(node->getRawName()),
108  baseType, node->getSizeInBits(), node->getAlignInBits(),
109  node->getOffsetInBits(), node->getDWARFAddressSpace(), extraData);
110 }
111 
112 DIStringTypeAttr DebugImporter::translateImpl(llvm::DIStringType *node) {
113  return DIStringTypeAttr::get(
114  context, node->getTag(), getStringAttrOrNull(node->getRawName()),
115  node->getSizeInBits(), node->getAlignInBits(),
116  translate(node->getStringLength()),
117  translateExpression(node->getStringLengthExp()),
118  translateExpression(node->getStringLocationExp()), node->getEncoding());
119 }
120 
121 DIFileAttr DebugImporter::translateImpl(llvm::DIFile *node) {
122  return DIFileAttr::get(context, node->getFilename(), node->getDirectory());
123 }
124 
125 DILabelAttr DebugImporter::translateImpl(llvm::DILabel *node) {
126  // Return nullptr if the scope or type is a cyclic dependency.
127  DIScopeAttr scope = translate(node->getScope());
128  if (node->getScope() && !scope)
129  return nullptr;
130  return DILabelAttr::get(context, scope,
131  getStringAttrOrNull(node->getRawName()),
132  translate(node->getFile()), node->getLine());
133 }
134 
135 DILexicalBlockAttr DebugImporter::translateImpl(llvm::DILexicalBlock *node) {
136  // Return nullptr if the scope or type is a cyclic dependency.
137  DIScopeAttr scope = translate(node->getScope());
138  if (node->getScope() && !scope)
139  return nullptr;
140  return DILexicalBlockAttr::get(context, scope, translate(node->getFile()),
141  node->getLine(), node->getColumn());
142 }
143 
144 DILexicalBlockFileAttr
145 DebugImporter::translateImpl(llvm::DILexicalBlockFile *node) {
146  // Return nullptr if the scope or type is a cyclic dependency.
147  DIScopeAttr scope = translate(node->getScope());
148  if (node->getScope() && !scope)
149  return nullptr;
150  return DILexicalBlockFileAttr::get(context, scope, translate(node->getFile()),
151  node->getDiscriminator());
152 }
153 
154 DIGlobalVariableAttr
155 DebugImporter::translateImpl(llvm::DIGlobalVariable *node) {
156  // Names of DIGlobalVariables can be empty. MLIR models them as null, instead
157  // of empty strings, so this special handling is necessary.
158  auto convertToStringAttr = [&](StringRef name) -> StringAttr {
159  if (name.empty())
160  return {};
161  return StringAttr::get(context, node->getName());
162  };
164  context, translate(node->getScope()),
165  convertToStringAttr(node->getName()),
166  convertToStringAttr(node->getLinkageName()), translate(node->getFile()),
167  node->getLine(), translate(node->getType()), node->isLocalToUnit(),
168  node->isDefinition(), node->getAlignInBits());
169 }
170 
171 DILocalVariableAttr DebugImporter::translateImpl(llvm::DILocalVariable *node) {
172  // Return nullptr if the scope or type is a cyclic dependency.
173  DIScopeAttr scope = translate(node->getScope());
174  if (node->getScope() && !scope)
175  return nullptr;
177  context, scope, getStringAttrOrNull(node->getRawName()),
178  translate(node->getFile()), node->getLine(), node->getArg(),
179  node->getAlignInBits(), translate(node->getType()),
180  symbolizeDIFlags(node->getFlags()).value_or(DIFlags::Zero));
181 }
182 
183 DIVariableAttr DebugImporter::translateImpl(llvm::DIVariable *node) {
184  return cast<DIVariableAttr>(translate(static_cast<llvm::DINode *>(node)));
185 }
186 
187 DIScopeAttr DebugImporter::translateImpl(llvm::DIScope *node) {
188  return cast<DIScopeAttr>(translate(static_cast<llvm::DINode *>(node)));
189 }
190 
191 DIModuleAttr DebugImporter::translateImpl(llvm::DIModule *node) {
192  return DIModuleAttr::get(
193  context, translate(node->getFile()), translate(node->getScope()),
194  getStringAttrOrNull(node->getRawName()),
195  getStringAttrOrNull(node->getRawConfigurationMacros()),
196  getStringAttrOrNull(node->getRawIncludePath()),
197  getStringAttrOrNull(node->getRawAPINotesFile()), node->getLineNo(),
198  node->getIsDecl());
199 }
200 
201 DINamespaceAttr DebugImporter::translateImpl(llvm::DINamespace *node) {
202  return DINamespaceAttr::get(context, getStringAttrOrNull(node->getRawName()),
203  translate(node->getScope()),
204  node->getExportSymbols());
205 }
206 
207 DIImportedEntityAttr
208 DebugImporter::translateImpl(llvm::DIImportedEntity *node) {
209  SmallVector<DINodeAttr> elements;
210  for (llvm::DINode *element : node->getElements()) {
211  assert(element && "expected a non-null element type");
212  elements.push_back(translate(element));
213  }
214 
216  context, node->getTag(), translate(node->getScope()),
217  translate(node->getEntity()), translate(node->getFile()), node->getLine(),
218  getStringAttrOrNull(node->getRawName()), elements);
219 }
220 
221 DISubprogramAttr DebugImporter::translateImpl(llvm::DISubprogram *node) {
222  // Only definitions require a distinct identifier.
224  if (node->isDistinct())
225  id = getOrCreateDistinctID(node);
226 
227  // Return nullptr if the scope or type is invalid.
228  DIScopeAttr scope = translate(node->getScope());
229  if (node->getScope() && !scope)
230  return nullptr;
231  std::optional<DISubprogramFlags> subprogramFlags =
232  symbolizeDISubprogramFlags(node->getSubprogram()->getSPFlags());
233  assert(subprogramFlags && "expected valid subprogram flags");
234  DISubroutineTypeAttr type = translate(node->getType());
235  if (node->getType() && !type)
236  return nullptr;
237 
238  // Convert the retained nodes but drop all of them if one of them is invalid.
239  SmallVector<DINodeAttr> retainedNodes;
240  for (llvm::DINode *retainedNode : node->getRetainedNodes())
241  retainedNodes.push_back(translate(retainedNode));
242  if (llvm::is_contained(retainedNodes, nullptr))
243  retainedNodes.clear();
244 
245  SmallVector<DINodeAttr> annotations;
246  // We currently only support `string` values for annotations on the MLIR side.
247  // Theoretically we could support other primitives, but LLVM is not using
248  // other types in practice.
249  if (llvm::DINodeArray rawAnns = node->getAnnotations(); rawAnns) {
250  for (size_t i = 0, e = rawAnns->getNumOperands(); i < e; ++i) {
251  const llvm::MDTuple *tuple = cast<llvm::MDTuple>(rawAnns->getOperand(i));
252  if (tuple->getNumOperands() != 2)
253  continue;
254  const llvm::MDString *name = cast<llvm::MDString>(tuple->getOperand(0));
255  const llvm::MDString *value =
256  dyn_cast<llvm::MDString>(tuple->getOperand(1));
257  if (name && value) {
258  annotations.push_back(DIAnnotationAttr::get(
259  context, StringAttr::get(context, name->getString()),
260  StringAttr::get(context, value->getString())));
261  }
262  }
263  }
264 
265  return DISubprogramAttr::get(context, id, translate(node->getUnit()), scope,
266  getStringAttrOrNull(node->getRawName()),
267  getStringAttrOrNull(node->getRawLinkageName()),
268  translate(node->getFile()), node->getLine(),
269  node->getScopeLine(), *subprogramFlags, type,
270  retainedNodes, annotations);
271 }
272 
273 DISubrangeAttr DebugImporter::translateImpl(llvm::DISubrange *node) {
274  auto getAttrOrNull = [&](llvm::DISubrange::BoundType data) -> Attribute {
275  if (data.isNull())
276  return nullptr;
277  if (auto *constInt = dyn_cast<llvm::ConstantInt *>(data))
278  return IntegerAttr::get(IntegerType::get(context, 64),
279  constInt->getSExtValue());
280  if (auto *expr = dyn_cast<llvm::DIExpression *>(data))
281  return translateExpression(expr);
282  if (auto *var = dyn_cast<llvm::DIVariable *>(data)) {
283  if (auto *local = dyn_cast<llvm::DILocalVariable>(var))
284  return translate(local);
285  if (auto *global = dyn_cast<llvm::DIGlobalVariable>(var))
286  return translate(global);
287  return nullptr;
288  }
289  return nullptr;
290  };
291  Attribute count = getAttrOrNull(node->getCount());
292  Attribute upperBound = getAttrOrNull(node->getUpperBound());
293  // Either count or the upper bound needs to be present. Otherwise, the
294  // metadata is invalid. The conversion might fail due to unsupported DI nodes.
295  if (!count && !upperBound)
296  return {};
297  return DISubrangeAttr::get(context, count,
298  getAttrOrNull(node->getLowerBound()), upperBound,
299  getAttrOrNull(node->getStride()));
300 }
301 
302 DICommonBlockAttr DebugImporter::translateImpl(llvm::DICommonBlock *node) {
303  return DICommonBlockAttr::get(context, translate(node->getScope()),
304  translate(node->getDecl()),
305  getStringAttrOrNull(node->getRawName()),
306  translate(node->getFile()), node->getLineNo());
307 }
308 
309 DIGenericSubrangeAttr
310 DebugImporter::translateImpl(llvm::DIGenericSubrange *node) {
311  auto getAttrOrNull =
313  if (data.isNull())
314  return nullptr;
315  if (auto *expr = dyn_cast<llvm::DIExpression *>(data))
316  return translateExpression(expr);
317  if (auto *var = dyn_cast<llvm::DIVariable *>(data)) {
318  if (auto *local = dyn_cast<llvm::DILocalVariable>(var))
319  return translate(local);
320  if (auto *global = dyn_cast<llvm::DIGlobalVariable>(var))
321  return translate(global);
322  return nullptr;
323  }
324  return nullptr;
325  };
326  Attribute count = getAttrOrNull(node->getCount());
327  Attribute upperBound = getAttrOrNull(node->getUpperBound());
328  Attribute lowerBound = getAttrOrNull(node->getLowerBound());
329  Attribute stride = getAttrOrNull(node->getStride());
330  // Either count or the upper bound needs to be present. Otherwise, the
331  // metadata is invalid.
332  if (!count && !upperBound)
333  return {};
334  return DIGenericSubrangeAttr::get(context, count, lowerBound, upperBound,
335  stride);
336 }
337 
338 DISubroutineTypeAttr
339 DebugImporter::translateImpl(llvm::DISubroutineType *node) {
341  for (llvm::DIType *type : node->getTypeArray()) {
342  if (!type) {
343  // A nullptr entry may appear at the beginning or the end of the
344  // subroutine types list modeling either a void result type or the type of
345  // a variadic argument. Translate the nullptr to an explicit
346  // DINullTypeAttr since the attribute list cannot contain a nullptr entry.
347  types.push_back(DINullTypeAttr::get(context));
348  continue;
349  }
350  types.push_back(translate(type));
351  }
352  // Return nullptr if any of the types is invalid.
353  if (llvm::is_contained(types, nullptr))
354  return nullptr;
355  return DISubroutineTypeAttr::get(context, node->getCC(), types);
356 }
357 
358 DITypeAttr DebugImporter::translateImpl(llvm::DIType *node) {
359  return cast<DITypeAttr>(translate(static_cast<llvm::DINode *>(node)));
360 }
361 
363  if (!node)
364  return nullptr;
365 
366  // Check for a cached instance.
367  auto cacheEntry = cache.lookupOrInit(node);
368  if (std::optional<DINodeAttr> result = cacheEntry.get())
369  return *result;
370 
371  // Convert the debug metadata if possible.
372  auto translateNode = [this](llvm::DINode *node) -> DINodeAttr {
373  if (auto *casted = dyn_cast<llvm::DIBasicType>(node))
374  return translateImpl(casted);
375  if (auto *casted = dyn_cast<llvm::DICommonBlock>(node))
376  return translateImpl(casted);
377  if (auto *casted = dyn_cast<llvm::DICompileUnit>(node))
378  return translateImpl(casted);
379  if (auto *casted = dyn_cast<llvm::DICompositeType>(node))
380  return translateImpl(casted);
381  if (auto *casted = dyn_cast<llvm::DIDerivedType>(node))
382  return translateImpl(casted);
383  if (auto *casted = dyn_cast<llvm::DIStringType>(node))
384  return translateImpl(casted);
385  if (auto *casted = dyn_cast<llvm::DIFile>(node))
386  return translateImpl(casted);
387  if (auto *casted = dyn_cast<llvm::DIGlobalVariable>(node))
388  return translateImpl(casted);
389  if (auto *casted = dyn_cast<llvm::DIImportedEntity>(node))
390  return translateImpl(casted);
391  if (auto *casted = dyn_cast<llvm::DILabel>(node))
392  return translateImpl(casted);
393  if (auto *casted = dyn_cast<llvm::DILexicalBlock>(node))
394  return translateImpl(casted);
395  if (auto *casted = dyn_cast<llvm::DILexicalBlockFile>(node))
396  return translateImpl(casted);
397  if (auto *casted = dyn_cast<llvm::DILocalVariable>(node))
398  return translateImpl(casted);
399  if (auto *casted = dyn_cast<llvm::DIModule>(node))
400  return translateImpl(casted);
401  if (auto *casted = dyn_cast<llvm::DINamespace>(node))
402  return translateImpl(casted);
403  if (auto *casted = dyn_cast<llvm::DISubprogram>(node))
404  return translateImpl(casted);
405  if (auto *casted = dyn_cast<llvm::DISubrange>(node))
406  return translateImpl(casted);
407  if (auto *casted = dyn_cast<llvm::DIGenericSubrange>(node))
408  return translateImpl(casted);
409  if (auto *casted = dyn_cast<llvm::DISubroutineType>(node))
410  return translateImpl(casted);
411  return nullptr;
412  };
413  if (DINodeAttr attr = translateNode(node)) {
414  // If this node was repeated, lookup its recursive ID and assign it to the
415  // base result.
416  if (cacheEntry.wasRepeated()) {
417  DistinctAttr recId = nodeToRecId.lookup(node);
418  auto recType = cast<DIRecursiveTypeAttrInterface>(attr);
419  attr = cast<DINodeAttr>(recType.withRecId(recId));
420  }
421  cacheEntry.resolve(attr);
422  return attr;
423  }
424  cacheEntry.resolve(nullptr);
425  return nullptr;
426 }
427 
428 /// Get the `getRecSelf` constructor for the translated type of `node` if its
429 /// translated DITypeAttr supports recursion. Otherwise, returns nullptr.
430 static function_ref<DIRecursiveTypeAttrInterface(DistinctAttr)>
431 getRecSelfConstructor(llvm::DINode *node) {
432  using CtorType = function_ref<DIRecursiveTypeAttrInterface(DistinctAttr)>;
434  .Case([&](llvm::DICompositeType *) {
435  return CtorType(DICompositeTypeAttr::getRecSelf);
436  })
437  .Case([&](llvm::DISubprogram *) {
438  return CtorType(DISubprogramAttr::getRecSelf);
439  })
440  .Default(CtorType());
441 }
442 
443 std::optional<DINodeAttr> DebugImporter::createRecSelf(llvm::DINode *node) {
444  auto recSelfCtor = getRecSelfConstructor(node);
445  if (!recSelfCtor)
446  return std::nullopt;
447 
448  // The original node may have already been assigned a recursive ID from
449  // a different self-reference. Use that if possible.
450  DistinctAttr recId = nodeToRecId.lookup(node);
451  if (!recId) {
452  recId = DistinctAttr::create(UnitAttr::get(context));
453  nodeToRecId[node] = recId;
454  }
455  DIRecursiveTypeAttrInterface recSelf = recSelfCtor(recId);
456  return cast<DINodeAttr>(recSelf);
457 }
458 
459 //===----------------------------------------------------------------------===//
460 // Locations
461 //===----------------------------------------------------------------------===//
462 
463 Location DebugImporter::translateLoc(llvm::DILocation *loc) {
464  if (!loc)
465  return UnknownLoc::get(context);
466 
467  // Get the file location of the instruction.
468  Location result = FileLineColLoc::get(context, loc->getFilename(),
469  loc->getLine(), loc->getColumn());
470 
471  // Add scope information.
472  assert(loc->getScope() && "expected non-null scope");
473  result = FusedLocWith<DIScopeAttr>::get({result}, translate(loc->getScope()),
474  context);
475 
476  // Add call site information, if available.
477  if (llvm::DILocation *inlinedAt = loc->getInlinedAt())
478  result = CallSiteLoc::get(result, translateLoc(inlinedAt));
479 
480  return result;
481 }
482 
483 DIExpressionAttr DebugImporter::translateExpression(llvm::DIExpression *node) {
484  if (!node)
485  return nullptr;
486 
488 
489  // Begin processing the operations.
490  for (const llvm::DIExpression::ExprOperand &op : node->expr_ops()) {
491  SmallVector<uint64_t> operands;
492  operands.reserve(op.getNumArgs());
493  for (const auto &i : llvm::seq(op.getNumArgs()))
494  operands.push_back(op.getArg(i));
495  const auto attr = DIExpressionElemAttr::get(context, op.getOp(), operands);
496  ops.push_back(attr);
497  }
498  return DIExpressionAttr::get(context, ops);
499 }
500 
502  llvm::DIGlobalVariableExpression *node) {
504  context, translate(node->getVariable()),
505  translateExpression(node->getExpression()));
506 }
507 
508 StringAttr DebugImporter::getStringAttrOrNull(llvm::MDString *stringNode) {
509  if (!stringNode)
510  return StringAttr();
511  return StringAttr::get(context, stringNode->getString());
512 }
513 
514 DistinctAttr DebugImporter::getOrCreateDistinctID(llvm::DINode *node) {
515  DistinctAttr &id = nodeToDistinctAttr[node];
516  if (!id)
517  id = DistinctAttr::create(UnitAttr::get(context));
518  return id;
519 }
static function_ref< DIRecursiveTypeAttrInterface(DistinctAttr)> getRecSelfConstructor(llvm::DINode *node)
Get the getRecSelf constructor for the translated type of node if its translated DITypeAttr supports ...
Attributes are known-constant values of operations.
Definition: Attributes.h:25
An attribute that associates a referenced attribute with a unique identifier.
static DistinctAttr create(Attribute referencedAttr)
Creates a distinct attribute that associates a referenced attribute with a unique identifier.
static FileLineColLoc get(StringAttr filename, unsigned line, unsigned column)
Definition: Location.cpp:157
This class represents the base attribute for all debug info attributes.
Definition: LLVMAttrs.h:29
This class represents a LLVM attribute that describes a debug info scope.
Definition: LLVMAttrs.h:38
This class represents a LLVM attribute that describes a debug info type.
Definition: LLVMAttrs.h:57
This class represents a LLVM attribute that describes a debug info variable.
Definition: LLVMAttrs.h:66
DINodeAttr translate(llvm::DINode *node)
Translates the given LLVM debug metadata to MLIR.
DIExpressionAttr translateExpression(llvm::DIExpression *node)
Translates the LLVM DWARF expression metadata to MLIR.
DIGlobalVariableExpressionAttr translateGlobalVariableExpression(llvm::DIGlobalVariableExpression *node)
Translates the LLVM DWARF global variable expression metadata to MLIR.
Location translateLoc(llvm::DILocation *loc)
Translates the given LLVM debug location to an MLIR location.
Location translateFuncLocation(llvm::Function *func)
Translates the debug information for the given function into a Location.
DebugImporter(ModuleOp mlirModule, bool dropDICompositeTypeElements)
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:76
The OpAsmOpInterface, see OpAsmInterface.td for more details.
Definition: CallGraph.h:229
BoundType
The type of bound: equal, lower bound or upper bound.
Include the generated interface declarations.
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...