[llvm] [SPIR-V] Deduce pointee type for all global variables, not only initialized ones (PR #202047)

Julian Klappenbach via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 14 19:15:57 PDT 2026


https://github.com/jklappenbach updated https://github.com/llvm/llvm-project/pull/202047

>From 819ac14ff3f130d0992b7ff51791fbc8000a5250 Mon Sep 17 00:00:00 2001
From: Julian Klappenbach <julian at twilight.digital>
Date: Sat, 6 Jun 2026 11:14:10 -0400
Subject: [PATCH 1/3] [SPIR-V] Deduce pointee type for all global variables,
 not only initialized ones
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

SPIRVEmitIntrinsics::processGlobalValue only recorded a global variable's
element type (deduceElementTypeHelper) when hasInitializer() was true. For an
undef-initialized, non-constant aggregate global — e.g. a Workgroup `[N x T]`
shared tile (`@g = internal addrspace(3) global [N x T] undef`) — hasInitializer
returns false, so the variable's type was never recorded from its declared value
type. Its pointee type was then inferred lazily from a flat element-typed GEP use
(`getelementptr T, ptr @g, %i`) as the scalar `T`.

With the variable typed as a scalar pointer, the array-to-pointer-decay rewrite
in visitGetElementPtrInst (which prepends a 0 index so a Logical-SPIR-V access
chain can index the array) is skipped, because its DeducedPointeeTy is not an
ArrayType. The GEP stays a byte-offset pointer-arithmetic form Logical SPIR-V
cannot express, and the dynamic index is dropped: every invocation accesses
element 0. A Workgroup array indexed by a loop variable thus collapsed to a
scalar variable, silently corrupting workgroup-shared reductions/staging on
Vulkan (the emitted module passes spirv-val but computes wrong results).

A global variable's pointee type is concrete and authoritative, so record it for
every global in processGlobalValue (which runs before the use-based forward
pass). The deduction result is still ignored at the call site — TypedPointerType
isn't expressible in general LLVM IR; it is stored in the Global Registry.

(cherry picked from commit 2849c532820328544bee3ea3d8acd25289d3f457)
---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp |  9 ++++---
 .../type-deduce-global-array-poison.ll        | 27 +++++++++++++++++++
 2 files changed, 32 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/pointers/type-deduce-global-array-poison.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 97fa49d8836fb..6a99bea0db1bc 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2553,12 +2553,13 @@ void SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
   if (!shouldEmitIntrinsicsForGlobalValue(GVUsers, GV, CurrF))
     return;
 
+  // Record the pointee type for every global, not only initialized ones, so an
+  // undef non-constant aggregate global is not later collapsed to its element
+  // type. Result is ignored; the type is stored in the Global Registry.
+  deduceElementTypeHelper(&GV, false);
+
   Constant *Init = nullptr;
   if (hasInitializer(&GV)) {
-    // Deduce element type and store results in Global Registry.
-    // Result is ignored, because TypedPointerType is not supported
-    // by llvm IR general logic.
-    deduceElementTypeHelper(&GV, false);
     Init = GV.getInitializer();
     Value *InitOp = Init;
     if (isa<UndefValue>(Init) && Init->getType()->isAggregateType()) {
diff --git a/llvm/test/CodeGen/SPIRV/pointers/type-deduce-global-array-poison.ll b/llvm/test/CodeGen/SPIRV/pointers/type-deduce-global-array-poison.ll
new file mode 100644
index 0000000000000..43017d8de95fc
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/type-deduce-global-array-poison.ll
@@ -0,0 +1,27 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; A global variable keeps its concrete array pointee type even when its
+; initializer is poison and it is non-constant, so a dynamic index is lowered as
+; an OpAccessChain rather than dropped (which would make every invocation access
+; element 0).
+
+ at tile = internal addrspace(3) global [64 x i32] poison, align 16
+
+; CHECK-DAG: %[[#U32:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#Arr:]] = OpTypeArray %[[#U32]] %[[#]]
+; CHECK-DAG: %[[#ArrPtr:]] = OpTypePointer Workgroup %[[#Arr]]
+; CHECK-DAG: %[[#EltPtr:]] = OpTypePointer Workgroup %[[#U32]]
+; CHECK-DAG: %[[#Tile:]] = OpVariable %[[#ArrPtr]] Workgroup
+
+; The dynamic index survives as an OpAccessChain into the array tile and the
+; variable is not collapsed to a scalar pointer.
+; CHECK: %[[#Ptr:]] = OpAccessChain %[[#EltPtr]] %[[#Tile]] %[[#]]
+; CHECK: OpStore %[[#Ptr]] %[[#]]
+
+define void @store_dynamic_index(i32 %id) {
+entry:
+  %p = getelementptr i32, ptr addrspace(3) @tile, i32 %id
+  store i32 42, ptr addrspace(3) %p, align 4
+  ret void
+}

>From 8dbc10fda8c27a10622640079057acd1ae00eb30 Mon Sep 17 00:00:00 2001
From: Julian Klappenbach <julian at twilight.digital>
Date: Sun, 14 Jun 2026 21:43:48 -0400
Subject: [PATCH 2/3] Update llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp

Co-authored-by: Arseniy Obolenskiy <gooddoog at student.su>
---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 6a99bea0db1bc..22c624e338665 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -2555,7 +2555,8 @@ void SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
 
   // Record the pointee type for every global, not only initialized ones, so an
   // undef non-constant aggregate global is not later collapsed to its element
-  // type. Result is ignored; the type is stored in the Global Registry.
+  // type. Result is ignored, because TypedPointerType is not supported
+  // by llvm IR general logic.
   deduceElementTypeHelper(&GV, false);
 
   Constant *Init = nullptr;

>From eed04555d935c77530c74240f9cb32ac99467c60 Mon Sep 17 00:00:00 2001
From: Julian Klappenbach <julian at twilight.digital>
Date: Sun, 14 Jun 2026 22:15:47 -0400
Subject: [PATCH 3/3] [SPIR-V] test: also cover vulkan1.3 (Shader/Logical)
 flavor

Add spirv-unknown-vulkan1.3-compute RUN lines alongside spirv-unknown-unknown
(per review). The unknown-unknown triple resolves to the Kernel/OpenCL flavor
(Physical addressing); the bug this PR fixes only manifests under Logical
SPIR-V, so the Vulkan triple is the path that actually exercises it. Both pass
FileCheck and spirv-val with the existing shared CHECK set.
---
 .../CodeGen/SPIRV/pointers/type-deduce-global-array-poison.ll   | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/test/CodeGen/SPIRV/pointers/type-deduce-global-array-poison.ll b/llvm/test/CodeGen/SPIRV/pointers/type-deduce-global-array-poison.ll
index 43017d8de95fc..91f6df634b567 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/type-deduce-global-array-poison.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/type-deduce-global-array-poison.ll
@@ -1,3 +1,5 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-unknown-vulkan1.3-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %}
 ; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-unknown-unknown %s -o - | FileCheck %s
 ; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 



More information about the llvm-commits mailing list