152 lines · cpp
1//===- SPIRVCBufferAccess.cpp - Translate CBuffer Loads ---------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This pass replaces all accesses to constant buffer global variables with10// accesses to the proper SPIR-V resource.11//12// The pass operates as follows:13// 1. It finds all constant buffers by looking for the `!hlsl.cbs` metadata.14// 2. For each cbuffer, it finds the global variable holding the resource handle15// and the global variables for each of the cbuffer's members.16// 3. For each member variable, it creates a call to the17// `llvm.spv.resource.getpointer` intrinsic. This intrinsic takes the18// resource handle and the member's index within the cbuffer as arguments.19// The result is a pointer to that member within the SPIR-V resource.20// 4. It then replaces all uses of the original member global variable with the21// pointer returned by the `getpointer` intrinsic. This effectively retargets22// all loads and GEPs to the new resource pointer.23// 5. Finally, it cleans up by deleting the original global variables and the24// `!hlsl.cbs` metadata.25//26// This approach allows subsequent passes, like SPIRVEmitIntrinsics, to27// correctly handle GEPs that operate on the result of the `getpointer` call,28// folding them into a single OpAccessChain instruction.29//30//===----------------------------------------------------------------------===//31 32#include "SPIRVCBufferAccess.h"33#include "SPIRV.h"34#include "llvm/Frontend/HLSL/CBuffer.h"35#include "llvm/IR/IRBuilder.h"36#include "llvm/IR/IntrinsicsSPIRV.h"37#include "llvm/IR/Module.h"38#include "llvm/IR/ReplaceConstant.h"39 40#define DEBUG_TYPE "spirv-cbuffer-access"41using namespace llvm;42 43// Finds the single instruction that defines the resource handle. This is44// typically a call to `llvm.spv.resource.handlefrombinding`.45static Instruction *findHandleDef(GlobalVariable *HandleVar) {46 for (User *U : HandleVar->users()) {47 if (auto *SI = dyn_cast<StoreInst>(U)) {48 if (auto *I = dyn_cast<Instruction>(SI->getValueOperand())) {49 return I;50 }51 }52 }53 return nullptr;54}55 56static bool replaceCBufferAccesses(Module &M) {57 std::optional<hlsl::CBufferMetadata> CBufMD =58 hlsl::CBufferMetadata::get(M, [](Type *Ty) {59 if (auto *TET = dyn_cast<TargetExtType>(Ty))60 return TET->getName() == "spirv.Padding";61 return false;62 });63 if (!CBufMD)64 return false;65 66 SmallVector<Constant *> CBufferGlobals;67 for (const hlsl::CBufferMapping &Mapping : *CBufMD)68 for (const hlsl::CBufferMember &Member : Mapping.Members)69 CBufferGlobals.push_back(Member.GV);70 convertUsersOfConstantsToInstructions(CBufferGlobals);71 72 for (const hlsl::CBufferMapping &Mapping : *CBufMD) {73 Instruction *HandleDef = findHandleDef(Mapping.Handle);74 if (!HandleDef) {75 report_fatal_error("Could not find handle definition for cbuffer: " +76 Mapping.Handle->getName());77 }78 79 // The handle definition should dominate all uses of the cbuffer members.80 // We'll insert our getpointer calls right after it.81 IRBuilder<> Builder(HandleDef->getNextNode());82 auto *HandleTy = cast<TargetExtType>(Mapping.Handle->getValueType());83 auto *LayoutTy = cast<StructType>(HandleTy->getTypeParameter(0));84 const StructLayout *SL = M.getDataLayout().getStructLayout(LayoutTy);85 86 for (const hlsl::CBufferMember &Member : Mapping.Members) {87 GlobalVariable *MemberGV = Member.GV;88 if (MemberGV->use_empty()) {89 continue;90 }91 92 uint32_t IndexInStruct = SL->getElementContainingOffset(Member.Offset);93 94 // Create the getpointer intrinsic call.95 Value *IndexVal = Builder.getInt32(IndexInStruct);96 Type *PtrType = MemberGV->getType();97 Value *GetPointerCall = Builder.CreateIntrinsic(98 PtrType, Intrinsic::spv_resource_getpointer, {HandleDef, IndexVal});99 100 MemberGV->replaceAllUsesWith(GetPointerCall);101 }102 }103 104 // Now that all uses are replaced, clean up the globals and metadata.105 for (const hlsl::CBufferMapping &Mapping : *CBufMD) {106 for (const auto &Member : Mapping.Members) {107 Member.GV->eraseFromParent();108 }109 // Erase the stores to the handle variable before erasing the handle itself.110 SmallVector<Instruction *, 4> HandleStores;111 for (User *U : Mapping.Handle->users()) {112 if (auto *SI = dyn_cast<StoreInst>(U)) {113 HandleStores.push_back(SI);114 }115 }116 for (Instruction *I : HandleStores) {117 I->eraseFromParent();118 }119 Mapping.Handle->eraseFromParent();120 }121 122 CBufMD->eraseFromModule();123 return true;124}125 126PreservedAnalyses SPIRVCBufferAccess::run(Module &M,127 ModuleAnalysisManager &AM) {128 if (replaceCBufferAccesses(M)) {129 return PreservedAnalyses::none();130 }131 return PreservedAnalyses::all();132}133 134namespace {135class SPIRVCBufferAccessLegacy : public ModulePass {136public:137 bool runOnModule(Module &M) override { return replaceCBufferAccesses(M); }138 StringRef getPassName() const override { return "SPIRV CBuffer Access"; }139 SPIRVCBufferAccessLegacy() : ModulePass(ID) {}140 141 static char ID; // Pass identification.142};143char SPIRVCBufferAccessLegacy::ID = 0;144} // end anonymous namespace145 146INITIALIZE_PASS(SPIRVCBufferAccessLegacy, DEBUG_TYPE, "SPIRV CBuffer Access",147 false, false)148 149ModulePass *llvm::createSPIRVCBufferAccessLegacyPass() {150 return new SPIRVCBufferAccessLegacy();151}152