[llvm] [SPIR-V] Handle undef aggregate initializers for global variables (PR #186785)

Arseniy Obolenskiy via llvm-commits llvm-commits at lists.llvm.org
Mon Mar 16 10:48:13 PDT 2026


https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/186785

>From 90770b298cd2a8a99c05627a3430d8b5edf2fb10 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Mon, 16 Mar 2026 13:08:09 +0100
Subject: [PATCH 1/5] [SPIR-V] Handle undef aggregate initializers for global
 variables

Expand undef aggregate global initializers into per-element spv_undef intrinsics
---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 32 ++++++++++++-
 llvm/lib/Target/SPIRV/SPIRVUtils.h            |  6 ++-
 .../undef-global-aggregate-initializer.ll     | 48 +++++++++++++++++++
 3 files changed, 83 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 87bb871dda80c..eb97269898115 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -255,6 +255,7 @@ class SPIRVEmitIntrinsics
   bool shouldTryToAddMemAliasingDecoration(Instruction *Inst);
   void insertSpirvDecorations(Instruction *I, IRBuilder<> &B);
   void insertConstantsForFPFastMathDefault(Module &M);
+  Value *buildSpvUndefComposite(Type *AggrTy, IRBuilder<> &B);
   void processGlobalValue(GlobalVariable &GV, IRBuilder<> &B);
   void processParamTypes(Function *F, IRBuilder<> &B);
   void processParamTypesByFunHeader(Function *F, IRBuilder<> &B);
@@ -424,7 +425,9 @@ static bool isMemInstrToReplace(Instruction *I) {
 static bool isAggrConstForceInt32(const Value *V) {
   return isa<ConstantArray>(V) || isa<ConstantStruct>(V) ||
          isa<ConstantDataArray>(V) ||
-         (isa<ConstantAggregateZero>(V) && !V->getType()->isVectorTy());
+         (isa<ConstantAggregateZero>(V) && !V->getType()->isVectorTy()) ||
+         (isa<UndefValue>(V) && !isa<PoisonValue>(V) &&
+          V->getType()->isAggregateType());
 }
 
 static void setInsertPointSkippingPhis(IRBuilder<> &B, Instruction *I) {
@@ -2269,6 +2272,28 @@ shouldEmitIntrinsicsForGlobalValue(const GlobalVariableUsers &GVUsers,
   return F == &FirstDefinition;
 }
 
+Value *SPIRVEmitIntrinsics::buildSpvUndefComposite(Type *AggrTy,
+                                                   IRBuilder<> &B) {
+  unsigned NumElems = isa<StructType>(AggrTy)
+                          ? cast<StructType>(AggrTy)->getNumElements()
+                          : cast<ArrayType>(AggrTy)->getNumElements();
+  SmallVector<Value *, 4> Elems(NumElems);
+  for (unsigned I = 0; I < NumElems; ++I) {
+    Type *ElemTy = isa<StructType>(AggrTy)
+                       ? AggrTy->getContainedType(I)
+                       : cast<ArrayType>(AggrTy)->getElementType();
+    auto *UI = B.CreateIntrinsic(Intrinsic::spv_undef, {});
+    AggrConsts[UI] = UndefValue::get(ElemTy);
+    AggrConstTypes[UI] = ElemTy;
+    Elems[I] = UI;
+  }
+  auto *Composite = B.CreateIntrinsic(Intrinsic::spv_const_composite,
+                                      {B.getInt32Ty()}, Elems);
+  AggrConsts[Composite] = UndefValue::get(AggrTy);
+  AggrConstTypes[Composite] = AggrTy;
+  return Composite;
+}
+
 void SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
                                              IRBuilder<> &B) {
 
@@ -2282,11 +2307,14 @@ void SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
     // by llvm IR general logic.
     deduceElementTypeHelper(&GV, false);
     Init = GV.getInitializer();
+    Value *InitOp = Init;
+    if (isa<UndefValue>(Init) && Init->getType()->isAggregateType())
+      InitOp = buildSpvUndefComposite(Init->getType(), B);
     Type *Ty = isAggrConstForceInt32(Init) ? B.getInt32Ty() : Init->getType();
     Constant *Const = isAggrConstForceInt32(Init) ? B.getInt32(1) : Init;
     auto *InitInst = B.CreateIntrinsic(Intrinsic::spv_init_global,
                                        {GV.getType(), Ty}, {&GV, Const});
-    InitInst->setArgOperand(1, Init);
+    InitInst->setArgOperand(1, InitOp);
   }
   if (!Init && GV.use_empty())
     B.CreateIntrinsic(Intrinsic::spv_unref_global, GV.getType(), &GV);
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index 8a152cd58d517..3b5d908928c35 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -347,7 +347,11 @@ bool matchPeeledArrayPattern(const StructType *Ty, Type *&OriginalElementType,
 Type *reconstitutePeeledArrayType(Type *Ty);
 
 inline bool hasInitializer(const GlobalVariable *GV) {
-  return GV->hasInitializer() && !isa<UndefValue>(GV->getInitializer());
+  if (!GV->hasInitializer())
+    return false;
+  if (const auto *Init = GV->getInitializer(); isa<UndefValue>(Init))
+    return !isa<PoisonValue>(Init) && Init->getType()->isAggregateType();
+  return true;
 }
 
 // True if this is an instance of TypedPointerType.
diff --git a/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll b/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
new file mode 100644
index 0000000000000..15d005215f7aa
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
@@ -0,0 +1,48 @@
+; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+target datalayout = "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024"
+target triple = "spir"
+
+%struct.simple = type { i8 }
+ at g_simple = private unnamed_addr addrspace(2) constant %struct.simple undef, align 1
+
+%struct.multi = type { i32, float, i8 }
+ at g_multi = private addrspace(2) constant %struct.multi undef, align 4
+
+ at g_arr = private addrspace(2) constant [3 x i32] undef, align 4
+
+%struct.inner = type { i32 }
+%struct.outer = type { %struct.inner, float }
+ at g_nested = private addrspace(2) constant %struct.outer undef, align 4
+
+define spir_kernel void @k() #0 !kernel_arg_addr_space !0 !kernel_arg_access_qual !0 !kernel_arg_type !0 !kernel_arg_base_type !0 !kernel_arg_type_qual !0 {
+entry:
+  ret void
+}
+
+; CHECK-DAG: %[[#I32:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#F32:]] = OpTypeFloat 32
+; CHECK-DAG: %[[#I8:]] = OpTypeInt 8 0
+
+; CHECK-DAG: %[[#INNER:]] = OpTypeStruct %[[#I32]]
+; CHECK-DAG: %[[#OUTER:]] = OpTypeStruct %[[#INNER]] %[[#F32]]
+; CHECK-DAG: %[[#MULTI:]] = OpTypeStruct %[[#I32]] %[[#F32]] %[[#I8]]
+; CHECK-DAG: %[[#SIMPLE:]] = OpTypeStruct %[[#I8]]
+; CHECK-DAG: %[[#ARR:]] = OpTypeArray %[[#I32]] %[[#]]
+
+; CHECK-DAG: %[[#OUTER_PTR:]] = OpTypePointer UniformConstant %[[#OUTER]]
+; CHECK-DAG: %[[#ARR_PTR:]] = OpTypePointer UniformConstant %[[#ARR]]
+; CHECK-DAG: %[[#MULTI_PTR:]] = OpTypePointer UniformConstant %[[#MULTI]]
+; CHECK-DAG: %[[#SIMPLE_PTR:]] = OpTypePointer UniformConstant %[[#SIMPLE]]
+
+; CHECK: OpConstantComposite %[[#SIMPLE]]
+; CHECK: OpVariable %[[#SIMPLE_PTR]] UniformConstant
+; CHECK: OpConstantComposite %[[#MULTI]]
+; CHECK: OpVariable %[[#MULTI_PTR]] UniformConstant
+; CHECK: OpConstantComposite %[[#ARR]]
+; CHECK: OpVariable %[[#ARR_PTR]] UniformConstant
+; CHECK: OpConstantComposite %[[#OUTER]]
+; CHECK: OpVariable %[[#OUTER_PTR]] UniformConstant
+
+!0 = !{}

>From a6a94e6125d3592cbbd189f8c49addf71e9abc70 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Mon, 16 Mar 2026 13:29:03 +0100
Subject: [PATCH 2/5] Address deprecator concerns

---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp             | 7 +++----
 llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp        | 5 +++--
 llvm/lib/Target/SPIRV/SPIRVUtils.h                        | 2 +-
 .../CodeGen/SPIRV/undef-global-aggregate-initializer.ll   | 8 ++++----
 4 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index eb97269898115..307e01071093b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -426,8 +426,7 @@ static bool isAggrConstForceInt32(const Value *V) {
   return isa<ConstantArray>(V) || isa<ConstantStruct>(V) ||
          isa<ConstantDataArray>(V) ||
          (isa<ConstantAggregateZero>(V) && !V->getType()->isVectorTy()) ||
-         (isa<UndefValue>(V) && !isa<PoisonValue>(V) &&
-          V->getType()->isAggregateType());
+         (isa<UndefValue>(V) && V->getType()->isAggregateType());
 }
 
 static void setInsertPointSkippingPhis(IRBuilder<> &B, Instruction *I) {
@@ -2283,13 +2282,13 @@ Value *SPIRVEmitIntrinsics::buildSpvUndefComposite(Type *AggrTy,
                        ? AggrTy->getContainedType(I)
                        : cast<ArrayType>(AggrTy)->getElementType();
     auto *UI = B.CreateIntrinsic(Intrinsic::spv_undef, {});
-    AggrConsts[UI] = UndefValue::get(ElemTy);
+    AggrConsts[UI] = PoisonValue::get(ElemTy);
     AggrConstTypes[UI] = ElemTy;
     Elems[I] = UI;
   }
   auto *Composite = B.CreateIntrinsic(Intrinsic::spv_const_composite,
                                       {B.getInt32Ty()}, Elems);
-  AggrConsts[Composite] = UndefValue::get(AggrTy);
+  AggrConsts[Composite] = PoisonValue::get(AggrTy);
   AggrConstTypes[Composite] = AggrTy;
   return Composite;
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 7cf039e03430a..879c3322ad0f3 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -554,8 +554,9 @@ static bool isConstReg(MachineRegisterInfo *MRI, MachineInstr *OpDef) {
     case TargetOpcode::G_INTRINSIC:
     case TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS:
     case TargetOpcode::G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS:
-      if (cast<GIntrinsic>(*OpDef).getIntrinsicID() !=
-          Intrinsic::spv_const_composite)
+      if (cast<GIntrinsic>(*MI).getIntrinsicID() !=
+              Intrinsic::spv_const_composite &&
+          cast<GIntrinsic>(*MI).getIntrinsicID() != Intrinsic::spv_undef)
         return false;
       continue;
     case TargetOpcode::G_BUILD_VECTOR:
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index 3b5d908928c35..d541ead5ac22c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -350,7 +350,7 @@ inline bool hasInitializer(const GlobalVariable *GV) {
   if (!GV->hasInitializer())
     return false;
   if (const auto *Init = GV->getInitializer(); isa<UndefValue>(Init))
-    return !isa<PoisonValue>(Init) && Init->getType()->isAggregateType();
+    return GV->isConstant() && Init->getType()->isAggregateType();
   return true;
 }
 
diff --git a/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll b/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
index 15d005215f7aa..6bd4a0dfced6a 100644
--- a/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
+++ b/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
@@ -5,16 +5,16 @@ target datalayout = "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:2
 target triple = "spir"
 
 %struct.simple = type { i8 }
- at g_simple = private unnamed_addr addrspace(2) constant %struct.simple undef, align 1
+ at g_simple = private unnamed_addr addrspace(2) constant %struct.simple poison, align 1
 
 %struct.multi = type { i32, float, i8 }
- at g_multi = private addrspace(2) constant %struct.multi undef, align 4
+ at g_multi = private addrspace(2) constant %struct.multi poison, align 4
 
- at g_arr = private addrspace(2) constant [3 x i32] undef, align 4
+ at g_arr = private addrspace(2) constant [3 x i32] poison, align 4
 
 %struct.inner = type { i32 }
 %struct.outer = type { %struct.inner, float }
- at g_nested = private addrspace(2) constant %struct.outer undef, align 4
+ at g_nested = private addrspace(2) constant %struct.outer poison, align 4
 
 define spir_kernel void @k() #0 !kernel_arg_addr_space !0 !kernel_arg_access_qual !0 !kernel_arg_type !0 !kernel_arg_base_type !0 !kernel_arg_type_qual !0 {
 entry:

>From 14c15d6de142414ee8481de504e0a64f4643156d Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Mon, 16 Mar 2026 17:03:48 +0100
Subject: [PATCH 3/5] Address review comments

---
 .../undef-global-aggregate-initializer.ll     | 44 +++++++++++--------
 1 file changed, 26 insertions(+), 18 deletions(-)

diff --git a/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll b/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
index 6bd4a0dfced6a..2cb2f57da010c 100644
--- a/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
+++ b/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
@@ -1,9 +1,6 @@
 ; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s
 ; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 
-target datalayout = "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024"
-target triple = "spir"
-
 %struct.simple = type { i8 }
 @g_simple = private unnamed_addr addrspace(2) constant %struct.simple poison, align 1
 
@@ -16,7 +13,10 @@ target triple = "spir"
 %struct.outer = type { %struct.inner, float }
 @g_nested = private addrspace(2) constant %struct.outer poison, align 4
 
-define spir_kernel void @k() #0 !kernel_arg_addr_space !0 !kernel_arg_access_qual !0 !kernel_arg_type !0 !kernel_arg_base_type !0 !kernel_arg_type_qual !0 {
+%struct.mixed = type { i32, float }
+ at g_mixed = private addrspace(2) constant %struct.mixed { i32 poison, float 1.0 }, align 4
+
+define spir_kernel void @k() {
 entry:
   ret void
 }
@@ -24,25 +24,33 @@ entry:
 ; CHECK-DAG: %[[#I32:]] = OpTypeInt 32 0
 ; CHECK-DAG: %[[#F32:]] = OpTypeFloat 32
 ; CHECK-DAG: %[[#I8:]] = OpTypeInt 8 0
-
-; CHECK-DAG: %[[#INNER:]] = OpTypeStruct %[[#I32]]
-; CHECK-DAG: %[[#OUTER:]] = OpTypeStruct %[[#INNER]] %[[#F32]]
 ; CHECK-DAG: %[[#MULTI:]] = OpTypeStruct %[[#I32]] %[[#F32]] %[[#I8]]
-; CHECK-DAG: %[[#SIMPLE:]] = OpTypeStruct %[[#I8]]
+; CHECK-DAG: %[[#SIMPLE:]] = OpTypeStruct %[[#I8]]{{$}}
 ; CHECK-DAG: %[[#ARR:]] = OpTypeArray %[[#I32]] %[[#]]
+; CHECK-DAG: %[[#INNER:]] = OpTypeStruct %[[#I32]]{{$}}
 
+; CHECK-DAG: %[[#OUTER:]] = OpTypeStruct %[[#INNER]] %[[#F32]]
+; CHECK-DAG: %[[#MIXED:]] = OpTypeStruct %[[#I32]] %[[#F32]]{{$}}
+
+; CHECK-DAG: %[[#MIXED_PTR:]] = OpTypePointer UniformConstant %[[#MIXED]]
 ; CHECK-DAG: %[[#OUTER_PTR:]] = OpTypePointer UniformConstant %[[#OUTER]]
 ; CHECK-DAG: %[[#ARR_PTR:]] = OpTypePointer UniformConstant %[[#ARR]]
 ; CHECK-DAG: %[[#MULTI_PTR:]] = OpTypePointer UniformConstant %[[#MULTI]]
 ; CHECK-DAG: %[[#SIMPLE_PTR:]] = OpTypePointer UniformConstant %[[#SIMPLE]]
 
-; CHECK: OpConstantComposite %[[#SIMPLE]]
-; CHECK: OpVariable %[[#SIMPLE_PTR]] UniformConstant
-; CHECK: OpConstantComposite %[[#MULTI]]
-; CHECK: OpVariable %[[#MULTI_PTR]] UniformConstant
-; CHECK: OpConstantComposite %[[#ARR]]
-; CHECK: OpVariable %[[#ARR_PTR]] UniformConstant
-; CHECK: OpConstantComposite %[[#OUTER]]
-; CHECK: OpVariable %[[#OUTER_PTR]] UniformConstant
-
-!0 = !{}
+; CHECK-DAG: %[[#CONST_F32:]] = OpConstant %[[#F32]] 1
+; CHECK-DAG: %[[#UNDEF_I8:]] = OpUndef %[[#I8]]
+; CHECK-DAG: %[[#UNDEF_I32:]] = OpUndef %[[#I32]]
+; CHECK-DAG: %[[#UNDEF_F32:]] = OpUndef %[[#F32]]
+; CHECK-DAG: %[[#UNDEF_INNER:]] = OpUndef %[[#INNER]]
+
+; CHECK-DAG: OpConstantComposite %[[#SIMPLE]] %[[#UNDEF_I8]]
+; CHECK-DAG: OpVariable %[[#SIMPLE_PTR]] UniformConstant
+; CHECK-DAG: OpConstantComposite %[[#MULTI]] %[[#UNDEF_I32]] %[[#UNDEF_F32]] %[[#UNDEF_I8]]
+; CHECK-DAG: OpVariable %[[#MULTI_PTR]] UniformConstant
+; CHECK-DAG: OpConstantComposite %[[#ARR]] %[[#UNDEF_I32]] %[[#UNDEF_I32]] %[[#UNDEF_I32]]
+; CHECK-DAG: OpVariable %[[#ARR_PTR]] UniformConstant
+; CHECK-DAG: OpConstantComposite %[[#OUTER]] %[[#UNDEF_INNER]] %[[#UNDEF_F32]]
+; CHECK-DAG: OpVariable %[[#OUTER_PTR]] UniformConstant
+; CHECK-DAG: OpConstantComposite %[[#MIXED]] %[[#UNDEF_I32]] %[[#CONST_F32]]
+; CHECK-DAG: OpVariable %[[#MIXED_PTR]] UniformConstant

>From 224e9ef151ed68815ce1f3846d82592fb2932b1b Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Mon, 16 Mar 2026 18:29:08 +0100
Subject: [PATCH 4/5] Address review comments

---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 29 ++++++++++++-------
 1 file changed, 19 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 307e01071093b..88c9653ab6472 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -423,10 +423,11 @@ static bool isMemInstrToReplace(Instruction *I) {
 }
 
 static bool isAggrConstForceInt32(const Value *V) {
+  bool IsUndefAggregate = isa<UndefValue>(V) && V->getType()->isAggregateType();
   return isa<ConstantArray>(V) || isa<ConstantStruct>(V) ||
          isa<ConstantDataArray>(V) ||
          (isa<ConstantAggregateZero>(V) && !V->getType()->isVectorTy()) ||
-         (isa<UndefValue>(V) && V->getType()->isAggregateType());
+         IsUndefAggregate;
 }
 
 static void setInsertPointSkippingPhis(IRBuilder<> &B, Instruction *I) {
@@ -2273,18 +2274,26 @@ shouldEmitIntrinsicsForGlobalValue(const GlobalVariableUsers &GVUsers,
 
 Value *SPIRVEmitIntrinsics::buildSpvUndefComposite(Type *AggrTy,
                                                    IRBuilder<> &B) {
-  unsigned NumElems = isa<StructType>(AggrTy)
-                          ? cast<StructType>(AggrTy)->getNumElements()
-                          : cast<ArrayType>(AggrTy)->getNumElements();
-  SmallVector<Value *, 4> Elems(NumElems);
-  for (unsigned I = 0; I < NumElems; ++I) {
-    Type *ElemTy = isa<StructType>(AggrTy)
-                       ? AggrTy->getContainedType(I)
-                       : cast<ArrayType>(AggrTy)->getElementType();
+  SmallVector<Value *, 4> Elems;
+  if (auto *ArrTy = dyn_cast<ArrayType>(AggrTy)) {
+    Type *ElemTy = ArrTy->getElementType();
     auto *UI = B.CreateIntrinsic(Intrinsic::spv_undef, {});
     AggrConsts[UI] = PoisonValue::get(ElemTy);
     AggrConstTypes[UI] = ElemTy;
-    Elems[I] = UI;
+    Elems.assign(ArrTy->getNumElements(), UI);
+  } else {
+    auto *StructTy = cast<StructType>(AggrTy);
+    DenseMap<Type *, Instruction *> UndefByType;
+    for (unsigned I = 0; I < StructTy->getNumElements(); ++I) {
+      Type *ElemTy = StructTy->getContainedType(I);
+      auto &Entry = UndefByType[ElemTy];
+      if (!Entry) {
+        Entry = B.CreateIntrinsic(Intrinsic::spv_undef, {});
+        AggrConsts[Entry] = PoisonValue::get(ElemTy);
+        AggrConstTypes[Entry] = ElemTy;
+      }
+      Elems.push_back(Entry);
+    }
   }
   auto *Composite = B.CreateIntrinsic(Intrinsic::spv_const_composite,
                                       {B.getInt32Ty()}, Elems);

>From 12256535b829f514c1989df0bbc88c3f9f24a3f3 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Mon, 16 Mar 2026 18:47:57 +0100
Subject: [PATCH 5/5] Add test: struct nested in an array

---
 .../SPIRV/undef-global-aggregate-initializer.ll  | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll b/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
index 2cb2f57da010c..ce247a11e37f4 100644
--- a/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
+++ b/llvm/test/CodeGen/SPIRV/undef-global-aggregate-initializer.ll
@@ -16,6 +16,11 @@
 %struct.mixed = type { i32, float }
 @g_mixed = private addrspace(2) constant %struct.mixed { i32 poison, float 1.0 }, align 4
 
+%struct.with_arr = type { [2 x i32], float }
+ at g_struct_with_arr = private addrspace(2) constant %struct.with_arr poison, align 4
+
+ at g_arr_of_struct = private addrspace(2) constant [2 x %struct.with_arr] poison, align 4
+
 define spir_kernel void @k() {
 entry:
   ret void
@@ -24,14 +29,19 @@ entry:
 ; CHECK-DAG: %[[#I32:]] = OpTypeInt 32 0
 ; CHECK-DAG: %[[#F32:]] = OpTypeFloat 32
 ; CHECK-DAG: %[[#I8:]] = OpTypeInt 8 0
+; CHECK-DAG: %[[#I32_ARR2:]] = OpTypeArray %[[#I32]] %[[#]]
 ; CHECK-DAG: %[[#MULTI:]] = OpTypeStruct %[[#I32]] %[[#F32]] %[[#I8]]
 ; CHECK-DAG: %[[#SIMPLE:]] = OpTypeStruct %[[#I8]]{{$}}
 ; CHECK-DAG: %[[#ARR:]] = OpTypeArray %[[#I32]] %[[#]]
 ; CHECK-DAG: %[[#INNER:]] = OpTypeStruct %[[#I32]]{{$}}
+; CHECK-DAG: %[[#WITH_ARR:]] = OpTypeStruct %[[#I32_ARR2]] %[[#F32]]
 
 ; CHECK-DAG: %[[#OUTER:]] = OpTypeStruct %[[#INNER]] %[[#F32]]
 ; CHECK-DAG: %[[#MIXED:]] = OpTypeStruct %[[#I32]] %[[#F32]]{{$}}
+; CHECK-DAG: %[[#ARR_OF_STRUCT:]] = OpTypeArray %[[#WITH_ARR]] %[[#]]
 
+; CHECK-DAG: %[[#ARR_OF_STRUCT_PTR:]] = OpTypePointer UniformConstant %[[#ARR_OF_STRUCT]]
+; CHECK-DAG: %[[#WITH_ARR_PTR:]] = OpTypePointer UniformConstant %[[#WITH_ARR]]
 ; CHECK-DAG: %[[#MIXED_PTR:]] = OpTypePointer UniformConstant %[[#MIXED]]
 ; CHECK-DAG: %[[#OUTER_PTR:]] = OpTypePointer UniformConstant %[[#OUTER]]
 ; CHECK-DAG: %[[#ARR_PTR:]] = OpTypePointer UniformConstant %[[#ARR]]
@@ -43,6 +53,8 @@ entry:
 ; CHECK-DAG: %[[#UNDEF_I32:]] = OpUndef %[[#I32]]
 ; CHECK-DAG: %[[#UNDEF_F32:]] = OpUndef %[[#F32]]
 ; CHECK-DAG: %[[#UNDEF_INNER:]] = OpUndef %[[#INNER]]
+; CHECK-DAG: %[[#UNDEF_I32_ARR2:]] = OpUndef %[[#I32_ARR2]]
+; CHECK-DAG: %[[#UNDEF_WITH_ARR:]] = OpUndef %[[#WITH_ARR]]
 
 ; CHECK-DAG: OpConstantComposite %[[#SIMPLE]] %[[#UNDEF_I8]]
 ; CHECK-DAG: OpVariable %[[#SIMPLE_PTR]] UniformConstant
@@ -54,3 +66,7 @@ entry:
 ; CHECK-DAG: OpVariable %[[#OUTER_PTR]] UniformConstant
 ; CHECK-DAG: OpConstantComposite %[[#MIXED]] %[[#UNDEF_I32]] %[[#CONST_F32]]
 ; CHECK-DAG: OpVariable %[[#MIXED_PTR]] UniformConstant
+; CHECK-DAG: OpConstantComposite %[[#WITH_ARR]] %[[#UNDEF_I32_ARR2]] %[[#UNDEF_F32]]
+; CHECK-DAG: OpVariable %[[#WITH_ARR_PTR]] UniformConstant
+; CHECK-DAG: OpConstantComposite %[[#ARR_OF_STRUCT]] %[[#UNDEF_WITH_ARR]] %[[#UNDEF_WITH_ARR]]
+; CHECK-DAG: OpVariable %[[#ARR_OF_STRUCT_PTR]] UniformConstant



More information about the llvm-commits mailing list