[llvm] [SPIRV][HLSL] Implement CBuffer access lowering pass (PR #159136)
Steven Perron via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 18 11:00:30 PDT 2025
================
@@ -0,0 +1,172 @@
+//===- SPIRVCBufferAccess.cpp - Translate CBuffer Loads
+//--------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This pass replaces all accesses to constant buffer global variables with
+// accesses to the proper SPIR-V resource. It's designed to run after the
+// DXIL preparation passes and before the main SPIR-V legalization passes.
+//
+// The pass operates as follows:
+// 1. It finds all constant buffers by looking for the `!hlsl.cbs` metadata.
+// 2. For each cbuffer, it finds the global variable holding the resource handle
+// and the global variables for each of the cbuffer's members.
+// 3. For each member variable, it creates a call to the
+// `llvm.spv.resource.getpointer` intrinsic. This intrinsic takes the
+// resource handle and the member's index within the cbuffer as arguments.
+// The result is a pointer to that member within the SPIR-V resource.
+// 4. It then replaces all uses of the original member global variable with the
+// pointer returned by the `getpointer` intrinsic. This effectively retargets
+// all loads and GEPs to the new resource pointer.
+// 5. Finally, it cleans up by deleting the original global variables and the
+// `!hlsl.cbs` metadata.
+//
+// This approach allows subsequent passes, like SPIRVEmitIntrinsics, to
+// correctly handle GEPs that operate on the result of the `getpointer` call,
+// folding them into a single OpAccessChain instruction.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SPIRVCBufferAccess.h"
+#include "SPIRV.h"
+#include "llvm/Frontend/HLSL/CBuffer.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/IntrinsicsSPIRV.h"
+#include "llvm/IR/Module.h"
+
+#define DEBUG_TYPE "spirv-cbuffer-access"
+using namespace llvm;
+
+// Finds the single instruction that defines the resource handle. This is
+// typically a call to `llvm.spv.resource.handlefrombinding`.
+static Instruction *findHandleDef(GlobalVariable *HandleVar) {
+ for (User *U : HandleVar->users()) {
+ if (auto *SI = dyn_cast<StoreInst>(U)) {
+ if (auto *I = dyn_cast<Instruction>(SI->getValueOperand())) {
+ return I;
+ }
+ }
+ }
+ return nullptr;
+}
+
+static bool replaceCBufferAccesses(Module &M) {
+ std::optional<hlsl::CBufferMetadata> CBufMD = hlsl::CBufferMetadata::get(M);
+ if (!CBufMD)
+ return false;
+
+ for (const hlsl::CBufferMapping &Mapping : *CBufMD) {
+ Instruction *HandleDef = findHandleDef(Mapping.Handle);
+ if (!HandleDef) {
+ // If there's no handle definition, it might be because the cbuffer is
+ // unused. In this case, we can just clean up the globals.
+ if (Mapping.Handle->use_empty()) {
+ for (const auto &Member : Mapping.Members) {
+ if (Member.GV->use_empty()) {
+ Member.GV->eraseFromParent();
+ }
+ }
+ Mapping.Handle->eraseFromParent();
+ }
+ continue;
+ }
+
+ // The handle definition should dominate all uses of the cbuffer members.
+ // We'll insert our getpointer calls right after it.
+ IRBuilder<> Builder(HandleDef->getNextNode());
+
+ for (uint32_t Index = 0; Index < Mapping.Members.size(); ++Index) {
+ GlobalVariable *MemberGV = Mapping.Members[Index].GV;
+ if (MemberGV->use_empty()) {
+ continue;
+ }
+
+ // Create the getpointer intrinsic call.
+ Value *IndexVal = Builder.getInt32(Index);
+ Type *PtrType = MemberGV->getType();
+ Value *GetPointerCall = Builder.CreateIntrinsic(
+ PtrType, Intrinsic::spv_resource_getpointer, {HandleDef, IndexVal});
+
+ // We cannot use replaceAllUsesWith here because some uses may be
+ // ConstantExprs, which cannot be replaced with non-constants.
+ SmallVector<User *, 4> Users(MemberGV->users());
+ for (User *U : Users) {
+ if (auto *CE = dyn_cast<ConstantExpr>(U)) {
+ SmallVector<Instruction *, 4> Insts;
+ std::function<void(ConstantExpr *)> findInstructions =
+ [&](ConstantExpr *Const) {
+ for (User *ConstU : Const->users()) {
+ if (auto *ConstCE = dyn_cast<ConstantExpr>(ConstU)) {
+ findInstructions(ConstCE);
+ } else if (auto *I = dyn_cast<Instruction>(ConstU)) {
+ Insts.push_back(I);
+ }
+ }
+ };
+ findInstructions(CE);
+
+ for (Instruction *I : Insts) {
+ Instruction *NewInst = CE->getAsInstruction();
+ NewInst->insertBefore(I->getIterator());
+ I->replaceUsesOfWith(CE, NewInst);
----------------
s-perron wrote:
I'll see if I can add a test that would have multiple constant expr, and see what happens.
https://github.com/llvm/llvm-project/pull/159136
More information about the llvm-commits
mailing list