Skip to content

Commit 75405bc

Browse files
committed
[SPIRV][HLSL] Implement CBuffer access lowering pass
This patch introduces a new pass, SPIRVCBufferAccess, which is responsible for translating accesses to HLSL constant buffer (cbuffer) global variables into accesses to the proper SPIR-V resource. The pass operates by: 1. Identifying all cbuffers via the `!hlsl.cbs` metadata. 2. Replacing all uses of cbuffer member global variables with `llvm.spv.resource.getpointer` intrinsics. 3. Cleaning up the original global variables and metadata. This approach allows subsequent passes, like SPIRVEmitIntrinsics, to correctly fold GEPs into a single OpAccessChain instruction. The patch also includes a comprehensive set of lit tests to cover various scenarios: - Basic cbuffer access. - Cbuffer access within a `ConstantExpr`. - Unused and partially unused cbuffers. This implements the SPIR-V version of https://github.com/llvm/wg-hlsl/blob/main/proposals/0016-constant-buffers.md#lowering-to-buffer-load-intrinsics.
1 parent 7bc91f3 commit 75405bc

File tree

10 files changed

+380
-2
lines changed

10 files changed

+380
-2
lines changed

llvm/lib/Frontend/HLSL/CBuffer.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ using namespace llvm::hlsl;
1717

