[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
Thu Jun 11 16:46:11 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] [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
+}



More information about the llvm-commits mailing list