llvm-project/llvm/lib/Target/SPIRV/SPIRVCBufferAccess.cpp
Steven Perron 6e983e3876
[SPIRV] Support Peeled Array Layouts for HLSL CBuffers (#169078)
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
2025-11-26 10:10:53 -05:00

152 lines
5.6 KiB
C++

//===- SPIRVCBufferAccess.cpp - Translate CBuffer Loads ---------*- C++ -*-===//
//
// 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.
//
// 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"
#include "llvm/IR/ReplaceConstant.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, [](Type *Ty) {
if (auto *TET = dyn_cast<TargetExtType>(Ty))
return TET->getName() == "spirv.Padding";
return false;
});
if (!CBufMD)
return false;
SmallVector<Constant *> CBufferGlobals;
for (const hlsl::CBufferMapping &Mapping : *CBufMD)
for (const hlsl::CBufferMember &Member : Mapping.Members)
CBufferGlobals.push_back(Member.GV);
convertUsersOfConstantsToInstructions(CBufferGlobals);
for (const hlsl::CBufferMapping &Mapping : *CBufMD) {
Instruction *HandleDef = findHandleDef(Mapping.Handle);
if (!HandleDef) {
report_fatal_error("Could not find handle definition for cbuffer: " +
Mapping.Handle->getName());
}
// 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 (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(IndexInStruct);
Type *PtrType = MemberGV->getType();
Value *GetPointerCall = Builder.CreateIntrinsic(
PtrType, Intrinsic::spv_resource_getpointer, {HandleDef, IndexVal});
MemberGV->replaceAllUsesWith(GetPointerCall);
}
}
// Now that all uses are replaced, clean up the globals and metadata.
for (const hlsl::CBufferMapping &Mapping : *CBufMD) {
for (const auto &Member : Mapping.Members) {
Member.GV->eraseFromParent();
}
// Erase the stores to the handle variable before erasing the handle itself.
SmallVector<Instruction *, 4> HandleStores;
for (User *U : Mapping.Handle->users()) {
if (auto *SI = dyn_cast<StoreInst>(U)) {
HandleStores.push_back(SI);
}
}
for (Instruction *I : HandleStores) {
I->eraseFromParent();
}
Mapping.Handle->eraseFromParent();
}
CBufMD->eraseFromModule();
return true;
}
PreservedAnalyses SPIRVCBufferAccess::run(Module &M,
ModuleAnalysisManager &AM) {
if (replaceCBufferAccesses(M)) {
return PreservedAnalyses::none();
}
return PreservedAnalyses::all();
}
namespace {
class SPIRVCBufferAccessLegacy : public ModulePass {
public:
bool runOnModule(Module &M) override { return replaceCBufferAccesses(M); }
StringRef getPassName() const override { return "SPIRV CBuffer Access"; }
SPIRVCBufferAccessLegacy() : ModulePass(ID) {}
static char ID; // Pass identification.
};
char SPIRVCBufferAccessLegacy::ID = 0;
} // end anonymous namespace
INITIALIZE_PASS(SPIRVCBufferAccessLegacy, DEBUG_TYPE, "SPIRV CBuffer Access",
false, false)
ModulePass *llvm::createSPIRVCBufferAccessLegacyPass() {
return new SPIRVCBufferAccessLegacy();
}