1818
static size_t getMemberOffset(GlobalVariable *Handle, size_t Index) {
1919
auto *HandleTy = cast<TargetExtType>(Handle->getValueType());
20-
assert(HandleTy->getName().ends_with(".CBuffer") && "Not a cbuffer type");
20+
assert((HandleTy->getName().ends_with(".CBuffer") ||
21+
HandleTy->getName() == "spirv.VulkanBuffer") &&
22+
"Not a cbuffer type");
2123
assert(HandleTy->getNumTypeParameters() == 1 && "Expected layout type");
2224

2325
auto *LayoutTy = cast<TargetExtType>(HandleTy->getTypeParameter(0));

llvm/lib/Target/SPIRV/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ add_llvm_target(SPIRVCodeGen
4848
SPIRVTargetTransformInfo.cpp
4949
SPIRVUtils.cpp
5050
SPIRVEmitNonSemanticDI.cpp
51+
SPIRVCBufferAccess.cpp
5152

5253
LINK_COMPONENTS
5354
Analysis
@@ -57,8 +58,9 @@ add_llvm_target(SPIRVCodeGen
5758
Core
5859
Demangle
5960
GlobalISel
60-
SPIRVAnalysis
61+
FrontendHLSL
6162
MC
63+
SPIRVAnalysis
6264
SPIRVDesc
6365
SPIRVInfo
6466
ScalarOpts

llvm/lib/Target/SPIRV/SPIRV.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class RegisterBankInfo;
2121

2222
ModulePass *createSPIRVPrepareFunctionsPass(const SPIRVTargetMachine &TM);
2323
FunctionPass *createSPIRVStructurizerPass();
24+
ModulePass *createSPIRVCBufferAccessLegacyPass();
2425
FunctionPass *createSPIRVMergeRegionExitTargetsPass();
2526
FunctionPass *createSPIRVStripConvergenceIntrinsicsPass();
2627
ModulePass *createSPIRVLegalizeImplicitBindingPass();
@@ -43,6 +44,7 @@ void initializeSPIRVPreLegalizerPass(PassRegistry &);
4344
void initializeSPIRVPreLegalizerCombinerPass(PassRegistry &);
4445
void initializeSPIRVPostLegalizerPass(PassRegistry &);
4546
void initializeSPIRVStructurizerPass(PassRegistry &);
47+
void initializeSPIRVCBufferAccessLegacyPass(PassRegistry &);
4648
void initializeSPIRVEmitIntrinsicsPass(PassRegistry &);
4749
void initializeSPIRVEmitNonSemanticDIPass(PassRegistry &);
4850
void initializeSPIRVLegalizePointerCastPass(PassRegistry &);
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
//===- SPIRVCBufferAccess.cpp - Translate CBuffer Loads
2+
//--------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This pass replaces all accesses to constant buffer global variables with
11+
// accesses to the proper SPIR-V resource. It's designed to run after the
12+
// DXIL preparation passes and before the main SPIR-V legalization passes.
13+
//
14+
// The pass operates as follows:
15+
// 1. It finds all constant buffers by looking for the `!hlsl.cbs` metadata.
16+
// 2. For each cbuffer, it finds the global variable holding the resource handle
17+
// and the global variables for each of the cbuffer's members.
18+
// 3. For each member variable, it creates a call to the
19+
// `llvm.spv.resource.getpointer` intrinsic. This intrinsic takes the
20+
// resource handle and the member's index within the cbuffer as arguments.
21+
// The result is a pointer to that member within the SPIR-V resource.
22+
// 4. It then replaces all uses of the original member global variable with the
23+
// pointer returned by the `getpointer` intrinsic. This effectively retargets
24+
// all loads and GEPs to the new resource pointer.
25+
// 5. Finally, it cleans up by deleting the original global variables and the
26+
// `!hlsl.cbs` metadata.
27+
//
28+
// This approach allows subsequent passes, like SPIRVEmitIntrinsics, to
29+
// correctly handle GEPs that operate on the result of the `getpointer` call,
30+
// folding them into a single OpAccessChain instruction.
31+
//
32+
//===----------------------------------------------------------------------===//
33+
34+
#include "SPIRVCBufferAccess.h"
35+
#include "SPIRV.h"
36+
#include "llvm/Frontend/HLSL/CBuffer.h"
37+
#include "llvm/IR/IRBuilder.h"
38+
#include "llvm/IR/IntrinsicsSPIRV.h"
39+
#include "llvm/IR/Module.h"
40+
41+
#define DEBUG_TYPE "spirv-cbuffer-access"
42+
using namespace llvm;
43+
44+
// Finds the single instruction that defines the resource handle. This is
45+
// typically a call to `llvm.spv.resource.handlefrombinding`.
46+
static Instruction *findHandleDef(GlobalVariable *HandleVar) {
47+
for (User *U : HandleVar->users()) {
48+
if (auto *SI = dyn_cast<StoreInst>(U)) {
49+
if (auto *I = dyn_cast<Instruction>(SI->getValueOperand())) {
50+
return I;
51+
}
52+
}
53+
}
54+
return nullptr;
55+
}
56+
57+
static bool replaceCBufferAccesses(Module &M) {
58+
std::optional<hlsl::CBufferMetadata> CBufMD = hlsl::CBufferMetadata::get(M);
59+
if (!CBufMD)
60+
return false;
61+
62+
for (const hlsl::CBufferMapping &Mapping : *CBufMD) {
63+
Instruction *HandleDef = findHandleDef(Mapping.Handle);
64+
if (!HandleDef) {
65+
// If there's no handle definition, it might be because the cbuffer is
66+
// unused. In this case, we can just clean up the globals.
67+
if (Mapping.Handle->use_empty()) {
68+
for (const auto &Member : Mapping.Members) {
69+
if (Member.GV->use_empty()) {
70+
Member.GV->eraseFromParent();
71+
}
72+
}
73+
Mapping.Handle->eraseFromParent();
74+
}
75+
continue;
76+
}
77+
78+
// The handle definition should dominate all uses of the cbuffer members.
79+
// We'll insert our getpointer calls right after it.
80+
IRBuilder<> Builder(HandleDef->getNextNode());
81+
82+
for (uint32_t Index = 0; Index < Mapping.Members.size(); ++Index) {
83+
GlobalVariable *MemberGV = Mapping.Members[Index].GV;
84+
if (MemberGV->use_empty()) {
85+
continue;
86+
}
87+
88+
// Create the getpointer intrinsic call.
89+
Value *IndexVal = Builder.getInt32(Index);
90+
Type *PtrType = MemberGV->getType();
91+
Value *GetPointerCall = Builder.CreateIntrinsic(
92+
PtrType, Intrinsic::spv_resource_getpointer, {HandleDef, IndexVal});
93+
94+
// We cannot use replaceAllUsesWith here because some uses may be
95+
// ConstantExprs, which cannot be replaced with non-constants.
96+
SmallVector<User *, 4> Users(MemberGV->users());
97+
for (User *U : Users) {
98+
if (auto *CE = dyn_cast<ConstantExpr>(U)) {
99+
SmallVector<Instruction *, 4> Insts;
100+
std::function<void(ConstantExpr *)> findInstructions =
101+
[&](ConstantExpr *Const) {
102+
for (User *ConstU : Const->users()) {
103+
if (auto *ConstCE = dyn_cast<ConstantExpr>(ConstU)) {
104+
findInstructions(ConstCE);
105+
} else if (auto *I = dyn_cast<Instruction>(ConstU)) {
106+
Insts.push_back(I);
107+
}
108+
}
109+
};
110+
findInstructions(CE);
111+
112+
for (Instruction *I : Insts) {
113+
Instruction *NewInst = CE->getAsInstruction();
114+
NewInst->insertBefore(I);
115+
I->replaceUsesOfWith(CE, NewInst);
116+
NewInst->replaceUsesOfWith(MemberGV, GetPointerCall);
117+
}
118+
} else {
119+
U->replaceUsesOfWith(MemberGV, GetPointerCall);
120+
}
121+
}
122+
}
123+
}
124+
125+
// Now that all uses are replaced, clean up the globals and metadata.
126+
for (const hlsl::CBufferMapping &Mapping : *CBufMD) {
127+
for (const auto &Member : Mapping.Members) {
128+
Member.GV->eraseFromParent();
129+
}
130+
// Erase the stores to the handle variable before erasing the handle itself.
131+
SmallVector<Instruction *, 4> HandleStores;
132+
for (User *U : Mapping.Handle->users()) {
133+
if (auto *SI = dyn_cast<StoreInst>(U)) {
134+
HandleStores.push_back(SI);
135+
}
136+
}
137+
for (Instruction *I : HandleStores) {
138+
I->eraseFromParent();
139+
}
140+
Mapping.Handle->eraseFromParent();
141+
}
142+
143+
CBufMD->eraseFromModule();
144+
return true;
145+
}
146+
147+
PreservedAnalyses SPIRVCBufferAccess::run(Module &M,
148+
ModuleAnalysisManager &AM) {
149+
if (replaceCBufferAccesses(M)) {
150+
return PreservedAnalyses::none();
151+
}
152+
return PreservedAnalyses::all();
153+
}
154+
155+
namespace {
156+
class SPIRVCBufferAccessLegacy : public ModulePass {
157+
public:
158+
bool runOnModule(Module &M) override { return replaceCBufferAccesses(M); }
159+
StringRef getPassName() const override { return "SPIRV CBuffer Access"; }
160+
SPIRVCBufferAccessLegacy() : ModulePass(ID) {}
161+
162+
static char ID; // Pass identification.
163+
};
164+
char SPIRVCBufferAccessLegacy::ID = 0;
165+
} // end anonymous namespace
166+
167+
INITIALIZE_PASS(SPIRVCBufferAccessLegacy, DEBUG_TYPE, "SPIRV CBuffer Access",
168+
false, false)
169+
170+
ModulePass *llvm::createSPIRVCBufferAccessLegacyPass() {
171+
return new SPIRVCBufferAccessLegacy();
172+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//===- SPIRVCBufferAccess.cpp - Translate CBuffer Loads
2+
//--------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
9+
#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVCBUFFERACCESS_H_
10+
#define LLVM_LIB_TARGET_SPIRV_SPIRVCBUFFERACCESS_H_
11+
12+
#include "llvm/IR/PassManager.h"
13+
14+
namespace llvm {
15+
16+
class SPIRVCBufferAccess : public PassInfoMixin<SPIRVCBufferAccess> {
17+
public:
18+
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
19+
};
20+
21+
} // namespace llvm
22+
23+
#endif // LLVM_LIB_TARGET_SPIRV_SPIRVCBUFFERACCESS_H_

llvm/lib/Target/SPIRV/SPIRVPassRegistry.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313

1414
// NOTE: NO INCLUDE GUARD DESIRED!
1515

16+
#ifndef MODULE_PASS
17+
#define MODULE_PASS(NAME, CREATE_PASS)
18+
#endif
19+
MODULE_PASS("spirv-cbuffer-access", SPIRVCBufferAccess())
20+
#undef MODULE_PASS
1621

1722
#ifndef FUNCTION_PASS
1823
#define FUNCTION_PASS(NAME, CREATE_PASS)

llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
#include "SPIRVTargetMachine.h"
1414
#include "SPIRV.h"
15+
#include "SPIRVCBufferAccess.h"
16+
#include "SPIRVCallLowering.h"
1517
#include "SPIRVGlobalRegistry.h"
1618
#include "SPIRVLegalizerInfo.h"
1719
#include "SPIRVStructurizerWrapper.h"
@@ -48,6 +50,7 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTarget() {
4850
initializeSPIRVAsmPrinterPass(PR);
4951
initializeSPIRVConvergenceRegionAnalysisWrapperPassPass(PR);
5052
initializeSPIRVStructurizerPass(PR);
53+
initializeSPIRVCBufferAccessLegacyPass(PR);
5154
initializeSPIRVPreLegalizerCombinerPass(PR);
5255
initializeSPIRVLegalizePointerCastPass(PR);
5356
initializeSPIRVRegularizerPass(PR);
@@ -206,6 +209,7 @@ void SPIRVPassConfig::addISelPrepare() {
206209

207210
addPass(createSPIRVStripConvergenceIntrinsicsPass());
208211
addPass(createSPIRVLegalizeImplicitBindingPass());
212+
addPass(createSPIRVCBufferAccessLegacyPass());
209213
addPass(createSPIRVEmitIntrinsicsPass(&getTM<SPIRVTargetMachine>()));
210214
if (TM.getSubtargetImpl()->isLogicalSPIRV())
211215
addPass(createSPIRVLegalizePointerCastPass(&getTM<SPIRVTargetMachine>()));
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
2+
3+
; CHECK-DAG: OpDecorate %[[MyCBuffer:[0-9]+]] DescriptorSet 0
4+
; CHECK-DAG: OpDecorate %[[MyCBuffer]] Binding 0
5+
; CHECK-DAG: OpMemberDecorate %[[__cblayout_MyCBuffer:[0-9]+]] 0 Offset 0
6+
; CHECK-DAG: OpMemberDecorate %[[__cblayout_MyCBuffer]] 1 Offset 16
7+
; CHECK-DAG: %[[uint:[0-9]+]] = OpTypeInt 32 0
8+
; CHECK-DAG: %[[uint_0:[0-9]+]] = OpConstant %[[uint]] 0{{$}}
9+
; CHECK-DAG: %[[uint_1:[0-9]+]] = OpConstant %[[uint]] 1{{$}}
10+
; CHECK-DAG: %[[float:[0-9]+]] = OpTypeFloat 32
11+
; CHECK-DAG: %[[v4float:[0-9]+]] = OpTypeVector %[[float]] 4
12+
; CHECK-DAG: %[[__cblayout_MyCBuffer]] = OpTypeStruct %[[v4float]] %[[v4float]]
13+
; CHECK-DAG: %[[wrapper:[0-9]+]] = OpTypeStruct %[[__cblayout_MyCBuffer]]
14+
; CHECK-DAG: %[[wrapper_ptr_t:[0-9]+]] = OpTypePointer Uniform %[[wrapper]]
15+
; CHECK-DAG: %[[MyCBuffer]] = OpVariable %[[wrapper_ptr_t]] Uniform
16+
; CHECK-DAG: %[[_ptr_Uniform_v4float:[0-9]+]] = OpTypePointer Uniform %[[v4float]]
17+
18+
%__cblayout_MyCBuffer = type <{ <4 x float>, <4 x float> }>
19+
20+
@MyCBuffer.cb = local_unnamed_addr global target("spirv.VulkanBuffer", target("spirv.Layout", %__cblayout_MyCBuffer, 32, 0, 16), 2, 0) poison
21+
@a = external hidden local_unnamed_addr addrspace(12) global <4 x float>, align 16
22+
@b = external hidden local_unnamed_addr addrspace(12) global <4 x float>, align 16
23+
@MyCBuffer.str = private unnamed_addr constant [10 x i8] c"MyCBuffer\00", align 1
24+
@.str = private unnamed_addr constant [7 x i8] c"output\00", align 1
25+
26+
; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, argmem: write, inaccessiblemem: none)
27+
define void @main() local_unnamed_addr #1 {
28+
entry:
29+
; CHECK: %[[tmp:[0-9]+]] = OpCopyObject %[[wrapper_ptr_t]] %[[MyCBuffer]]
30+
%MyCBuffer.cb_h.i.i = tail call target("spirv.VulkanBuffer", target("spirv.Layout", %__cblayout_MyCBuffer, 32, 0, 16), 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_tspirv.Layout_s___cblayout_MyCBuffers_32_0_16t_2_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @MyCBuffer.str)
31+
store target("spirv.VulkanBuffer", target("spirv.Layout", %__cblayout_MyCBuffer, 32, 0, 16), 2, 0) %MyCBuffer.cb_h.i.i, ptr @MyCBuffer.cb, align 8
32+
%0 = tail call target("spirv.Image", <4 x float>, 5, 2, 0, 0, 2, 3) @llvm.spv.resource.handlefrombinding.tspirv.Image_v4f32_5_2_0_0_2_3t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
33+
; CHECK: %[[a_ptr:.+]] = OpAccessChain %[[_ptr_Uniform_v4float]] %[[tmp]] %[[uint_0]] %[[uint_0]]
34+
; CHECK: %[[b_ptr:.+]] = OpAccessChain %[[_ptr_Uniform_v4float]] %[[tmp]] %[[uint_0]] %[[uint_1]]
35+
; CHECK: %[[a_val:.+]] = OpLoad %[[v4float]] %[[a_ptr]]
36+
; CHECK: %[[b_val:.+]] = OpLoad %[[v4float]] %[[b_ptr]]
37+
%a_val = load <4 x float>, ptr addrspace(12) @a, align 16
38+
%b_val = load <4 x float>, ptr addrspace(12) @b, align 16
39+
%add = fadd <4 x float> %a_val, %b_val
40+
%output_ptr = tail call noundef ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.Image_v4f32_5_2_0_0_2_3t(target("spirv.Image", <4 x float>, 5, 2, 0, 0, 2, 3) %0, i32 0)
41+
store <4 x float> %add, ptr addrspace(11) %output_ptr, align 16
42+
ret void
43+
}
44+
45+
attributes #1 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
46+
47+
!hlsl.cbs = !{!0}
48+
49+
!0 = !{ptr @MyCBuffer.cb, ptr addrspace(12) @a, ptr addrspace(12) @b}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
2+
; Test that uses of cbuffer members inside ConstantExprs are handled correctly.
3+
4+
; CHECK-DAG: OpDecorate %[[MyCBuffer:[0-9]+]] DescriptorSet 0
5+
; CHECK-DAG: OpDecorate %[[MyCBuffer]] Binding 0
6+
; CHECK-DAG: OpMemberDecorate %[[__cblayout_MyCBuffer:[0-9]+]] 0 Offset 0
7+
; CHECK-DAG: OpMemberDecorate %[[__cblayout_MyCBuffer]] 1 Offset 16
8+
; CHECK-DAG: %[[uint:[0-9]+]] = OpTypeInt 32 0
9+
; CHECK-DAG: %[[uint_0:[0-9]+]] = OpConstant %[[uint]] 0{{$}}
10+
; CHECK-DAG: %[[uint_1:[0-9]+]] = OpConstant %[[uint]] 1{{$}}
11+
; CHECK-DAG: %[[float:[0-9]+]] = OpTypeFloat 32
12+
; CHECK-DAG: %[[v4float:[0-9]+]] = OpTypeVector %[[float]] 4
13+
; CHECK-DAG: %[[MyStruct:[0-9]+]] = OpTypeStruct %[[v4float]]
14+
; CHECK-DAG: %[[__cblayout_MyCBuffer]] = OpTypeStruct %[[MyStruct]] %[[v4float]]
15+
; CHECK-DAG: %[[wrapper:[0-9]+]] = OpTypeStruct %[[__cblayout_MyCBuffer]]
16+
; CHECK-DAG: %[[wrapper_ptr_t:[0-9]+]] = OpTypePointer Uniform %[[wrapper]]
17+
; CHECK-DAG: %[[MyCBuffer]] = OpVariable %[[wrapper_ptr_t]] Uniform
18+
; CHECK-DAG: %[[_ptr_Uniform_v4float:[0-9]+]] = OpTypePointer Uniform %[[v4float]]
19+
; CHECK-DAG: %[[_ptr_Uniform_float:[0-9]+]] = OpTypePointer Uniform %[[float]]
20+
21+
%MyStruct = type { <4 x float> }
22+
%__cblayout_MyCBuffer = type <{ %MyStruct, <4 x float> }>
23+
24+
@MyCBuffer.cb = local_unnamed_addr global target("spirv.VulkanBuffer", target("spirv.Layout", %__cblayout_MyCBuffer, 32, 0, 16), 2, 0) poison
25+
@s = external hidden local_unnamed_addr addrspace(12) global %MyStruct, align 16
26+
@v = external hidden local_unnamed_addr addrspace(12) global <4 x float>, align 16
27+
@MyCBuffer.str = private unnamed_addr constant [10 x i8] c"MyCBuffer\00", align 1
28+
@.str = private unnamed_addr constant [7 x i8] c"output\00", align 1
29+
30+
define void @main() {
31+
entry:
32+
; CHECK: %[[tmp:[0-9]+]] = OpCopyObject %[[wrapper_ptr_t]] %[[MyCBuffer]]
33+
%MyCBuffer.cb_h.i.i = tail call target("spirv.VulkanBuffer", target("spirv.Layout", %__cblayout_MyCBuffer, 32, 0, 16), 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_tspirv.Layout_s___cblayout_MyCBuffers_32_0_16t_2_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @MyCBuffer.str)
34+
store target("spirv.VulkanBuffer", target("spirv.Layout", %__cblayout_MyCBuffer, 32, 0, 16), 2, 0) %MyCBuffer.cb_h.i.i, ptr @MyCBuffer.cb, align 8
35+
%0 = tail call target("spirv.Image", float, 5, 2, 0, 0, 2, 3) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_5_2_0_0_2_3t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
36+
37+
; This GEP is a ConstantExpr that uses @s
38+
; CHECK: %[[tmp_ptr:[0-9]+]] = OpAccessChain {{%[0-9]+}} %[[tmp]] %[[uint_0]] %[[uint_0]]
39+
; CHECK: %[[v_ptr:.+]] = OpAccessChain %[[_ptr_Uniform_v4float]] %[[tmp]] %[[uint_0]] %[[uint_1]]
40+
; CHECK: %[[s_ptr_gep:[0-9]+]] = OpInBoundsAccessChain %[[_ptr_Uniform_float]] %[[tmp_ptr]] %[[uint_0]] %[[uint_1]]
41+
%gep = getelementptr inbounds %MyStruct, ptr addrspace(12) @s, i32 0, i32 0, i32 1
42+
43+
; CHECK: %[[s_val:.+]] = OpLoad %[[float]] %[[s_ptr_gep]]
44+
%load_from_gep = load float, ptr addrspace(12) %gep, align 4
45+
46+
; CHECK: %[[v_val:.+]] = OpLoad %[[v4float]] %[[v_ptr]]
47+
%load_v = load <4 x float>, ptr addrspace(12) @v, align 16
48+
49+
%extract_v = extractelement <4 x float> %load_v, i64 0
50+
%add = fadd float %load_from_gep, %extract_v
51+
%get_output_ptr = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.Image_f32_5_2_0_0_2_3t(target("spirv.Image", float, 5, 2, 0, 0, 2, 3) %0, i32 0)
52+
store float %add, ptr addrspace(11) %get_output_ptr, align 4
53+
ret void
54+
}
55+
56+
!hlsl.cbs = !{!0}
57+
!0 = !{ptr @MyCBuffer.cb, ptr addrspace(12) @s, ptr addrspace(12) @v}

0 commit comments

Comments
 (0)