unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
api-designbackwards-compatibilityrefactoring

[mlir][LLVM] Preserve !associated and !absolute_symbol on llvm.mlir.global

Project: llvm/llvm-project | Language: C++ | Concepts: api-design, backwards-compatibility, refactoring | Difficulty: mid
Link: https://github.com/llvm/llvm-project/pull/218839

The problem

The MLIR LLVM dialect did not preserve the LLVM IR !associated and !absolute_symbol metadata on globals when importing/exporting between MLIR and LLVM IR. This metadata is important for correct semantics (e.g., ELF SHF_LINK_ORDER for !associated), but was being lost in translation.

Why it's non-trivial

A naive fix would just add new attributes, but:

  • The !associated metadata refers to other symbols, which may not exist yet when a global is first imported/exported. This requires careful ordering to avoid unresolved references.
  • The !absolute_symbol metadata is a list of integer range pairs, which must be validated for type and structure.
  • The verifier must reject malformed or self-referential metadata to prevent invalid IR.
  • Both import and export logic must be updated, and round-tripping must be tested.

The approach

1. Add new optional attributes to the operation definition:

+    OptionalAttr<FlatSymbolRefAttr>:$associated,
+    OptionalAttr<ArrayAttr>:$absolute_symbol,

These lines add two new optional attributes to the llvm.mlir.global operation: associated (a symbol reference), and absolute_symbol (an array of integers).

2. Document the new attributes and provide examples:

+    The optional `associated` attribute models LLVM IR `!associated` metadata
+    on a global. It is a symbol reference to another global object (or an
+    alias of one) and is used to emit `SHF_LINK_ORDER` on ELF.
+
+    The optional `absolute_symbol` attribute models LLVM IR `!absolute_symbol`
+    metadata. It is an array of integer attributes forming one or more
+    `[lower, upper)` range pairs for the global's address, using the same
+    encoding as LLVM `range` metadata. The pair `[-1, -1]` represents the
+    full set.

This ensures users and developers know how to use the new attributes and what they mean.

3. Add verification logic to reject invalid metadata:

+  if (FlatSymbolRefAttr associated = getAssociatedAttr()) {
+    if (associated.getValue() == getSymName())
+      return emitOpError("associated cannot refer to the global itself");
+  }
+
+  if (ArrayAttr absSym = getAbsoluteSymbolAttr()) {
+    if (absSym.empty() || absSym.size() % 2 != 0)
+      return emitOpError(
+          "absolute_symbol must contain one or more integer range pairs");
+    Type pairType;
+    for (Attribute attr : absSym) {
+      auto intAttr = dyn_cast<IntegerAttr>(attr);
+      if (!intAttr)
+        return emitOpError("absolute_symbol operands must be integers");
+      if (!pairType)
+        pairType = intAttr.getType();
+      else if (intAttr.getType() != pairType)
+        return emitOpError("absolute_symbol range pair types must match");
+    }
+  }

This code, in the verifier, ensures that associated does not refer to itself and that absolute_symbol is a non-empty, even-length array of integers of the same type.

4. Import metadata from LLVM IR into MLIR attributes:

+  if (llvm::MDNode *associatedMD =
+          globalVar->getMetadata(llvm::LLVMContext::MD_associated)) {
+    FlatSymbolRefAttr symbolRef;
+    if (associatedMD->getNumOperands() == 1)
+      symbolRef =
+          getMetadataOperandSymbolRef(associatedMD->getOperand(0).get());
+    if (!symbolRef) {
+      emitWarning(globalOp.getLoc()) << "unhandled associated metadata: "
+                                     << diagMD(associatedMD, llvmModule.get())
+                                     << " on " << diag(*globalVar);
+    } else {
+      globalOp.setAssociatedAttr(symbolRef);
+    }
+  }
+
+  if (llvm::MDNode *absSymMD =
+          globalVar->getMetadata(llvm::LLVMContext::MD_absolute_symbol)) {
+    unsigned numOps = absSymMD->getNumOperands();
+    if (numOps >= 2 && numOps % 2 == 0) {
+      SmallVector<Attribute> rangeAttrs;
+      rangeAttrs.reserve(numOps);
+
+      for (const llvm::MDOperand &op : absSymMD->operands()) {
+        auto *constInt = llvm::mdconst::dyn_extract<llvm::ConstantInt>(op);
+        if (!constInt)
+          break;
+
+        auto intType = IntegerType::get(context, constInt->getBitWidth());
+        rangeAttrs.push_back(IntegerAttr::get(intType, constInt->getValue()));
+      }
+
+      if (rangeAttrs.size() == numOps)
+        globalOp.setAbsoluteSymbolAttr(ArrayAttr::get(context, rangeAttrs));
+    }
+  }

