[llvm] cbuffer final (PR #169078)
Steven Perron via llvm-commits
llvm-commits at lists.llvm.org
Fri Nov 21 10:27:15 PST 2025
https://github.com/s-perron created https://github.com/llvm/llvm-project/pull/169078
- **[SPIRV] Improve Logical SPIR-V Pointer Access and GEP Legalization**
- **[SPIRV] Support Peeled Array Layouts for HLSL CBuffers**
>From 99e9b1716cacd3e727850ac43f6204d810a834c7 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Fri, 21 Nov 2025 12:37:33 -0500
Subject: [PATCH 1/2] [SPIRV] Improve Logical SPIR-V Pointer Access and GEP
Legalization
This commit improves the handling of GetElementPtr (GEP) instructions for
Logical SPIR-V. It includes:
- Rewriting of GEPs that are not allowed in Logical SPIR-V (specifically,
handling non-zero first indices by rebuilding access chains or adjusting
types).
- Better deduction of element types for pointer casting.
- Updates to instruction selection to ensure GEPs are correctly lowered to
OpAccessChain or OpInBoundsAccessChain only when valid (e.g. first index 0).
- Support for standard HLSL cbuffer layouts in tests.
---
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 72 +++++++--
.../Target/SPIRV/SPIRVInstructionSelector.cpp | 6 +
.../Target/SPIRV/SPIRVLegalizePointerCast.cpp | 34 ++--
.../SPIRV/hlsl-resources/cbuffer-array.ll | 71 +++++++++
.../SPIRV/hlsl-resources/cbuffer-simple.ll | 68 ++++++++
.../SPIRV/hlsl-resources/cbuffer-struct.ll | 146 ++++++++++++++++++
6 files changed, 359 insertions(+), 38 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-array.ll
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-simple.ll
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-struct.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 8e14fb03127fc..855d1d33a5b4a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -1569,16 +1569,60 @@ Instruction *SPIRVEmitIntrinsics::visitSwitchInst(SwitchInst &I) {
return BrI;
}
-Instruction *SPIRVEmitIntrinsics::visitGetElementPtrInst(GetElementPtrInst &I) {
- if (I.getSourceElementType() == IntegerType::getInt8Ty(CurrF->getContext()) &&
- TM->getSubtargetImpl()->isLogicalSPIRV()) {
- Instruction *Result = buildLogicalAccessChainFromGEP(I);
- if (Result)
- return Result;
+static bool isFirstIndexZero(const GetElementPtrInst *GEP) {
+ if (GEP->getNumIndices() == 0)
+ return false;
+ if (const auto *CI = dyn_cast<ConstantInt>(GEP->getOperand(1))) {
+ return CI->getZExtValue() == 0;
}
+ return false;
+}
+Instruction *SPIRVEmitIntrinsics::visitGetElementPtrInst(GetElementPtrInst &I) {
IRBuilder<> B(I.getParent());
B.SetInsertPoint(&I);
+
+ if (TM->getSubtargetImpl()->isLogicalSPIRV() && !isFirstIndexZero(&I)) {
+ // Logical SPIR-V cannot use the OpPtrAccessChain instruction. If the first
+ // index of the GEP is not 0, then we need to try to adjust it.
+ //
+ // If the GEP is doing byte addressing, try to rebuild the full access chain
+ // from the type of the pointer.
+ if (I.getSourceElementType() ==
+ IntegerType::getInt8Ty(CurrF->getContext())) {
+ Instruction *Result = buildLogicalAccessChainFromGEP(I);
+ if (Result) {
+ return Result;
+ }
+ }
+
+ // Look for the array-to-pointer decay. If this is the pattern
+ // we can adjust the types, and prepend a 0 to the indices.
+ Value *PtrOp = I.getPointerOperand();
+ Type *SrcElemTy = I.getSourceElementType();
+ Type *DeducedPointeeTy = deduceElementType(PtrOp, true);
+
+ if (auto *ArrTy = dyn_cast<ArrayType>(DeducedPointeeTy)) {
+ if (ArrTy->getElementType() == SrcElemTy) {
+ SmallVector<Value *> NewIndices;
+ Type *FirstIdxType = I.getOperand(1)->getType();
+ NewIndices.push_back(ConstantInt::get(FirstIdxType, 0));
+ for (Value *Idx : I.indices())
+ NewIndices.push_back(Idx);
+
+ SmallVector<Type *, 2> Types = {I.getType(), I.getPointerOperandType()};
+ SmallVector<Value *, 4> Args;
+ Args.push_back(B.getInt1(I.isInBounds()));
+ Args.push_back(I.getPointerOperand());
+ Args.append(NewIndices.begin(), NewIndices.end());
+
+ auto *NewI = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args});
+ replaceAllUsesWithAndErase(B, &I, NewI);
+ return NewI;
+ }
+ }
+ }
+
SmallVector<Type *, 2> Types = {I.getType(), I.getOperand(0)->getType()};
SmallVector<Value *, 4> Args;
Args.push_back(B.getInt1(I.isInBounds()));
@@ -1772,16 +1816,12 @@ void SPIRVEmitIntrinsics::insertPtrCastOrAssignTypeInstr(Instruction *I,
Value *Pointer = GEPI->getPointerOperand();
Type *OpTy = nullptr;
- // Knowing the accessed type is mandatory for logical SPIR-V. Sadly,
- // the GEP source element type should not be used for this purpose, and
- // the alternative type-scavenging method is not working.
- // Physical SPIR-V can work around this, but not logical, hence still
- // try to rely on the broken type scavenging for logical.
- bool IsRewrittenGEP =
- GEPI->getSourceElementType() == IntegerType::getInt8Ty(I->getContext());
- if (IsRewrittenGEP && TM->getSubtargetImpl()->isLogicalSPIRV()) {
- Value *Src = getPointerRoot(Pointer);
- OpTy = GR->findDeducedElementType(Src);
+ // Logical SPIR-V is not allowed to use Op*PtrAccessChain instructions. If
+ // the first index is 0, then we can trivially lower to OpAccessChain. If
+ // not we need to try to rewrite the GEP. We avoid adding a pointer cast at
+ // this time, and will rewrite the GEP when visiting it.
+ if (TM->getSubtargetImpl()->isLogicalSPIRV() && !isFirstIndexZero(GEPI)) {
+ return;
}
// In all cases, fall back to the GEP type if type scavenging failed.
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index d3fc08eb56cb3..69606c10fb224 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -467,6 +467,7 @@ static bool isConstReg(MachineRegisterInfo *MRI, MachineInstr *OpDef,
switch (Opcode) {
case TargetOpcode::G_CONSTANT:
case TargetOpcode::G_FCONSTANT:
+ case TargetOpcode::G_IMPLICIT_DEF:
return true;
case TargetOpcode::G_INTRINSIC:
case TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS:
@@ -3088,6 +3089,11 @@ bool SPIRVInstructionSelector::selectGEP(Register ResVReg,
.addUse(GR.getSPIRVTypeID(ResType))
// Object to get a pointer to.
.addUse(I.getOperand(3).getReg());
+ assert(Opcode == SPIRV::OpPtrAccessChain ||
+ Opcode == SPIRV::OpInBoundsPtrAccessChain ||
+ (getImm(I.getOperand(4), MRI) && foldImm(I.getOperand(4), MRI) == 0) &&
+ "Cannot translate GEP to OpAccessChain. First index must be 0.");
+
// Adding indices.
const unsigned StartingIndex =
(Opcode == SPIRV::OpAccessChain || Opcode == SPIRV::OpInBoundsAccessChain)
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index 4ce871b6f5e5d..81c7596530ee2 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -104,9 +104,13 @@ class SPIRVLegalizePointerCast : public FunctionPass {
Value *loadFirstValueFromAggregate(IRBuilder<> &B, Type *ElementType,
Value *Source, LoadInst *BadLoad) {
SmallVector<Type *, 2> Types = {BadLoad->getPointerOperandType(),
- BadLoad->getPointerOperandType()};
- SmallVector<Value *, 3> Args{/* isInBounds= */ B.getInt1(false), Source,
- B.getInt32(0), B.getInt32(0)};
+ Source->getType()};
+ SmallVector<Value *, 8> Args{/* isInBounds= */ B.getInt1(false), Source};
+
+ Type *AggregateType = GR->findDeducedElementType(Source);
+ assert(AggregateType && "Could not deduce aggregate type");
+ buildGEPIndexChain(B, ElementType, AggregateType, Args);
+
auto *GEP = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args});
GR->buildAssignPtr(B, ElementType, GEP);
@@ -201,34 +205,20 @@ class SPIRVLegalizePointerCast : public FunctionPass {
auto *SAT = dyn_cast<ArrayType>(FromTy);
auto *SVT = dyn_cast<FixedVectorType>(FromTy);
- auto *SST = dyn_cast<StructType>(FromTy);
auto *DVT = dyn_cast<FixedVectorType>(ToTy);
B.SetInsertPoint(LI);
- // Destination is the element type of Source, and source is an array ->
- // Loading 1st element.
+ // Destination is the element type of some member of FromTy. For example,
+ // loading the 1st element of an array:
// - float a = array[0];
- if (SAT && SAT->getElementType() == ToTy)
- Output = loadFirstValueFromAggregate(B, SAT->getElementType(),
- OriginalOperand, LI);
- // Destination is the element type of Source, and source is a vector ->
- // Vector to scalar.
- // - float a = vector.x;
- else if (!DVT && SVT && SVT->getElementType() == ToTy) {
- Output = loadFirstValueFromAggregate(B, SVT->getElementType(),
- OriginalOperand, LI);
- }
+ if (isTypeFirstElementAggregate(ToTy, FromTy))
+ Output = loadFirstValueFromAggregate(B, ToTy, OriginalOperand, LI);
// Destination is a smaller vector than source or different vector type.
// - float3 v3 = vector4;
// - float4 v2 = int4;
else if (SVT && DVT)
Output = loadVectorFromVector(B, SVT, DVT, OriginalOperand);
- // Destination is the scalar type stored at the start of an aggregate.
- // - struct S { float m };
- // - float v = s.m;
- else if (SST && SST->getTypeAtIndex(0u) == ToTy)
- Output = loadFirstValueFromAggregate(B, ToTy, OriginalOperand, LI);
else if (SAT && DVT && SAT->getElementType() == DVT->getElementType())
Output = loadVectorFromArray(B, DVT, OriginalOperand);
else
@@ -334,7 +324,7 @@ class SPIRVLegalizePointerCast : public FunctionPass {
Value *storeToFirstValueAggregate(IRBuilder<> &B, Value *Src, Value *Dst,
Type *DstPointeeType, Align Alignment) {
SmallVector<Type *, 2> Types = {Dst->getType(), Dst->getType()};
- SmallVector<Value *, 3> Args{/* isInBounds= */ B.getInt1(true), Dst};
+ SmallVector<Value *, 8> Args{/* isInBounds= */ B.getInt1(true), Dst};
buildGEPIndexChain(B, Src->getType(), DstPointeeType, Args);
auto *GEP = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args});
GR->buildAssignPtr(B, Src->getType(), GEP);
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-array.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-array.ll
new file mode 100644
index 0000000000000..15b4320d2683d
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-array.ll
@@ -0,0 +1,71 @@
+; RUN: llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[FLOAT:[0-9]+]] = OpTypeFloat 32
+; CHECK-DAG: %[[VEC4:[0-9]+]] = OpTypeVector %[[FLOAT]] 4
+; CHECK-DAG: %[[PTR_VEC4:[0-9]+]] = OpTypePointer Uniform %[[VEC4]]
+; CHECK-DAG: %[[INT:[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: %[[PTR_INT:[0-9]+]] = OpTypePointer Uniform %[[INT]]
+; CHECK-DAG: %[[INT64:[0-9]+]] = OpTypeInt 64 0
+; CHECK-DAG: %[[CONST_4:[0-9]+]] = OpConstant %[[INT]] 4{{$}}
+
+; CHECK: %[[ARRAY:[0-9]+]] = OpTypeArray %[[VEC4]] %[[CONST_4]]
+; CHECK-DAG: %[[PTR_ARRAY:[0-9]+]] = OpTypePointer Uniform %[[ARRAY]]
+
+; CHECK: %[[STRUCT_INNER:[0-9]+]] = OpTypeStruct %[[ARRAY]] %[[INT]]
+; CHECK: %[[STRUCT_CBUFFER:[0-9]+]] = OpTypeStruct %[[STRUCT_INNER]]
+; CHECK: %[[PTR_CBUFFER:[0-9]+]] = OpTypePointer Uniform %[[STRUCT_CBUFFER]]
+
+; CHECK-DAG: %[[ZERO:[0-9]+]] = OpConstant %[[INT]] 0{{$}}
+; CHECK-DAG: %[[ONE:[0-9]+]] = OpConstant %[[INT]] 1{{$}}
+
+; CHECK: %[[CBUFFER:[0-9]+]] = OpVariable %[[PTR_CBUFFER]] Uniform
+
+%__cblayout_MyCBuffer = type <{ [4 x <4 x float>], i32 }>
+
+ at MyCBuffer.cb = local_unnamed_addr global target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) poison
+ at colors = external hidden local_unnamed_addr addrspace(12) global [4 x <4 x float>], align 16
+ at index = external hidden local_unnamed_addr addrspace(12) global i32, align 4
+ at MyCBuffer.str = private unnamed_addr constant [10 x i8] c"MyCBuffer\00", align 1
+ at .str = private unnamed_addr constant [7 x i8] c"output\00", align 1
+
+declare target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_s___cblayout_MyCBuffers_2_0t(i32, i32, i32, i32, ptr)
+
+define void @main() #1 {
+entry:
+; Get pointers to the two elements of the cbuffer
+; CHECK: %[[COPY:[0-9]+]] = OpCopyObject %[[PTR_CBUFFER]] %[[CBUFFER]]
+; CHECK: %[[PTR_ARRAY_ACCESS:[0-9]+]] = OpAccessChain %[[PTR_ARRAY]] %[[COPY]] %[[ZERO]] %[[ZERO]]
+; CHECK: %[[PTR_INT_ACCESS:[0-9]+]] = OpAccessChain %[[PTR_INT]] %[[COPY]] %[[ZERO]] %[[ONE]]
+ %MyCBuffer.cb_h.i.i = tail call target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_s___cblayout_MyCBuffers_2_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @MyCBuffer.str)
+ store target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) %MyCBuffer.cb_h.i.i, ptr @MyCBuffer.cb, align 8
+
+ %0 = tail call target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0v4f32_12_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+
+; CHECK: %[[VAL_INT:[0-9]+]] = OpLoad %[[INT]] %[[PTR_INT_ACCESS]] Aligned 4
+ %1 = load i32, ptr addrspace(12) @index, align 4
+
+; CHECK: %[[VAL_INT64:[0-9]+]] = OpSConvert %[[INT64]] %[[VAL_INT]]
+ %idxprom.i = sext i32 %1 to i64
+
+; CHECK: %[[PTR_ELEM:[0-9]+]] = OpInBoundsAccessChain %[[PTR_VEC4]] %[[PTR_ARRAY_ACCESS]] %[[VAL_INT64]]
+ %arrayidx.i = getelementptr inbounds <4 x float>, ptr addrspace(12) @colors, i64 %idxprom.i
+
+; CHECK: %[[VAL_ELEM:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_ELEM]] Aligned 16
+ %2 = load <4 x float>, ptr addrspace(12) %arrayidx.i, align 16
+
+; CHECK: OpStore {{%[0-9]+}} %[[VAL_ELEM]] Aligned 16
+ %3 = tail call noundef align 16 dereferenceable(16) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0v4f32_12_1t(target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1) %0, i32 0)
+ store <4 x float> %2, ptr addrspace(11) %3, align 16
+ ret void
+}
+
+declare target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0v4f32_12_1t(i32, i32, i32, i32, ptr)
+
+declare ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0v4f32_12_1t(target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1), i32)
+
+attributes #1 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+!hlsl.cbs = !{!0}
+
+!0 = !{ptr @MyCBuffer.cb, ptr addrspace(12) @colors, ptr addrspace(12) @index}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-simple.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-simple.ll
new file mode 100644
index 0000000000000..2bdee9f9c25ea
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-simple.ll
@@ -0,0 +1,68 @@
+; RUN: llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[FLOAT:[0-9]+]] = OpTypeFloat 32
+; CHECK-DAG: %[[VEC4:[0-9]+]] = OpTypeVector %[[FLOAT]] 4
+; CHECK-DAG: %[[PTR_FLOAT:[0-9]+]] = OpTypePointer Uniform %[[FLOAT]]
+; CHECK-DAG: %[[PTR_VEC4:[0-9]+]] = OpTypePointer Uniform %[[VEC4]]
+; CHECK-DAG: %[[STRUCT:[0-9]+]] = OpTypeStruct %[[VEC4]] %[[FLOAT]]
+; CHECK-DAG: %[[CBUFFER_TYPE:[0-9]+]] = OpTypeStruct %[[STRUCT]]
+; CHECK-DAG: %[[PTR_CBUFFER:[0-9]+]] = OpTypePointer Uniform %[[CBUFFER_TYPE]]
+; CHECK-DAG: %[[INT:[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: %[[ZERO:[0-9]+]] = OpConstant %[[INT]] 0{{$}}
+; CHECK-DAG: %[[ONE:[0-9]+]] = OpConstant %[[INT]] 1{{$}}
+
+; CHECK-DAG: %[[CBUFFER:[0-9]+]] = OpVariable %[[PTR_CBUFFER]] Uniform
+
+%__cblayout_MyCBuffer = type <{ <4 x float>, float }>
+
+ at MyCBuffer.cb = local_unnamed_addr global target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) poison
+ at color = external hidden local_unnamed_addr addrspace(12) global <4 x float>, align 16
+ at factor = external hidden local_unnamed_addr addrspace(12) global float, align 4
+ at MyCBuffer.str = private unnamed_addr constant [10 x i8] c"MyCBuffer\00", align 1
+ at .str = private unnamed_addr constant [7 x i8] c"output\00", align 1
+
+declare target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_s___cblayout_MyCBuffers_2_0t(i32, i32, i32, i32, ptr)
+
+define void @main() #1 {
+entry:
+; CHECK: %[[COPY:[0-9]+]] = OpCopyObject %[[PTR_CBUFFER]] %[[CBUFFER]]
+; CHECK: %[[PTR_VEC4_ACCESS:[0-9]+]] = OpAccessChain %[[PTR_VEC4]] %[[COPY]] %[[ZERO]] %[[ZERO]]
+; CHECK: %[[PTR_FLOAT_ACCESS:[0-9]+]] = OpAccessChain %[[PTR_FLOAT]] %[[COPY]] %[[ZERO]] %[[ONE]]
+ %MyCBuffer.cb_h.i.i = tail call target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_s___cblayout_MyCBuffers_2_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @MyCBuffer.str)
+ store target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) %MyCBuffer.cb_h.i.i, ptr @MyCBuffer.cb, align 8
+
+ %0 = tail call target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0v4f32_12_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %1 = tail call i32 @llvm.spv.thread.id.i32(i32 0)
+ %2 = tail call i32 @llvm.spv.thread.id.i32(i32 1)
+ %conv.i = uitofp i32 %1 to float
+ %conv2.i = uitofp i32 %2 to float
+ %3 = insertelement <4 x float> <float poison, float poison, float 0.000000e+00, float 1.000000e+00>, float %conv.i, i64 0
+ %vecinit5.i = insertelement <4 x float> %3, float %conv2.i, i64 1
+
+; CHECK: %[[VAL_VEC4:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_VEC4_ACCESS]] Aligned 16
+ %4 = load <4 x float>, ptr addrspace(12) @color, align 16
+ %mul.i = fmul reassoc nnan ninf nsz arcp afn <4 x float> %vecinit5.i, %4
+
+; CHECK: %[[VAL_FLOAT:[0-9]+]] = OpLoad %[[FLOAT]] %[[PTR_FLOAT_ACCESS]] Aligned 4
+ %5 = load float, ptr addrspace(12) @factor, align 4
+
+ %splat.splatinsert.i = insertelement <4 x float> poison, float %5, i64 0
+ %splat.splat.i = shufflevector <4 x float> %splat.splatinsert.i, <4 x float> poison, <4 x i32> zeroinitializer
+ %mul6.i = fmul reassoc nnan ninf nsz arcp afn <4 x float> %mul.i, %splat.splat.i
+ %6 = tail call noundef align 16 dereferenceable(16) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0v4f32_12_1t(target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1) %0, i32 0)
+ store <4 x float> %mul6.i, ptr addrspace(11) %6, align 16
+ ret void
+}
+
+declare i32 @llvm.spv.thread.id.i32(i32)
+
+declare target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0v4f32_12_1t(i32, i32, i32, i32, ptr)
+
+declare ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0v4f32_12_1t(target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1), i32)
+
+attributes #1 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+!hlsl.cbs = !{!0}
+
+!0 = !{ptr @MyCBuffer.cb, ptr addrspace(12) @color, ptr addrspace(12) @factor}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-struct.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-struct.ll
new file mode 100644
index 0000000000000..ebdb6a7b71a1c
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-struct.ll
@@ -0,0 +1,146 @@
+; RUN: llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[FLOAT:[0-9]+]] = OpTypeFloat 32
+; CHECK-DAG: %[[VEC4:[0-9]+]] = OpTypeVector %[[FLOAT]] 4
+; CHECK-DAG: %[[PTR_VEC4:[0-9]+]] = OpTypePointer Uniform %[[VEC4]]
+; CHECK-DAG: %[[INT:[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: %[[ZERO:[0-9]+]] = OpConstant %[[INT]] 0{{$}}
+
+; CHECK-DAG: %[[STRUCT_MATRIX:[0-9]+]] = OpTypeStruct %[[VEC4]] %[[VEC4]] %[[VEC4]] %[[VEC4]]
+; CHECK-DAG: %[[PTR_MATRIX:[0-9]+]] = OpTypePointer Uniform %[[STRUCT_MATRIX]]
+; CHECK-DAG: %[[PTR_FLOAT:[0-9]+]] = OpTypePointer Uniform %[[FLOAT]]
+
+; CHECK-DAG: %[[STRUCT_MYSTRUCT:[0-9]+]] = OpTypeStruct %[[STRUCT_MATRIX]] %[[STRUCT_MATRIX]] %[[STRUCT_MATRIX]]
+
+; CHECK-DAG: %[[PTR_MYSTRUCT:[0-9]+]] = OpTypePointer Uniform %[[STRUCT_MYSTRUCT]]
+; CHECK-DAG: %[[STRUCT_INNER:[0-9]+]] = OpTypeStruct %[[STRUCT_MYSTRUCT]] %[[FLOAT]]
+
+; CHECK-DAG: %[[STRUCT_CBUFFER:[0-9]+]] = OpTypeStruct %[[STRUCT_INNER]]
+; CHECK-DAG: %[[PTR_CBUFFER:[0-9]+]] = OpTypePointer Uniform %[[STRUCT_CBUFFER]]
+; CHECK-DAG: %[[INT64:[0-9]+]] = OpTypeInt 64 0
+
+; CHECK-DAG: %[[ONE:[0-9]+]] = OpConstant %[[INT]] 1{{$}}
+; CHECK-DAG: %[[ZERO_64:[0-9]+]] = OpConstant %[[INT64]] 0{{$}}
+; CHECK-DAG: %[[ONE_64:[0-9]+]] = OpConstant %[[INT64]] 1{{$}}
+; CHECK-DAG: %[[TWO_64:[0-9]+]] = OpConstant %[[INT64]] 2{{$}}
+; CHECK-DAG: %[[THREE_64:[0-9]+]] = OpConstant %[[INT64]] 3{{$}}
+
+; CHECK: %[[CBUFFER:[0-9]+]] = OpVariable %[[PTR_CBUFFER]] Uniform
+
+%__cblayout_MyCBuffer = type <{ %MyStruct, float }>
+%MyStruct = type <{ %MyMatrix, %MyMatrix, %MyMatrix }>
+%MyMatrix = type <{ <4 x float>, <4 x float>, <4 x float>, <4 x float> }>
+
+ at MyCBuffer.cb = local_unnamed_addr global target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) poison
+ at transforms = external hidden local_unnamed_addr addrspace(12) global %MyStruct, align 1
+ at blend = external hidden local_unnamed_addr addrspace(12) global float, align 4
+ at MyCBuffer.str = private unnamed_addr constant [10 x i8] c"MyCBuffer\00", align 1
+ at .str = private unnamed_addr constant [7 x i8] c"output\00", align 1
+
+declare target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_s___cblayout_MyCBuffers_2_0t(i32, i32, i32, i32, ptr)
+
+declare <4 x float> @llvm.fmuladd.v4f32(<4 x float>, <4 x float>, <4 x float>)
+
+define void @main() #3 {
+entry:
+; CHECK: %[[COPY:[0-9]+]] = OpCopyObject %[[PTR_CBUFFER]] %[[CBUFFER]]
+; CHECK: %[[PTR_STRUCT:[0-9]+]] = OpAccessChain %[[PTR_MYSTRUCT]] %[[COPY]] %[[ZERO]] %[[ZERO]]
+; CHECK: %[[PTR_FLOAT_VAL:[0-9]+]] = OpAccessChain %[[PTR_FLOAT]] %[[COPY]] %[[ZERO]] %[[ONE]]
+ %MyCBuffer.cb_h.i.i = tail call target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_s___cblayout_MyCBuffers_2_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @MyCBuffer.str)
+ store target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) %MyCBuffer.cb_h.i.i, ptr @MyCBuffer.cb, align 8
+
+ %0 = tail call target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0v4f32_12_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %1 = tail call i32 @llvm.spv.thread.id.i32(i32 0)
+ %2 = tail call i32 @llvm.spv.thread.id.i32(i32 1)
+ %conv.i = uitofp i32 %1 to float
+ %conv2.i = uitofp i32 %2 to float
+ %3 = insertelement <4 x float> poison, float %conv.i, i64 0
+
+; CHECK: %[[PTR_M0_V0:[0-9]+]] = OpAccessChain %[[PTR_VEC4]] %[[PTR_STRUCT]] %[[ZERO]] %[[ZERO]]
+; CHECK: %[[VAL_M0_V0:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M0_V0]] Aligned 16
+ %4 = load <4 x float>, ptr addrspace(12) @transforms, align 16
+
+; CHECK: %[[PTR_M0_V1:[0-9]+]] = OpInBoundsAccessChain %[[PTR_VEC4]] %[[PTR_STRUCT]] %[[ZERO_64]] %[[ONE_64]]
+; CHECK: %[[VAL_M0_V1:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M0_V1]] Aligned 16
+ %5 = load <4 x float>, ptr addrspace(12) getelementptr inbounds nuw (i8, ptr addrspace(12) @transforms, i64 16), align 16
+
+; CHECK: %[[PTR_M0_V3:[0-9]+]] = OpInBoundsAccessChain %[[PTR_VEC4]] %[[PTR_STRUCT]] %[[ZERO_64]] %[[THREE_64]]
+; CHECK: %[[VAL_M0_V3:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M0_V3]] Aligned 16
+ %6 = load <4 x float>, ptr addrspace(12) getelementptr inbounds nuw (i8, ptr addrspace(12) @transforms, i64 48), align 16
+
+ %splat.splat.i18.i = shufflevector <4 x float> %3, <4 x float> poison, <4 x i32> zeroinitializer
+ %7 = insertelement <4 x float> poison, float %conv2.i, i64 0
+ %splat.splat2.i19.i = shufflevector <4 x float> %7, <4 x float> poison, <4 x i32> zeroinitializer
+ %mul3.i20.i = fmul reassoc nnan ninf nsz arcp afn <4 x float> %splat.splat2.i19.i, %5
+ %8 = tail call reassoc nnan ninf nsz arcp afn <4 x float> @llvm.fmuladd.v4f32(<4 x float> %splat.splat.i18.i, <4 x float> nofpclass(nan inf) %4, <4 x float> %mul3.i20.i)
+ %9 = fadd reassoc nnan ninf nsz arcp afn <4 x float> %8, %6
+; CHECK: %[[PTR_M1:[0-9]+]] = OpInBoundsAccessChain %[[PTR_MATRIX]] %[[PTR_STRUCT]] %[[ONE_64]]
+; CHECK: %[[PTR_M1_V0:[0-9]+]] = OpAccessChain %[[PTR_VEC4]] %[[PTR_M1]] %[[ZERO]]
+; CHECK: %[[VAL_M1_V0:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M1_V0]] Aligned 16
+ %10 = load <4 x float>, ptr addrspace(12) getelementptr inbounds nuw (i8, ptr addrspace(12) @transforms, i64 64), align 16
+; CHECK: %[[PTR_M1_V1:[0-9]+]] = OpInBoundsAccessChain %[[PTR_VEC4]] %[[PTR_STRUCT]] %[[ONE_64]] %[[ONE_64]]
+; CHECK: %[[VAL_M1_V1:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M1_V1]] Aligned 16
+ %11 = load <4 x float>, ptr addrspace(12) getelementptr inbounds nuw (i8, ptr addrspace(12) @transforms, i64 80), align 16
+; CHECK: %[[PTR_M1_V2:[0-9]+]] = OpInBoundsAccessChain %[[PTR_VEC4]] %[[PTR_STRUCT]] %[[ONE_64]] %[[TWO_64]]
+; CHECK: %[[VAL_M1_V2:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M1_V2]] Aligned 16
+ %12 = load <4 x float>, ptr addrspace(12) getelementptr inbounds nuw (i8, ptr addrspace(12) @transforms, i64 96), align 16
+; CHECK: %[[PTR_M1_V3:[0-9]+]] = OpInBoundsAccessChain %[[PTR_VEC4]] %[[PTR_STRUCT]] %[[ONE_64]] %[[THREE_64]]
+; CHECK: %[[VAL_M1_V3:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M1_V3]] Aligned 16
+ %13 = load <4 x float>, ptr addrspace(12) getelementptr inbounds nuw (i8, ptr addrspace(12) @transforms, i64 112), align 16
+ %splat.splat.i13.i = shufflevector <4 x float> %9, <4 x float> poison, <4 x i32> zeroinitializer
+ %splat.splat2.i14.i = shufflevector <4 x float> %9, <4 x float> poison, <4 x i32> <i32 1, i32 1, i32 1, i32 1>
+ %mul3.i15.i = fmul reassoc nnan ninf nsz arcp afn <4 x float> %splat.splat2.i14.i, %11
+ %14 = tail call reassoc nnan ninf nsz arcp afn <4 x float> @llvm.fmuladd.v4f32(<4 x float> %splat.splat.i13.i, <4 x float> nofpclass(nan inf) %10, <4 x float> %mul3.i15.i)
+ %splat.splat5.i16.i = shufflevector <4 x float> %9, <4 x float> poison, <4 x i32> <i32 2, i32 2, i32 2, i32 2>
+ %15 = tail call reassoc nnan ninf nsz arcp afn <4 x float> @llvm.fmuladd.v4f32(<4 x float> %splat.splat5.i16.i, <4 x float> nofpclass(nan inf) %12, <4 x float> %14)
+ %splat.splat7.i17.i = shufflevector <4 x float> %9, <4 x float> poison, <4 x i32> <i32 3, i32 3, i32 3, i32 3>
+ %16 = tail call reassoc nnan ninf nsz arcp afn noundef <4 x float> @llvm.fmuladd.v4f32(<4 x float> %splat.splat7.i17.i, <4 x float> nofpclass(nan inf) %13, <4 x float> %15)
+; CHECK: %[[PTR_M2:[0-9]+]] = OpInBoundsAccessChain %[[PTR_MATRIX]] %[[PTR_STRUCT]] %[[TWO_64]]
+; CHECK: %[[PTR_M2_V0:[0-9]+]] = OpAccessChain %[[PTR_VEC4]] %[[PTR_M2]] %[[ZERO]]
+; CHECK: %[[VAL_M2_V0:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M2_V0]] Aligned 16
+ %17 = load <4 x float>, ptr addrspace(12) getelementptr inbounds nuw (i8, ptr addrspace(12) @transforms, i64 128), align 16
+; CHECK: %[[PTR_M2_V1:[0-9]+]] = OpInBoundsAccessChain %[[PTR_VEC4]] %[[PTR_STRUCT]] %[[TWO_64]] %[[ONE_64]]
+; CHECK: %[[VAL_M2_V1:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M2_V1]] Aligned 16
+ %18 = load <4 x float>, ptr addrspace(12) getelementptr inbounds nuw (i8, ptr addrspace(12) @transforms, i64 144), align 16
+; CHECK: %[[PTR_M2_V2:[0-9]+]] = OpInBoundsAccessChain %[[PTR_VEC4]] %[[PTR_STRUCT]] %[[TWO_64]] %[[TWO_64]]
+; CHECK: %[[VAL_M2_V2:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M2_V2]] Aligned 16
+ %19 = load <4 x float>, ptr addrspace(12) getelementptr inbounds nuw (i8, ptr addrspace(12) @transforms, i64 160), align 16
+; CHECK: %[[PTR_M2_V3:[0-9]+]] = OpInBoundsAccessChain %[[PTR_VEC4]] %[[PTR_STRUCT]] %[[TWO_64]] %[[THREE_64]]
+; CHECK: %[[VAL_M2_V3:[0-9]+]] = OpLoad %[[VEC4]] %[[PTR_M2_V3]] Aligned 16
+ %20 = load <4 x float>, ptr addrspace(12) getelementptr inbounds nuw (i8, ptr addrspace(12) @transforms, i64 176), align 16
+ %splat.splat.i.i = shufflevector <4 x float> %16, <4 x float> poison, <4 x i32> zeroinitializer
+ %splat.splat2.i.i = shufflevector <4 x float> %16, <4 x float> poison, <4 x i32> <i32 1, i32 1, i32 1, i32 1>
+ %mul3.i.i = fmul reassoc nnan ninf nsz arcp afn <4 x float> %splat.splat2.i.i, %18
+ %21 = tail call reassoc nnan ninf nsz arcp afn <4 x float> @llvm.fmuladd.v4f32(<4 x float> %splat.splat.i.i, <4 x float> nofpclass(nan inf) %17, <4 x float> %mul3.i.i)
+ %splat.splat5.i.i = shufflevector <4 x float> %16, <4 x float> poison, <4 x i32> <i32 2, i32 2, i32 2, i32 2>
+ %22 = tail call reassoc nnan ninf nsz arcp afn <4 x float> @llvm.fmuladd.v4f32(<4 x float> %splat.splat5.i.i, <4 x float> nofpclass(nan inf) %19, <4 x float> %21)
+ %splat.splat7.i.i = shufflevector <4 x float> %16, <4 x float> poison, <4 x i32> <i32 3, i32 3, i32 3, i32 3>
+ %23 = tail call reassoc nnan ninf nsz arcp afn noundef <4 x float> @llvm.fmuladd.v4f32(<4 x float> %splat.splat7.i.i, <4 x float> nofpclass(nan inf) %20, <4 x float> %22)
+ %24 = load float, ptr addrspace(12) @blend, align 4
+; CHECK: %[[VAL_FLOAT:[0-9]+]] = OpLoad %[[FLOAT]] %[[PTR_FLOAT_VAL]] Aligned 4
+; CHECK: %[[SPLAT_INS:[0-9]+]] = OpCompositeInsert %[[VEC4]] %[[VAL_FLOAT]] {{.*}} 0
+; CHECK: %[[SPLAT:[0-9]+]] = OpVectorShuffle %[[VEC4]] %[[SPLAT_INS]] {{.*}} 0 0 0 0
+; CHECK: %[[RES:[0-9]+]] = OpFMul %[[VEC4]] {{%[0-9]+}} %[[SPLAT]]
+ %splat.splatinsert.i = insertelement <4 x float> poison, float %24, i64 0
+ %splat.splat.i = shufflevector <4 x float> %splat.splatinsert.i, <4 x float> poison, <4 x i32> zeroinitializer
+ %mul.i = fmul reassoc nnan ninf nsz arcp afn <4 x float> %23, %splat.splat.i
+ %25 = tail call noundef align 16 dereferenceable(16) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0v4f32_12_1t(target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1) %0, i32 0)
+ store <4 x float> %mul.i, ptr addrspace(11) %25, align 16
+; CHECK: OpStore {{%[0-9]+}} %[[RES]] Aligned 16
+ ret void
+}
+
+declare i32 @llvm.spv.thread.id.i32(i32)
+
+declare target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0v4f32_12_1t(i32, i32, i32, i32, ptr)
+
+declare ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0v4f32_12_1t(target("spirv.VulkanBuffer", [0 x <4 x float>], 12, 1), i32)
+
+attributes #1 = { alwaysinline mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
+attributes #3 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+attributes #4 = { mustprogress nofree nosync nounwind willreturn memory(none) }
+
+!hlsl.cbs = !{!0}
+
+!0 = !{ptr @MyCBuffer.cb, ptr addrspace(12) @transforms, ptr addrspace(12) @blend}
\ No newline at end of file
>From 5110e68dfec48ef1c522cc03377c620e16f8af05 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Fri, 21 Nov 2025 12:37:55 -0500
Subject: [PATCH 2/2] [SPIRV] Support Peeled Array Layouts for HLSL CBuffers
This commit adds support for 'peeled arrays' in HLSL constant buffers.
HLSL CBuffers may have padding between array elements but not after the
last element. This is represented in LLVM IR as {[N-1 x {T, pad}], T}.
Changes include:
- Recognition of the peeled array pattern.
- Logic to reconstitute these into SPIR-V compatible arrays.
- Support for spirv.Padding type in GlobalRegistry and Builtins.
- Updates to SPIRVCBufferAccess to correctly calculate member offsets
in these padded structures.
Depends on https://github.com/llvm/llvm-project/pull/169076
---
llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp | 2 +
llvm/lib/Target/SPIRV/SPIRVCBufferAccess.cpp | 11 +++-
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 1 +
llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp | 23 +++++++
llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h | 2 +
llvm/lib/Target/SPIRV/SPIRVIRMapping.h | 5 ++
llvm/lib/Target/SPIRV/SPIRVUtils.cpp | 65 +++++++++++++++++++
llvm/lib/Target/SPIRV/SPIRVUtils.h | 15 +++++
.../cbuffer-peeled-array-minimal.ll | 65 +++++++++++++++++++
.../hlsl-resources/cbuffer-peeled-array.ll | 64 ++++++++++++++++++
10 files changed, 250 insertions(+), 3 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-peeled-array-minimal.ll
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-peeled-array.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
index b2cbdb2ad7375..709f49b0fecc1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
@@ -3373,6 +3373,8 @@ SPIRVType *lowerBuiltinType(const Type *OpaqueType,
TargetType = getInlineSpirvType(BuiltinType, MIRBuilder, GR);
} else if (Name == "spirv.VulkanBuffer") {
TargetType = getVulkanBufferType(BuiltinType, MIRBuilder, GR);
+ } else if (Name == "spirv.Padding") {
+ TargetType = GR->getOrCreatePaddingType(MIRBuilder);
} else if (Name == "spirv.Layout") {
TargetType = getLayoutType(BuiltinType, MIRBuilder, GR);
} else {
diff --git a/llvm/lib/Target/SPIRV/SPIRVCBufferAccess.cpp b/llvm/lib/Target/SPIRV/SPIRVCBufferAccess.cpp
index 329774df554f4..227d8716d974a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVCBufferAccess.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVCBufferAccess.cpp
@@ -79,15 +79,20 @@ static bool replaceCBufferAccesses(Module &M) {
// The handle definition should dominate all uses of the cbuffer members.
// We'll insert our getpointer calls right after it.
IRBuilder<> Builder(HandleDef->getNextNode());
+ auto *HandleTy = cast<TargetExtType>(Mapping.Handle->getValueType());
+ auto *LayoutTy = cast<StructType>(HandleTy->getTypeParameter(0));
+ const StructLayout *SL = M.getDataLayout().getStructLayout(LayoutTy);
- for (uint32_t Index = 0; Index < Mapping.Members.size(); ++Index) {
- GlobalVariable *MemberGV = Mapping.Members[Index].GV;
+ for (const hlsl::CBufferMember &Member : Mapping.Members) {
+ GlobalVariable *MemberGV = Member.GV;
if (MemberGV->use_empty()) {
continue;
}
+ uint32_t IndexInStruct = SL->getElementContainingOffset(Member.Offset);
+
// Create the getpointer intrinsic call.
- Value *IndexVal = Builder.getInt32(Index);
+ Value *IndexVal = Builder.getInt32(IndexInStruct);
Type *PtrType = MemberGV->getType();
Value *GetPointerCall = Builder.CreateIntrinsic(
PtrType, Intrinsic::spv_resource_getpointer, {HandleDef, IndexVal});
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 855d1d33a5b4a..fc76bafe7e329 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -841,6 +841,7 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
uint32_t Index = cast<ConstantInt>(II->getOperand(1))->getZExtValue();
Ty = cast<StructType>(Ty)->getElementType(Index);
}
+ Ty = reconstitutePeeledArrayType(Ty);
} else {
llvm_unreachable("Unknown handle type for spv_resource_getpointer.");
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
index 76fd834fd7219..b67b16c87e862 100644
--- a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
@@ -889,6 +889,17 @@ SPIRVType *SPIRVGlobalRegistry::getOpTypeStruct(
const StructType *Ty, MachineIRBuilder &MIRBuilder,
SPIRV::AccessQualifier::AccessQualifier AccQual,
StructOffsetDecorator Decorator, bool EmitIR) {
+ Type *OriginalElementType = nullptr;
+ uint64_t TotalSize = 0;
+ if (matchPeeledArrayPattern(Ty, OriginalElementType, TotalSize)) {
+ SPIRVType *ElementSPIRVType = findSPIRVType(
+ OriginalElementType, MIRBuilder, AccQual,
+ /* ExplicitLayoutRequired= */ Decorator != nullptr, EmitIR);
+ return getOpTypeArray(TotalSize, ElementSPIRVType, MIRBuilder,
+ /*ExplicitLayoutRequired=*/Decorator != nullptr,
+ EmitIR);
+ }
+
const SPIRVSubtarget &ST =
cast<SPIRVSubtarget>(MIRBuilder.getMF().getSubtarget());
SmallVector<Register, 4> FieldTypes;
@@ -1405,6 +1416,18 @@ SPIRVType *SPIRVGlobalRegistry::getOrCreateVulkanBufferType(
return R;
}
+SPIRVType *
+SPIRVGlobalRegistry::getOrCreatePaddingType(MachineIRBuilder &MIRBuilder) {
+ auto Key = SPIRV::irhandle_padding();
+ if (const MachineInstr *MI = findMI(Key, &MIRBuilder.getMF()))
+ return MI;
+ auto *T = Type::getInt8Ty(MIRBuilder.getContext());
+ SPIRVType *R = getOrCreateSPIRVIntegerType(8, MIRBuilder);
+ finishCreatingSPIRVType(T, R);
+ add(Key, R);
+ return R;
+}
+
SPIRVType *SPIRVGlobalRegistry::getOrCreateLayoutType(
MachineIRBuilder &MIRBuilder, const TargetExtType *T, bool EmitIr) {
auto Key = SPIRV::handle(T);
diff --git a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
index c230e62e795e8..f6098634a81dc 100644
--- a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
+++ b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
@@ -611,6 +611,8 @@ class SPIRVGlobalRegistry : public SPIRVIRMapping {
SPIRV::StorageClass::StorageClass SC,
bool IsWritable, bool EmitIr = false);
+ SPIRVType *getOrCreatePaddingType(MachineIRBuilder &MIRBuilder);
+
SPIRVType *getOrCreateLayoutType(MachineIRBuilder &MIRBuilder,
const TargetExtType *T, bool EmitIr = false);
diff --git a/llvm/lib/Target/SPIRV/SPIRVIRMapping.h b/llvm/lib/Target/SPIRV/SPIRVIRMapping.h
index c99d603d340ea..47c7676d5631c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVIRMapping.h
+++ b/llvm/lib/Target/SPIRV/SPIRVIRMapping.h
@@ -64,6 +64,7 @@ enum SpecialTypeKind {
STK_Value,
STK_MachineInstr,
STK_VkBuffer,
+ STK_Padding,
STK_ExplictLayoutType,
STK_Last = -1
};
@@ -149,6 +150,10 @@ inline IRHandle irhandle_vkbuffer(const Type *ElementType,
SpecialTypeKind::STK_VkBuffer);
}
+inline IRHandle irhandle_padding() {
+ return std::make_tuple(nullptr, 0, SpecialTypeKind::STK_Padding);
+}
+
inline IRHandle irhandle_explict_layout_type(const Type *Ty) {
const Type *WrpTy = unifyPtrType(Ty);
return irhandle_ptr(WrpTy, Ty->getTypeID(), STK_ExplictLayoutType);
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 8f2fc01da476f..3a5069928848b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -1042,6 +1042,71 @@ getFirstValidInstructionInsertPoint(MachineBasicBlock &BB) {
: VarPos;
}
+bool matchPeeledArrayPattern(const StructType *Ty, Type *&OriginalElementType,
+ uint64_t &TotalSize) {
+ // An array of N padded structs is represented as {[N-1 x <{T, pad}>], T}.
+ if (!Ty->isStructTy() || Ty->getStructNumElements() != 2)
+ return false;
+
+ Type *FirstElement = Ty->getStructElementType(0);
+ Type *SecondElement = Ty->getStructElementType(1);
+
+ if (!FirstElement->isArrayTy())
+ return false;
+
+ Type *ArrayElementType = FirstElement->getArrayElementType();
+ if (!ArrayElementType->isStructTy() ||
+ ArrayElementType->getStructNumElements() != 2)
+ return false;
+
+ Type *T_in_struct = ArrayElementType->getStructElementType(0);
+ if (T_in_struct != SecondElement)
+ return false;
+
+ const uint64_t ArraySize = FirstElement->getArrayNumElements();
+ TotalSize = ArraySize + 1;
+ OriginalElementType = ArrayElementType;
+ return true;
+}
+
+Type *reconstitutePeeledArrayType(Type *Ty) {
+ if (!Ty->isStructTy())
+ return Ty;
+
+ auto *STy = cast<StructType>(Ty);
+ Type *OriginalElementType = nullptr;
+ uint64_t TotalSize = 0;
+ if (matchPeeledArrayPattern(STy, OriginalElementType, TotalSize)) {
+ Type *ResultTy = ArrayType::get(
+ reconstitutePeeledArrayType(OriginalElementType), TotalSize);
+ return ResultTy;
+ }
+
+ SmallVector<Type *, 4> NewElementTypes;
+ bool Changed = false;
+ for (Type *ElementTy : STy->elements()) {
+ Type *NewElementTy = reconstitutePeeledArrayType(ElementTy);
+ if (NewElementTy != ElementTy)
+ Changed = true;
+ NewElementTypes.push_back(NewElementTy);
+ }
+
+ if (Changed) {
+ Type *ResultTy;
+ if (STy->isLiteral())
+ ResultTy =
+ StructType::get(STy->getContext(), NewElementTypes, STy->isPacked());
+ else {
+ auto *NewTy = StructType::create(STy->getContext(), STy->getName());
+ NewTy->setBody(NewElementTypes, STy->isPacked());
+ ResultTy = NewTy;
+ }
+ return ResultTy;
+ }
+
+ return Ty;
+}
+
std::optional<SPIRV::LinkageType::LinkageType>
getSpirvLinkageTypeFor(const SPIRVSubtarget &ST, const GlobalValue &GV) {
if (GV.hasLocalLinkage() || GV.hasHiddenVisibility())
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index 99d9d403ea70c..45e211a1e5d2a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -321,6 +321,21 @@ Type *parseBasicTypeName(StringRef &TypeName, LLVMContext &Ctx);
// Returns true if the function was changed.
bool sortBlocks(Function &F);
+// Check for peeled array structs and recursively reconstitute them. In HLSL
+// CBuffers, arrays may have padding between the elements, but not after the
+// last element. To represent this in LLVM IR an array [N x T] will be
+// represented as {[N-1 x {T, spirv.Padding}], T}. The function
+// matchPeeledArrayPattern recognizes this pattern retrieving the type {T,
+// spirv.Padding}, and the size N.
+bool matchPeeledArrayPattern(const StructType *Ty, Type *&OriginalElementType,
+ uint64_t &TotalSize);
+
+// This function will turn the type {[N-1 x {T, spirv.Padding}], T} back into
+// [N x {T, spirv.Padding}]. So it can be translated into SPIR-V. The offset
+// decorations will be such that there will be no padding after the array when
+// relevant.
+Type *reconstitutePeeledArrayType(Type *Ty);
+
inline bool hasInitializer(const GlobalVariable *GV) {
return GV->hasInitializer() && !isa<UndefValue>(GV->getInitializer());
}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-peeled-array-minimal.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-peeled-array-minimal.ll
new file mode 100644
index 0000000000000..20a8a2fc0e776
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-peeled-array-minimal.ll
@@ -0,0 +1,65 @@
+; RUN: llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: %[[FLOAT:[0-9]+]] = OpTypeFloat 32
+; CHECK: %[[STRUCT:[0-9]+]] = OpTypeStruct %[[FLOAT]]
+; CHECK: %[[I8:[0-9]+]] = OpTypeInt 8 0
+; CHECK: %[[STRUCT_PAD:[0-9]+]] = OpTypeStruct %[[STRUCT]] %[[I8]]
+; CHECK: %[[UINT:[0-9]+]] = OpTypeInt 32 0
+; CHECK: %[[CONST_4:[0-9]+]] = OpConstant %[[UINT]] 4
+; CHECK: %[[ARRAY:[0-9]+]] = OpTypeArray %[[STRUCT_PAD]] %[[CONST_4]]
+; CHECK: %[[CBLAYOUT:[0-9]+]] = OpTypeStruct %[[ARRAY]] %[[FLOAT]]
+; CHECK: %[[WRAPPER:[0-9]+]] = OpTypeStruct %[[CBLAYOUT]]
+; CHECK: %[[PTR_WRAPPER:[0-9]+]] = OpTypePointer Uniform %[[WRAPPER]]
+; CHECK: %[[ZERO:[0-9]+]] = OpConstant %[[UINT]] 0
+; CHECK: %[[MYCBUFFER:[0-9]+]] = OpVariable %[[PTR_WRAPPER]] Uniform
+
+%__cblayout_MyCBuffer = type <{ <{ [3 x <{ %OrigType, target("spirv.Padding", 12) }>], %OrigType }>, float }>
+%OrigType = type <{ float }>
+
+ at MyCBuffer.cb = local_unnamed_addr global target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) poison
+ at myArray = external hidden local_unnamed_addr addrspace(12) global <{ [3 x <{ %OrigType, target("spirv.Padding", 12) }>], %OrigType }>, align 1
+ at MyCBuffer.str = private unnamed_addr constant [10 x i8] c"MyCBuffer\00", align 1
+ at .str = private unnamed_addr constant [7 x i8] c"output\00", align 1
+
+declare target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_s___cblayout_MyCBuffers_2_0t(i32, i32, i32, i32, ptr)
+
+define void @main() #1 {
+entry:
+; CHECK: %[[BUFFER_HANDLE:[0-9]+]] = OpCopyObject %[[PTR_WRAPPER]] %[[MYCBUFFER]]
+; CHECK: %[[ACCESS_ARRAY:[0-9]+]] = OpAccessChain {{%[0-9]+}} %[[BUFFER_HANDLE]] %[[ZERO]] %[[ZERO]]
+ %MyCBuffer.cb_h.i.i = tail call target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_s___cblayout_MyCBuffers_2_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @MyCBuffer.str)
+ store target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) %MyCBuffer.cb_h.i.i, ptr @MyCBuffer.cb, align 8
+
+ %0 = tail call target("spirv.Image", float, 5, 2, 0, 0, 2, 1) @llvm.spv.resource.handlefromimplicitbinding.tspirv.Image_f32_5_2_0_0_2_1t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %1 = tail call i32 @llvm.spv.thread.id.i32(i32 0)
+ %rem.i = and i32 %1, 3
+
+; CHECK: %[[IDX_CONV:[0-9]+]] = OpUConvert {{.*}}
+ %idxprom.i = zext nneg i32 %rem.i to i64
+
+; CHECK: %[[PTR_ELEM:[0-9]+]] = OpAccessChain {{%[0-9]+}} %[[ACCESS_ARRAY]] %[[IDX_CONV]]
+ %cbufferidx.i = getelementptr <{ %OrigType, target("spirv.Padding", 12) }>, ptr addrspace(12) @myArray, i64 %idxprom.i
+
+; CHECK: %[[PTR_FIELD:[0-9]+]] = OpAccessChain {{%[0-9]+}} %[[PTR_ELEM]] %[[ZERO]] %[[ZERO]]
+; CHECK: %[[VAL_FLOAT:[0-9]+]] = OpLoad %[[FLOAT]] %[[PTR_FIELD]] Aligned 4
+ %2 = load float, ptr addrspace(12) %cbufferidx.i, align 4
+
+ %vecinit4.i = insertelement <4 x float> <float poison, float 0.000000e+00, float 0.000000e+00, float 0.000000e+00>, float %2, i64 0
+ %3 = tail call noundef align 16 dereferenceable(16) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.Image_f32_5_2_0_0_2_1t(target("spirv.Image", float, 5, 2, 0, 0, 2, 1) %0, i32 0)
+ store <4 x float> %vecinit4.i, ptr addrspace(11) %3, align 16
+; CHECK: OpImageWrite {{%[0-9]+}} {{%[0-9]+}} {{%[0-9]+}}
+ ret void
+}
+
+declare i32 @llvm.spv.thread.id.i32(i32)
+
+declare target("spirv.Image", float, 5, 2, 0, 0, 2, 1) @llvm.spv.resource.handlefromimplicitbinding.tspirv.Image_f32_5_2_0_0_2_1t(i32, i32, i32, i32, ptr)
+
+declare ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.Image_f32_5_2_0_0_2_1t(target("spirv.Image", float, 5, 2, 0, 0, 2, 1), i32)
+
+attributes #1 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+!hlsl.cbs = !{!0}
+
+!0 = distinct !{ptr @MyCBuffer.cb, ptr addrspace(12) @myArray, null}
\ No newline at end of file
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-peeled-array.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-peeled-array.ll
new file mode 100644
index 0000000000000..e95f149e043e2
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/cbuffer-peeled-array.ll
@@ -0,0 +1,64 @@
+; RUN: llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: %[[FLOAT:[0-9]+]] = OpTypeFloat 32
+; CHECK: %[[VEC3:[0-9]+]] = OpTypeVector %[[FLOAT]] 3
+; CHECK: %[[I8:[0-9]+]] = OpTypeInt 8 0
+; CHECK: %[[STRUCT_PAD:[0-9]+]] = OpTypeStruct %[[VEC3]] %[[I8]]
+; CHECK: %[[UINT:[0-9]+]] = OpTypeInt 32 0
+; CHECK: %[[CONST_3:[0-9]+]] = OpConstant %[[UINT]] 3
+; CHECK: %[[ARRAY:[0-9]+]] = OpTypeArray %[[STRUCT_PAD]] %[[CONST_3]]
+; CHECK: %[[CBLAYOUT:[0-9]+]] = OpTypeStruct %[[ARRAY]]
+; CHECK: %[[WRAPPER:[0-9]+]] = OpTypeStruct %[[CBLAYOUT]]
+; CHECK: %[[PTR_WRAPPER:[0-9]+]] = OpTypePointer Uniform %[[WRAPPER]]
+; CHECK: %[[ZERO:[0-9]+]] = OpConstant %[[UINT]] 0
+; CHECK: %[[MYCBUFFER:[0-9]+]] = OpVariable %[[PTR_WRAPPER]] Uniform
+
+%__cblayout_MyCBuffer = type <{ <{ [2 x <{ <3 x float>, target("spirv.Padding", 4) }>], <3 x float> }> }>
+
+ at MyCBuffer.cb = local_unnamed_addr global target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) poison
+ at myArray = external hidden local_unnamed_addr addrspace(12) global <{ [2 x <{ <3 x float>, target("spirv.Padding", 4) }>], <3 x float> }>, align 16
+ at MyCBuffer.str = private unnamed_addr constant [10 x i8] c"MyCBuffer\00", align 1
+ at .str = private unnamed_addr constant [7 x i8] c"output\00", align 1
+
+declare target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_s___cblayout_MyCBuffers_2_0t(i32, i32, i32, i32, ptr)
+
+define void @main() #1 {
+entry:
+; CHECK: %[[BUFFER_HANDLE:[0-9]+]] = OpCopyObject %[[PTR_WRAPPER]] %[[MYCBUFFER]]
+; CHECK: %[[ACCESS_ARRAY:[0-9]+]] = OpAccessChain {{%[0-9]+}} %[[BUFFER_HANDLE]] %[[ZERO]] %[[ZERO]]
+ %MyCBuffer.cb_h.i.i = tail call target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_s___cblayout_MyCBuffers_2_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @MyCBuffer.str)
+ store target("spirv.VulkanBuffer", %__cblayout_MyCBuffer, 2, 0) %MyCBuffer.cb_h.i.i, ptr @MyCBuffer.cb, align 8
+
+ %0 = tail call target("spirv.VulkanBuffer", [0 x <3 x float>], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0v3f32_12_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %1 = tail call i32 @llvm.spv.thread.id.i32(i32 0)
+
+; CHECK: %[[IDX:[0-9]+]] = OpUMod %[[UINT]] {{%[0-9]+}} %[[CONST_3]]
+ %rem.i = urem i32 %1, 3
+
+; CHECK: %[[IDX_CONV:[0-9]+]] = OpUConvert {{.*}} %[[IDX]]
+ %idxprom.i = zext nneg i32 %rem.i to i64
+
+; CHECK: %[[PTR_ELEM:[0-9]+]] = OpAccessChain {{%[0-9]+}} %[[ACCESS_ARRAY]] %[[IDX_CONV]]
+ %cbufferidx.i = getelementptr <{ <3 x float>, target("spirv.Padding", 4) }>, ptr addrspace(12) @myArray, i64 %idxprom.i
+
+; CHECK: %[[PTR_FIELD:[0-9]+]] = OpAccessChain {{%[0-9]+}} %[[PTR_ELEM]] {{.*}}
+; CHECK: %[[VAL_VEC3:[0-9]+]] = OpLoad %[[VEC3]] %[[PTR_FIELD]] Aligned 16
+ %2 = load <3 x float>, ptr addrspace(12) %cbufferidx.i, align 16
+
+ %3 = tail call noundef align 16 dereferenceable(16) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0v3f32_12_1t(target("spirv.VulkanBuffer", [0 x <3 x float>], 12, 1) %0, i32 %1)
+ store <3 x float> %2, ptr addrspace(11) %3, align 16
+ ret void
+}
+
+declare i32 @llvm.spv.thread.id.i32(i32)
+
+declare target("spirv.VulkanBuffer", [0 x <3 x float>], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0v3f32_12_1t(i32, i32, i32, i32, ptr)
+
+declare ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0v3f32_12_1t(target("spirv.VulkanBuffer", [0 x <3 x float>], 12, 1), i32)
+
+attributes #1 = { "hlsl.numthreads"="8,1,1" "hlsl.shader"="compute" }
+
+!hlsl.cbs = !{!0}
+
+!0 = !{ptr @MyCBuffer.cb, ptr addrspace(12) @myArray}
\ No newline at end of file
More information about the llvm-commits
mailing list