| //===- 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 "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" | |
| #include "llvm/Transforms/Utils/ModuleUtils.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; | |
| SmallPtrSet<GlobalVariable *, 8> CBufferHandles; | |
| SmallVector<Constant *> CBufferGlobals; | |
| for (const hlsl::CBufferMapping &Mapping : *CBufMD) { | |
| CBufferHandles.insert(Mapping.Handle); | |
| 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); | |
| } | |
| } | |
| // Remove cbuffer handle globals from @llvm.compiler.used list. | |
| llvm::removeFromUsedLists(M, [&](Constant *C) -> bool { | |
| auto *GV = dyn_cast<GlobalVariable>(C); | |
| return GV && CBufferHandles.contains(GV); | |
| }); | |
| for (GlobalVariable *HandleGV : CBufferHandles) | |
| HandleGV->removeDeadConstantUsers(); | |
| // 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 SPIRVCBufferAccessPass::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(); | |
| } |