[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:25 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/3] [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/3] 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
>From 343e4efcf08ae907f5a09f279eed4c25297889f3 Mon Sep 17 00:00:00 2001
From: Hamza Qureshi <63870077+hamzaqureshi5 at users.noreply.github.com>
Date: Thu, 13 Aug 2026 12:05:15 +0500
Subject: [PATCH 3/3] 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 17514b739e24e..0c8f90b0de557 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
@@ -1833,7 +1833,7 @@ def LLVM_MDNullAttr : LLVM_Attr<"MDNull", "md_null"> {
def LLVM_MDAddrSpaceCastAttr
: LLVM_Attr<"MDAddrSpaceCast", "md_addrspacecast"> {
- let summary = "LLVM addrspacecast-constant-expression-as-metadata";
+ 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`
More information about the Mlir-commits
mailing list