[Mlir-commits] [mlir] [MLIR][LLVM] Preserve pointer-valued metadata operands on import (PR #215743)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Wed Aug 12 00:11:04 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-llvm

Author: Hamza Qureshi (hamzaqureshi5)

<details>
<summary>Changes</summary>

convertMetadataToAttrImpl only modelled ConstantInt operands wrapped in a ConstantAsMetadata, so any metadata node containing a pointer constant could not be represented and the whole node was rejected.

Add #llvm.md_null and #llvm.md_addrspacecast to model ConstantPointerNull and addrspacecast constant expressions, keeping the address space so that `ptr null` and `ptr addrspace(1) null` stay distinct. MDAddrSpaceCastAttr verifies that its operand is itself pointer-valued metadata.

Global values are constants, so ValueAsMetadata::get wraps them in a ConstantAsMetadata and they never reached the ValueAsMetadata case. Match them in the ConstantAsMetadata case instead, which also generalizes the existing function-only handling to any named global value and makes the addrspacecast operand representable.

Mirror both attributes in ModuleTranslation::convertMetadataAttr so the original LLVM IR is reconstructed on export.

Fixes #<!-- -->215550

---
Full diff: https://github.com/llvm/llvm-project/pull/215743.diff


8 Files Affected:

- (modified) mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td (+42-3) 
- (modified) mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp (+16) 
- (modified) mlir/lib/Target/LLVMIR/ModuleImport.cpp (+37-10) 
- (modified) mlir/lib/Target/LLVMIR/ModuleTranslation.cpp (+17) 
- (modified) mlir/test/Dialect/LLVMIR/invalid.mlir (+16) 
- (modified) mlir/test/Dialect/LLVMIR/roundtrip.mlir (+8) 
- (modified) mlir/test/Target/LLVMIR/Import/intrinsic-unregistered.ll (+86) 
- (modified) mlir/test/Target/LLVMIR/llvmir-named-metadata.mlir (+12) 


``````````diff
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
index a5e4bc52c84a6..7b15bfe37193f 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
@@ -1814,12 +1814,50 @@ def LLVM_MDGlobalValueAttr : LLVM_Attr<"MDGlobalValue", "md_global_value"> {
   let assemblyFormat = "`<` $name `>`";
 }
 
+def LLVM_MDNullAttr : LLVM_Attr<"MDNull", "md_null"> {
+  let summary = "LLVM null-pointer-as-metadata";
+  let description = [{
+    Wraps a null pointer constant as an LLVM metadata node, corresponding to
+    `llvm::ConstantAsMetadata` wrapping a `llvm::ConstantPointerNull` in LLVM
+    IR. `addressSpace` is the address space of the null pointer, so that
+    `ptr null` and `ptr addrspace(1) null` remain distinguishable.
+
+    Example:
+    ```mlir
+    #llvm.md_null<0>
+    ```
+  }];
+  let parameters = (ins "unsigned":$addressSpace);
+  let assemblyFormat = "`<` $addressSpace `>`";
+}
+
+def LLVM_MDAddrSpaceCastAttr
+    : LLVM_Attr<"MDAddrSpaceCast", "md_addrspacecast"> {
+  let summary = "LLVM addrspacecast-constant-expression-as-metadata";
+  let description = [{
+    Wraps an `addrspacecast` constant expression as an LLVM metadata node,
+    corresponding to `llvm::ConstantAsMetadata` wrapping a `llvm::ConstantExpr`
+    with an `addrspacecast` opcode in LLVM IR. `arg` is the metadata attribute
+    for the pointer being cast and `addressSpace` is the address space of the
+    resulting pointer.
+
+    Example:
+    ```mlir
+    #llvm.md_addrspacecast<#llvm.md_global_value<@my_global>, 0>
+    ```
+  }];
+  let parameters = (ins "Attribute":$arg, "unsigned":$addressSpace);
+  let assemblyFormat = "`<` $arg `,` $addressSpace `>`";
+  let genVerifyDecl = 1;
+}
+
 def LLVM_MDNodeAttr : LLVM_Attr<"MDNode", "md_node"> {
   let summary = "LLVM metadata node";
   let description = [{
     Represents an LLVM metadata node. The operands
     can be any combination of metadata attributes: `#llvm.md_string`,
-    `#llvm.md_const`, `#llvm.md_global_value`, or nested `#llvm.md_node`.
+    `#llvm.md_const`, `#llvm.md_global_value`, `#llvm.md_null`,
+    `#llvm.md_addrspacecast`, or nested `#llvm.md_node`.
 
     Example:
     ```mlir
@@ -1837,7 +1875,8 @@ def LLVM_MDNodeArrayAttr
 
 def LLVM_AnyMDAttr : AnyAttrOf<[
     LLVM_MDStringAttr, LLVM_MDConstantAttr, LLVM_MDGlobalValueAttr,
-    LLVM_MDNodeAttr],
-    "LLVM metadata attribute (md_string, md_const, md_global_value, or md_node)">;
+    LLVM_MDNullAttr, LLVM_MDAddrSpaceCastAttr, LLVM_MDNodeAttr],
+    "LLVM metadata attribute (md_string, md_const, md_global_value, md_null, "
+    "md_addrspacecast, or md_node)">;
 
 #endif // LLVMIR_ATTRDEFS
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
index 3b26aa2621370..b3a7d2e46030d 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
@@ -598,3 +598,19 @@ ModFlagBehavior ModuleFlagAttr::getModuleFlagBehavior() const {
 StringAttr ModuleFlagAttr::getModuleFlagKey() const { return getKey(); }
 
 Attribute ModuleFlagAttr::getModuleFlagValue() const { return getValue(); }
+
+//===----------------------------------------------------------------------===//
+// MDAddrSpaceCastAttr
+//===----------------------------------------------------------------------===//
+
+LogicalResult
+MDAddrSpaceCastAttr::verify(function_ref<InFlightDiagnostic()> emitError,
+                            Attribute arg, unsigned addressSpace) {
+  // `addrspacecast` operates on pointers, so the operand must be a metadata
+  // attribute that models a pointer-typed constant.
+  if (!isa<MDGlobalValueAttr, MDNullAttr, MDAddrSpaceCastAttr>(arg))
+    return emitError() << "expected #llvm.md_global_value, #llvm.md_null, or "
+                          "#llvm.md_addrspacecast operand, but got "
+                       << arg;
+  return success();
+}
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index 48e1b7d26ac67..d775457e94646 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -168,18 +168,45 @@ static Attribute convertMetadataToAttrImpl(
   if (auto *mdStr = dyn_cast<llvm::MDString>(md))
     return MDStringAttr::get(ctx, StringAttr::get(ctx, mdStr->getString()));
   if (auto *cam = dyn_cast<llvm::ConstantAsMetadata>(md)) {
-    auto *ci = dyn_cast<llvm::ConstantInt>(cam->getValue());
-    if (!ci)
-      return {};
-    auto intType = IntegerType::get(ctx, ci->getBitWidth());
-    return MDConstantAttr::get(ctx, IntegerAttr::get(intType, ci->getValue()));
+    llvm::Constant *constant = cam->getValue();
+    if (auto *ci = dyn_cast<llvm::ConstantInt>(constant)) {
+      auto intType = IntegerType::get(ctx, ci->getBitWidth());
+      return MDConstantAttr::get(ctx, IntegerAttr::get(intType, ci->getValue()));
+    }
+    // Global values are constants, so `llvm::ValueAsMetadata::get` wraps them
+    // in a `llvm::ConstantAsMetadata` and they are matched here rather than by
+    // the `llvm::ValueAsMetadata` case below.
+    if (auto *global = dyn_cast<llvm::GlobalValue>(constant)) {
+      StringRef name = global->getName();
+      // Nameless globals have no symbol to reference.
+      if (name.empty())
+        return {};
+      return MDGlobalValueAttr::get(ctx, FlatSymbolRefAttr::get(ctx, name));
+    }
+    if (auto *nullPtr = dyn_cast<llvm::ConstantPointerNull>(constant))
+      return MDNullAttr::get(ctx, nullPtr->getType()->getPointerAddressSpace());
+    if (auto *constExpr = dyn_cast<llvm::ConstantExpr>(constant)) {
+      // Only `addrspacecast` is modelled; other constant expressions have no
+      // metadata-attribute counterpart.
+      if (constExpr->getOpcode() != llvm::Instruction::AddrSpaceCast)
+        return {};
+      Attribute argAttr = convertMetadataToAttrImpl(
+          ctx, llvm::ConstantAsMetadata::get(constExpr->getOperand(0)), path,
+          attrMap);
+      if (!argAttr)
+        return {};
+      return MDAddrSpaceCastAttr::get(
+          ctx, argAttr, constExpr->getType()->getPointerAddressSpace());
+    }
+    return {};
   }
   if (auto *vam = dyn_cast<llvm::ValueAsMetadata>(md)) {
-    auto *fn = dyn_cast<llvm::Function>(vam->getValue());
-    if (!fn)
-      return {};
-    return MDGlobalValueAttr::get(ctx,
-                                  FlatSymbolRefAttr::get(ctx, fn->getName()));
+    // Constants, including global values, are handled by the
+    // `llvm::ConstantAsMetadata` case above. Anything left here is a local
+    // value, which has no metadata-attribute counterpart.
+    assert(!isa<llvm::Constant>(vam->getValue()) &&
+           "constants should be matched as ConstantAsMetadata");
+    return {};
   }
   if (auto *node = dyn_cast<llvm::MDNode>(md)) {
     // Metadata attributes cannot preserve distinctness, so bail out.
diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
index 536327e64bb9f..b87a581a5185e 100644
--- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
@@ -1615,6 +1615,23 @@ FailureOr<llvm::Metadata *> ModuleTranslation::convertMetadataAttr(
         return emitError() << "could not resolve metadata reference '"
                            << a.getName() << "'";
       })
+      .Case([&](MDNullAttr a) -> FailureOr<llvm::Metadata *> {
+        return llvm::ConstantAsMetadata::get(llvm::ConstantPointerNull::get(
+            llvm::PointerType::get(llvmContext, a.getAddressSpace())));
+      })
+      .Case([&](MDAddrSpaceCastAttr a) -> FailureOr<llvm::Metadata *> {
+        FailureOr<llvm::Metadata *> arg =
+            convertMetadataAttr(a.getArg(), emitError);
+        if (failed(arg))
+          return failure();
+        // The verifier restricts the operand to pointer-valued metadata
+        // attributes, all of which translate to a ConstantAsMetadata.
+        auto *argAsMD = cast<llvm::ConstantAsMetadata>(*arg);
+        return llvm::ConstantAsMetadata::get(
+            llvm::ConstantExpr::getAddrSpaceCast(
+                argAsMD->getValue(),
+                llvm::PointerType::get(llvmContext, a.getAddressSpace())));
+      })
       .Case([&](MDNodeAttr a) -> FailureOr<llvm::Metadata *> {
         SmallVector<llvm::Metadata *> operands;
         for (Attribute operand : a.getOperands()) {
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index 6265f67e594d0..2e0cbda9fa35d 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -2198,3 +2198,19 @@ llvm.mlir.global internal thread_local(invalid) constant @thread_local(42 : i32)
 // expected-error at +1{{expected ')'}}
 llvm.mlir.global internal thread_local(generaldynamic, localexec) constant @thread_local(42 : i32) : i32
 
+
+// -----
+
+llvm.func @md_addrspacecast_bad_operand() {
+  // expected-error at +1{{expected #llvm.md_global_value, #llvm.md_null, or #llvm.md_addrspacecast operand, but got #llvm.md_string<"not a pointer">}}
+  %0 = llvm.mlir.metadata_as_value #llvm.md_addrspacecast<#llvm.md_string<"not a pointer">, 0>
+  llvm.return
+}
+
+// -----
+
+llvm.func @md_addrspacecast_int_operand() {
+  // expected-error at +1{{expected #llvm.md_global_value, #llvm.md_null, or #llvm.md_addrspacecast operand, but got #llvm.md_const<42 : i32>}}
+  %0 = llvm.mlir.metadata_as_value #llvm.md_addrspacecast<#llvm.md_const<42 : i32>, 0>
+  llvm.return
+}
diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
index 0f268cf6a5fe7..f4805c5541f91 100644
--- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir
+++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
@@ -1165,6 +1165,14 @@ llvm.func @metadata_as_value_shapes() {
   %2 = llvm.mlir.metadata_as_value #llvm.md_global_value<@md_kernel>
   // CHECK: %{{.*}} = llvm.mlir.metadata_as_value #llvm.md_node<#llvm.md_string<"sp">>
   %3 = llvm.mlir.metadata_as_value #llvm.md_node<#llvm.md_string<"sp">>
+  // CHECK: %{{.*}} = llvm.mlir.metadata_as_value #llvm.md_null<0>
+  %4 = llvm.mlir.metadata_as_value #llvm.md_null<0>
+  // CHECK: %{{.*}} = llvm.mlir.metadata_as_value #llvm.md_null<1>
+  %5 = llvm.mlir.metadata_as_value #llvm.md_null<1>
+  // CHECK: %{{.*}} = llvm.mlir.metadata_as_value #llvm.md_addrspacecast<#llvm.md_global_value<@md_kernel>, 0>
+  %6 = llvm.mlir.metadata_as_value #llvm.md_addrspacecast<#llvm.md_global_value<@md_kernel>, 0>
+  // CHECK: %{{.*}} = llvm.mlir.metadata_as_value #llvm.md_addrspacecast<#llvm.md_null<1>, 0>
+  %7 = llvm.mlir.metadata_as_value #llvm.md_addrspacecast<#llvm.md_null<1>, 0>
   llvm.return
 }
 
diff --git a/mlir/test/Target/LLVMIR/Import/intrinsic-unregistered.ll b/mlir/test/Target/LLVMIR/Import/intrinsic-unregistered.ll
index e5d6a4391b124..672980af629db 100644
--- a/mlir/test/Target/LLVMIR/Import/intrinsic-unregistered.ll
+++ b/mlir/test/Target/LLVMIR/Import/intrinsic-unregistered.ll
@@ -115,3 +115,89 @@ define i32 @read_named_register() {
 }
 
 !0 = !{!"sp"}
+
+; // -----
+
+; Pointer constants used as metadata operands must be preserved.
+
+declare i32 @llvm.read_register.i32(metadata)
+
+; CHECK-LABEL: llvm.func @read_null_pointer_metadata
+define i32 @read_null_pointer_metadata() {
+  ; CHECK: %[[MD:.*]] = llvm.mlir.metadata_as_value #llvm.md_null<0>
+  ; CHECK: %{{.*}} = llvm.call_intrinsic "llvm.read_register.i32"(%[[MD]]) : (!llvm.metadata) -> i32
+  %r = call i32 @llvm.read_register.i32(metadata !0)
+  ret i32 %r
+}
+
+!0 = !{ptr null}
+
+; // -----
+
+; The address space of a null pointer operand must be preserved.
+
+declare i32 @llvm.read_register.i32(metadata)
+
+; CHECK-LABEL: llvm.func @read_null_pointer_addrspace_metadata
+define i32 @read_null_pointer_addrspace_metadata() {
+  ; CHECK: %[[MD:.*]] = llvm.mlir.metadata_as_value #llvm.md_null<1>
+  ; CHECK: %{{.*}} = llvm.call_intrinsic "llvm.read_register.i32"(%[[MD]]) : (!llvm.metadata) -> i32
+  %r = call i32 @llvm.read_register.i32(metadata !0)
+  ret i32 %r
+}
+
+!0 = !{ptr addrspace(1) null}
+
+; // -----
+
+; An addrspacecast constant expression operand must be preserved.
+
+ at g = addrspace(1) global i32 0
+
+declare i32 @llvm.read_register.i32(metadata)
+
+; CHECK-LABEL: llvm.func @read_addrspacecast_metadata
+define i32 @read_addrspacecast_metadata() {
+  ; CHECK: %[[MD:.*]] = llvm.mlir.metadata_as_value #llvm.md_addrspacecast<#llvm.md_global_value<@g>, 0>
+  ; CHECK: %{{.*}} = llvm.call_intrinsic "llvm.read_register.i32"(%[[MD]]) : (!llvm.metadata) -> i32
+  %r = call i32 @llvm.read_register.i32(metadata !0)
+  ret i32 %r
+}
+
+!0 = !{ptr addrspacecast (ptr addrspace(1) @g to ptr)}
+
+; // -----
+
+; A global value operand reached through ConstantAsMetadata must be preserved.
+
+ at gv = global i32 0
+
+declare i32 @llvm.read_register.i32(metadata)
+
+; CHECK-LABEL: llvm.func @read_global_value_metadata
+define i32 @read_global_value_metadata() {
+  ; CHECK: %[[MD:.*]] = llvm.mlir.metadata_as_value #llvm.md_global_value<@gv>
+  ; CHECK: %{{.*}} = llvm.call_intrinsic "llvm.read_register.i32"(%[[MD]]) : (!llvm.metadata) -> i32
+  %r = call i32 @llvm.read_register.i32(metadata !0)
+  ret i32 %r
+}
+
+!0 = !{ptr @gv}
+
+; // -----
+
+; Pointer constants nested inside a multi-operand metadata node.
+
+ at g = addrspace(1) global i32 0
+
+declare i32 @llvm.read_register.i32(metadata)
+
+; CHECK-LABEL: llvm.func @read_pointer_constants_in_node
+define i32 @read_pointer_constants_in_node() {
+  ; CHECK: %[[MD:.*]] = llvm.mlir.metadata_as_value #llvm.md_node<#llvm.md_null<0>, #llvm.md_addrspacecast<#llvm.md_global_value<@g>, 0>>
+  ; CHECK: %{{.*}} = llvm.call_intrinsic "llvm.read_register.i32"(%[[MD]]) : (!llvm.metadata) -> i32
+  %r = call i32 @llvm.read_register.i32(metadata !0)
+  ret i32 %r
+}
+
+!0 = !{ptr null, ptr addrspacecast (ptr addrspace(1) @g to ptr)}
diff --git a/mlir/test/Target/LLVMIR/llvmir-named-metadata.mlir b/mlir/test/Target/LLVMIR/llvmir-named-metadata.mlir
index abb6043b630cb..14769209930b6 100644
--- a/mlir/test/Target/LLVMIR/llvmir-named-metadata.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir-named-metadata.mlir
@@ -6,6 +6,7 @@
 // CHECK: !foo.language_version = !{![[LANG:[0-9]+]]}
 // CHECK: !foo.kernel = !{![[KERNEL:[0-9]+]]}
 // CHECK: !foo.global_refs = !{![[GLOBAL_REFS:[0-9]+]]}
+// CHECK: !foo.pointer_constants = !{![[PTR_CONSTS:[0-9]+]]}
 
 llvm.func @my_kernel() {
   llvm.return
@@ -74,3 +75,14 @@ llvm.named_metadata "foo.global_refs" [
     #llvm.md_global_value<@metadata_ifunc>>
 ]
 // CHECK-DAG: ![[GLOBAL_REFS]] = !{ptr @metadata_global, ptr @metadata_alias, ptr @metadata_ifunc}
+
+llvm.mlir.global external @md_addrspace_global(0 : i32) {addr_space = 1 : i32} : i32
+
+llvm.named_metadata "foo.pointer_constants" [
+  #llvm.md_node<
+    #llvm.md_null<0>,
+    #llvm.md_null<1>,
+    #llvm.md_addrspacecast<#llvm.md_global_value<@md_addrspace_global>, 0>,
+    #llvm.md_addrspacecast<#llvm.md_null<1>, 0>>
+]
+// CHECK-DAG: ![[PTR_CONSTS]] = !{ptr null, ptr addrspace(1) null, ptr addrspacecast (ptr addrspace(1) @md_addrspace_global to ptr), ptr addrspacecast (ptr addrspace(1) null to ptr)}

``````````

</details>


https://github.com/llvm/llvm-project/pull/215743


More information about the Mlir-commits mailing list