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

Hamza Qureshi llvmlistbot at llvm.org
Thu Aug 13 00:05:04 PDT 2026


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

>From 21c027bf1c598e16b0494d95233e393a37d9d71c Mon Sep 17 00:00:00 2001
From: hamzaqureshi5 <hamza7771.861 at gmail.com>
Date: Wed, 12 Aug 2026 12:07:16 +0500
Subject: [PATCH 1/2] [MLIR][LLVM] Preserve pointer-valued metadata operands on
 import

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
---
 .../mlir/Dialect/LLVMIR/LLVMAttrDefs.td       | 45 +++++++++++-
 mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp      | 16 +++++
 mlir/lib/Target/LLVMIR/ModuleImport.cpp       | 28 ++++++--
 mlir/lib/Target/LLVMIR/ModuleTranslation.cpp  | 17 +++++
 mlir/test/Dialect/LLVMIR/invalid.mlir         | 16 +++++
 mlir/test/Dialect/LLVMIR/roundtrip.mlir       |  8 +++
 .../LLVMIR/Import/intrinsic-unregistered.ll   | 70 +++++++++++++++++++
 .../Target/LLVMIR/llvmir-named-metadata.mlir  | 12 ++++
 8 files changed, 203 insertions(+), 9 deletions(-)

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 fbb5e878cbbaa..2ab4529ddef53 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -196,12 +196,28 @@ Attribute ModuleImport::convertMetadataToAttrImpl(
       if (FlatSymbolRefAttr symbolRef = getMetadataGlobalValueSymbolRef(global))
         return MDGlobalValueAttr::get(context, symbolRef);
     }
-    auto *ci = dyn_cast<llvm::ConstantInt>(constant);
-    if (!ci)
-      return {};
-    auto intType = IntegerType::get(context, ci->getBitWidth());
-    return MDConstantAttr::get(context,
-                               IntegerAttr::get(intType, ci->getValue()));
+    if (auto *ci = dyn_cast<llvm::ConstantInt>(constant)) {
+      auto intType = IntegerType::get(context, ci->getBitWidth());
+      return MDConstantAttr::get(context,
+                                 IntegerAttr::get(intType, ci->getValue()));
+    }
+    if (auto *nullPtr = dyn_cast<llvm::ConstantPointerNull>(constant))
+      return MDNullAttr::get(context,
+                             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(
+          llvm::ConstantAsMetadata::get(constExpr->getOperand(0)), path,
+          attrMap);
+      if (!argAttr)
+        return {};
+      return MDAddrSpaceCastAttr::get(
+          context, argAttr, constExpr->getType()->getPointerAddressSpace());
+    }
+    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 a33e2842bff7c..2ff2f731082c2 100644
--- a/mlir/test/Target/LLVMIR/Import/intrinsic-unregistered.ll
+++ b/mlir/test/Target/LLVMIR/Import/intrinsic-unregistered.ll
@@ -205,3 +205,73 @@ define i32 @read_ifunc_metadata() {
 }
 
 !0 = !{ptr @ifunc}
+
+; // -----
+
+; A null pointer constant metadata operand must be preserved.
+
+declare i32 @llvm.read_register.i32(metadata)
+
+; CHECK-LABEL: llvm.func @read_null_metadata
+define i32 @read_null_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 constant must be preserved.
+
+declare i32 @llvm.read_register.i32(metadata)
+
+; CHECK-LABEL: llvm.func @read_null_addrspace_metadata
+define i32 @read_null_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 metadata operand must be preserved.
+
+declare i32 @llvm.read_register.i32(metadata)
+
+ at addrspace_global = addrspace(1) global i32 0
+
+; CHECK: llvm.mlir.global external @[[$GLOBAL:addrspace_global]]
+; 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<@[[$GLOBAL]]>, 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) @addrspace_global to ptr)}
+
+; // -----
+
+; Pointer constants nested inside a multi-operand metadata node.
+
+declare i32 @llvm.read_register.i32(metadata)
+
+ at nested_global = addrspace(1) global i32 0
+
+; CHECK: llvm.mlir.global external @[[$GLOBAL:nested_global]]
+; 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<@[[$GLOBAL]]>, 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) @nested_global 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)}

>From 8d13b1d0af75150259f91eb102b0e7739e6ae7e0 Mon Sep 17 00:00:00 2001
From: Hamza Qureshi <63870077+hamzaqureshi5 at users.noreply.github.com>
Date: Thu, 13 Aug 2026 12:04:56 +0500
Subject: [PATCH 2/2] Update mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td

Co-authored-by: Tobias Gysi <tobias.gysi at nextsilicon.com>
---
 mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
index 7b15bfe37193f..17514b739e24e 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
@@ -1815,7 +1815,7 @@ def LLVM_MDGlobalValueAttr : LLVM_Attr<"MDGlobalValue", "md_global_value"> {
 }
 
 def LLVM_MDNullAttr : LLVM_Attr<"MDNull", "md_null"> {
-  let summary = "LLVM null-pointer-as-metadata";
+  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



More information about the Mlir-commits mailing list