[llvm] [SPIR-V] Simplify addrspacecast for null in composite constant preprocessing (PR #192030)
Arseniy Obolenskiy via llvm-commits
llvm-commits at lists.llvm.org
Wed Apr 15 08:44:20 PDT 2026
https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/192030
>From 8033a0a0a1f07b25a1e24dc86ae280a4be8032bf Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Tue, 14 Apr 2026 11:29:11 +0200
Subject: [PATCH 1/3] [SPIR-V] Simplify addrspacecast for null in composite
constant preprocessing
Fold addrspacecast(null) to a typed null pointer during composite constant preprocessing so that null pointer members in structs and arrays get the correct SPIR-V pointer type instead of being lowered as integers. This fixes invalid SPIR-V where OpConstantNull had an integer type instead of a pointer type, and where OpSpecConstantOp was unnecessarily emitted for null casts
---
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 10 ++++++++-
.../fun-ptr-addrcast.ll | 4 +++-
.../opencl/device_execution/execute_block.ll | 1 +
.../PtrCast-null-in-OpSpecConstantOp.ll | 13 ++++++-----
.../SPIRV/struct-null-pointer-member.ll | 22 +++++++++++++++++++
5 files changed, 42 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/struct-null-pointer-member.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 7600b3a695a4e..67746886b4271 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -1568,7 +1568,15 @@ void SPIRVEmitIntrinsics::preprocessCompositeConstants(IRBuilder<> &B) {
for (unsigned i = 0; i < COp->getNumElements(); ++i)
Args.push_back(COp->getElementAsConstant(i));
else
- llvm::append_range(Args, AggrConst->operands());
+ for (Value *Op : AggrConst->operands()) {
+ // Simplify addrspacecast(null) to null in the target address space
+ // so that null pointers get the correct pointer type when lowered.
+ if (auto *CE = dyn_cast<ConstantExpr>(Op);
+ CE && CE->getOpcode() == Instruction::AddrSpaceCast &&
+ isa<ConstantPointerNull>(CE->getOperand(0)))
+ Op = ConstantPointerNull::get(cast<PointerType>(CE->getType()));
+ Args.push_back(Op);
+ }
if (!BPrepared) {
IsPhi ? B.SetInsertPointPastAllocas(I->getParent()->getParent())
: B.SetInsertPoint(I);
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_function_pointers/fun-ptr-addrcast.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_function_pointers/fun-ptr-addrcast.ll
index e5111304765e2..854981ca06784 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_function_pointers/fun-ptr-addrcast.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_function_pointers/fun-ptr-addrcast.ll
@@ -8,7 +8,9 @@
; %_ptr_Generic_7 = OpTypePointer Generic %7
; TODO: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj --spirv-ext=+SPV_INTEL_function_pointers | spirv-val %}
-; CHECK-COUNT-3: %[[#]] = OpSpecConstantOp %[[#]] PtrCastToGeneric %[[#]]
+; CHECK-DAG: %[[#GENPTR:]] = OpTypePointer Generic %[[#]]
+; CHECK-DAG: %[[#]] = OpConstantNull %[[#GENPTR]]
+; CHECK-COUNT-2: %[[#]] = OpSpecConstantOp %[[#]] PtrCastToGeneric %[[#]]
; CHECK-COUNT-3: OpPtrCastToGeneric
@G1 = addrspace(1) constant { [3 x ptr addrspace(4)] } { [3 x ptr addrspace(4)] [ptr addrspace(4) null, ptr addrspace(4) addrspacecast (ptr @foo to ptr addrspace(4)), ptr addrspace(4) addrspacecast (ptr @bar to ptr addrspace(4))] }
diff --git a/llvm/test/CodeGen/SPIRV/opencl/device_execution/execute_block.ll b/llvm/test/CodeGen/SPIRV/opencl/device_execution/execute_block.ll
index 54a6e8e38d536..ab4fec3121dd3 100644
--- a/llvm/test/CodeGen/SPIRV/opencl/device_execution/execute_block.ll
+++ b/llvm/test/CodeGen/SPIRV/opencl/device_execution/execute_block.ll
@@ -1,4 +1,5 @@
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
; CHECK: %[[#bool:]] = OpTypeBool
; CHECK: %[[#true:]] = OpConstantTrue %[[#bool]]
diff --git a/llvm/test/CodeGen/SPIRV/pointers/PtrCast-null-in-OpSpecConstantOp.ll b/llvm/test/CodeGen/SPIRV/pointers/PtrCast-null-in-OpSpecConstantOp.ll
index 49fb43d7e9625..d8d1efc02bacb 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/PtrCast-null-in-OpSpecConstantOp.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/PtrCast-null-in-OpSpecConstantOp.ll
@@ -1,12 +1,13 @@
; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
-; TODO: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
-; CHECK-DAG: %[[Array:.*]] = OpTypeArray %[[#]] %[[#]]
+; CHECK-DAG: %[[Char:.*]] = OpTypeInt 8 0
+; CHECK-DAG: %[[GenPtr:.*]] = OpTypePointer Generic %[[Char]]
+; CHECK-DAG: %[[Array:.*]] = OpTypeArray %[[GenPtr]] %[[#]]
; CHECK-DAG: %[[Struct:.*]] = OpTypeStruct %[[Array]]
-; CHECK-DAG: %[[Zero:.*]] = OpTypeInt 64 0
-; CHECK-DAG: %[[Null:.*]] = OpConstantNull %[[Zero]]
-; CHECK-DAG: %[[R:.*]] = OpSpecConstantComposite %[[Array]] %[[Null]]
-; CHECK-DAG: %[[#]] = OpSpecConstantComposite %[[Struct]] %[[R]]
+; CHECK-DAG: %[[Null:.*]] = OpConstantNull %[[GenPtr]]
+; CHECK-DAG: %[[R:.*]] = OpConstantComposite %[[Array]] %[[Null]]
+; CHECK-DAG: %[[#]] = OpConstantComposite %[[Struct]] %[[R]]
@G1 = addrspace(1) constant { [1 x ptr addrspace(4)] } { [1 x ptr addrspace(4)] [ptr addrspace(4) addrspacecast (ptr null to ptr addrspace(4))] }
@G2 = addrspace(1) constant { [1 x ptr addrspace(4)] } { [1 x ptr addrspace(4)] [ptr addrspace(4) addrspacecast (ptr addrspace(1) null to ptr addrspace(4))] }
diff --git a/llvm/test/CodeGen/SPIRV/struct-null-pointer-member.ll b/llvm/test/CodeGen/SPIRV/struct-null-pointer-member.ll
new file mode 100644
index 0000000000000..3be36985dde2e
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/struct-null-pointer-member.ll
@@ -0,0 +1,22 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Test that a null pointer member in a struct constant (via addrspacecast)
+; gets the correct pointer type in OpConstantComposite, not integer type.
+
+%struct = type { i32, ptr addrspace(4) }
+
+ at gv = internal addrspace(1) constant %struct { i32 42, ptr addrspace(4) addrspacecast (ptr null to ptr addrspace(4)) }, align 8
+
+; CHECK-DAG: %[[#INT:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#CHAR:]] = OpTypeInt 8 0
+; CHECK-DAG: %[[#GENPTR:]] = OpTypePointer Generic %[[#CHAR]]
+; CHECK-DAG: %[[#STRUCT:]] = OpTypeStruct %[[#INT]] %[[#GENPTR]]
+; CHECK-DAG: %[[#CONST:]] = OpConstant %[[#INT]] 42
+; CHECK-DAG: %[[#NULL:]] = OpConstantNull %[[#GENPTR]]
+; CHECK: OpConstantComposite %[[#STRUCT]] %[[#CONST]] %[[#NULL]]
+
+define spir_kernel void @test(ptr addrspace(1) %out) {
+entry:
+ ret void
+}
>From 8adc51290a9e134b4fdfd0f2c627bf36c1e3e935 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Wed, 15 Apr 2026 13:23:02 +0200
Subject: [PATCH 2/3] Address review comments
---
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 16 +++++++++++++
.../fun-ptr-addrcast.ll | 2 +-
.../SPIRV/struct-null-pointer-member.ll | 24 ++++++++++++++++---
3 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 67746886b4271..6ff5c5720e28d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -1534,6 +1534,22 @@ void SPIRVEmitIntrinsics::preprocessUndefs(IRBuilder<> &B) {
}
void SPIRVEmitIntrinsics::preprocessCompositeConstants(IRBuilder<> &B) {
+ // Simplify addrspacecast(null) to ConstantPointerNull of the target type.
+ // Casting null always yields null, and this avoids SPIR-V lowering issues
+ // where the null gets typed as an integer instead of a pointer. This handles
+ // addrspacecast instructions (from expanded ConstantExprs); ConstantExpr
+ // sub-elements inside composites are simplified in the decomposition below.
+ SmallVector<Instruction *, 4> ToErase;
+ for (Instruction &I : instructions(CurrF))
+ if (auto *ASC = dyn_cast<AddrSpaceCastInst>(&I))
+ if (isa<ConstantPointerNull>(ASC->getPointerOperand())) {
+ ASC->replaceAllUsesWith(
+ ConstantPointerNull::get(cast<PointerType>(ASC->getType())));
+ ToErase.push_back(ASC);
+ }
+ for (Instruction *I : ToErase)
+ I->eraseFromParent();
+
std::queue<Instruction *> Worklist;
for (auto &I : instructions(CurrF))
Worklist.push(&I);
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_function_pointers/fun-ptr-addrcast.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_function_pointers/fun-ptr-addrcast.ll
index 854981ca06784..bd67305968973 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_function_pointers/fun-ptr-addrcast.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_function_pointers/fun-ptr-addrcast.ll
@@ -11,7 +11,7 @@
; CHECK-DAG: %[[#GENPTR:]] = OpTypePointer Generic %[[#]]
; CHECK-DAG: %[[#]] = OpConstantNull %[[#GENPTR]]
; CHECK-COUNT-2: %[[#]] = OpSpecConstantOp %[[#]] PtrCastToGeneric %[[#]]
-; CHECK-COUNT-3: OpPtrCastToGeneric
+; CHECK-COUNT-2: OpPtrCastToGeneric
@G1 = addrspace(1) constant { [3 x ptr addrspace(4)] } { [3 x ptr addrspace(4)] [ptr addrspace(4) null, ptr addrspace(4) addrspacecast (ptr @foo to ptr addrspace(4)), ptr addrspace(4) addrspacecast (ptr @bar to ptr addrspace(4))] }
@G2 = addrspace(1) constant { [3 x ptr addrspace(4)] } { [3 x ptr addrspace(4)] [ptr addrspace(4) addrspacecast (ptr null to ptr addrspace(4)), ptr addrspace(4) addrspacecast (ptr @bar to ptr addrspace(4)), ptr addrspace(4) addrspacecast (ptr @foo to ptr addrspace(4))] }
diff --git a/llvm/test/CodeGen/SPIRV/struct-null-pointer-member.ll b/llvm/test/CodeGen/SPIRV/struct-null-pointer-member.ll
index 3be36985dde2e..c53ceba5f9d03 100644
--- a/llvm/test/CodeGen/SPIRV/struct-null-pointer-member.ll
+++ b/llvm/test/CodeGen/SPIRV/struct-null-pointer-member.ll
@@ -1,8 +1,9 @@
; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
-; Test that a null pointer member in a struct constant (via addrspacecast)
-; gets the correct pointer type in OpConstantComposite, not integer type.
+; Test that addrspacecast(null) is simplified to null of the target address
+; space type in all contexts: composite constants, instruction operands, and
+; global initializers.
%struct = type { i32, ptr addrspace(4) }
@@ -11,12 +12,29 @@
; CHECK-DAG: %[[#INT:]] = OpTypeInt 32 0
; CHECK-DAG: %[[#CHAR:]] = OpTypeInt 8 0
; CHECK-DAG: %[[#GENPTR:]] = OpTypePointer Generic %[[#CHAR]]
+; CHECK-DAG: %[[#CWGPTR:]] = OpTypePointer CrossWorkgroup %[[#CHAR]]
; CHECK-DAG: %[[#STRUCT:]] = OpTypeStruct %[[#INT]] %[[#GENPTR]]
; CHECK-DAG: %[[#CONST:]] = OpConstant %[[#INT]] 42
; CHECK-DAG: %[[#NULL:]] = OpConstantNull %[[#GENPTR]]
-; CHECK: OpConstantComposite %[[#STRUCT]] %[[#CONST]] %[[#NULL]]
+; CHECK-DAG: %[[#CWGNULL:]] = OpConstantNull %[[#CWGPTR]]
+; CHECK-DAG: %[[#BOOL:]] = OpTypeBool
+
+; Composite constant: addrspacecast(null) in struct member should become
+; OpConstantNull of the pointer type.
+; CHECK-DAG: OpConstantComposite %[[#STRUCT]] %[[#CONST]] %[[#NULL]]
define spir_kernel void @test(ptr addrspace(1) %out) {
entry:
ret void
}
+
+; Instruction operand: addrspacecast(null) in icmp should become
+; OpConstantNull of the pointer type, not a cast from integer-typed null.
+; CHECK: %[[#]] = OpPtrEqual %[[#BOOL]] %[[#CWGNULL]] %[[#]]
+define spir_kernel void @test_icmp(ptr addrspace(1) %arg, ptr addrspace(1) %out) {
+entry:
+ %cmp = icmp eq ptr addrspace(1) addrspacecast (ptr null to ptr addrspace(1)), %arg
+ %zext = zext i1 %cmp to i32
+ store i32 %zext, ptr addrspace(1) %out
+ ret void
+}
>From 5ff8b10642c2375d1d46dba2a2c89ae9b86f12dd Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Wed, 15 Apr 2026 17:44:08 +0200
Subject: [PATCH 3/3] Address review comment
---
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 22 +++++++++----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 6ff5c5720e28d..7a886de005b88 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -241,6 +241,7 @@ class SPIRVEmitIntrinsics
void preprocessCompositeConstants(IRBuilder<> &B);
void preprocessUndefs(IRBuilder<> &B);
+ void simplifyNullAddrSpaceCasts();
Type *reconstructType(Value *Op, bool UnknownElemTypeI8,
bool IsPostprocessing);
@@ -1533,23 +1534,21 @@ void SPIRVEmitIntrinsics::preprocessUndefs(IRBuilder<> &B) {
}
}
-void SPIRVEmitIntrinsics::preprocessCompositeConstants(IRBuilder<> &B) {
- // Simplify addrspacecast(null) to ConstantPointerNull of the target type.
- // Casting null always yields null, and this avoids SPIR-V lowering issues
- // where the null gets typed as an integer instead of a pointer. This handles
- // addrspacecast instructions (from expanded ConstantExprs); ConstantExpr
- // sub-elements inside composites are simplified in the decomposition below.
- SmallVector<Instruction *, 4> ToErase;
- for (Instruction &I : instructions(CurrF))
+// Simplify addrspacecast(null) instructions to ConstantPointerNull of the
+// target type. Casting null always yields null, and this avoids SPIR-V
+// lowering issues where the null gets typed as an integer instead of a
+// pointer.
+void SPIRVEmitIntrinsics::simplifyNullAddrSpaceCasts() {
+ for (Instruction &I : make_early_inc_range(instructions(CurrF)))
if (auto *ASC = dyn_cast<AddrSpaceCastInst>(&I))
if (isa<ConstantPointerNull>(ASC->getPointerOperand())) {
ASC->replaceAllUsesWith(
ConstantPointerNull::get(cast<PointerType>(ASC->getType())));
- ToErase.push_back(ASC);
+ ASC->eraseFromParent();
}
- for (Instruction *I : ToErase)
- I->eraseFromParent();
+}
+void SPIRVEmitIntrinsics::preprocessCompositeConstants(IRBuilder<> &B) {
std::queue<Instruction *> Worklist;
for (auto &I : instructions(CurrF))
Worklist.push(&I);
@@ -3196,6 +3195,7 @@ bool SPIRVEmitIntrinsics::runOnFunction(Function &Func) {
processGlobalValue(GV, B);
preprocessUndefs(B);
+ simplifyNullAddrSpaceCasts();
preprocessCompositeConstants(B);
for (BasicBlock &BB : Func)
More information about the llvm-commits
mailing list