This logic, in the import code, extracts the metadata from LLVM IR and sets the corresponding MLIR attributes, after checking for validity.

5. Export metadata from MLIR attributes to LLVM IR, after all symbols are defined:

+LogicalResult ModuleTranslation::convertGlobalMetadata() {
+  for (auto op : getModuleBody(mlirModule).getOps<LLVM::GlobalOp>()) {
+    auto *var = cast<llvm::GlobalVariable>(lookupGlobal(op));
+    if (FlatSymbolRefAttr associated = op.getAssociatedAttr()) {
+      FailureOr<llvm::Metadata *> md =
+          convertSymbolRefToMetadata(associated, [&]() {
+            return op.emitError("failed to convert associated metadata");
+          });
+      if (failed(md))
+        return failure();
+      var->setMetadata(llvm::LLVMContext::MD_associated,
+                       llvm::MDNode::get(var->getContext(), *md));
+    }
+
+    if (ArrayAttr absSym = op.getAbsoluteSymbolAttr()) {
+      SmallVector<llvm::Metadata *> mdOps;
+      llvm::LLVMContext &ctx = var->getContext();
+      mdOps.reserve(absSym.size());
+      for (Attribute attr : absSym) {
+        auto intAttr = cast<IntegerAttr>(attr);
+        llvm::IntegerType *ty = llvm::IntegerType::get(
+            ctx, intAttr.getType().getIntOrFloatBitWidth());
+        mdOps.push_back(llvm::ConstantAsMetadata::get(
+            llvm::ConstantInt::get(ty, intAttr.getValue())));
+      }
+      var->setMetadata(llvm::LLVMContext::MD_absolute_symbol,
+                       llvm::MDNode::get(ctx, mdOps));
+    }
+  }
+
+  return success();
+}

This function attaches the metadata to LLVM IR globals after all global objects are defined, ensuring symbol references can be resolved.

6. Ensure the new export step is called in the right order:

@@ -2694,6 +2733,8 @@ mlir::translateModuleToLLVMIR(Operation *module, llvm::LLVMContext &llvmContext,
     return nullptr;
   if (failed(translator.convertIFuncs()))
     return nullptr;
+  if (failed(translator.convertGlobalMetadata()))
+    return nullptr;
   if (failed(translator.convertFunctionMetadata()))
     return nullptr;

This ensures that global metadata is attached after all globals, aliases, and ifuncs are created but before function metadata is handled.

7. Add test cases for round-tripping and verification:

+// CHECK: llvm.mlir.global external @associated_target(0 : i32) {addr_space = 0 : i32} : i32
+// CHECK: llvm.mlir.global external @associated_global(0 : i32) {addr_space = 0 : i32, associated = @associated_target} : i32
+llvm.mlir.global external @associated_target(0 : i32) : i32
+llvm.mlir.global external @associated_global(0 : i32) {associated = @associated_target} : i32
+
+// CHECK: llvm.mlir.global external @absolute_symbol_global() {absolute_symbol = [0, 42], addr_space = 0 : i32} : i8
+llvm.mlir.global external @absolute_symbol_global() {absolute_symbol = [0 : i64, 42 : i64]} : i8

These test snippets ensure the new attributes are preserved and round-trip correctly.

From the review thread

No notable review discussion on this PR.

Takeaways

  • When adding attributes that reference other symbols, ensure they are attached only after all referenced symbols are defined to avoid unresolved references.
  • Always validate new attributes in the verifier to catch malformed or self-referential metadata early.
  • When extending IR import/export logic, update both directions and provide round-trip tests to ensure correctness and backward compatibility.