| //===- SPIRVLegalizeResourceBinding.cpp - Legalize resource bindings ----*- 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 legalizes the @llvm.spv.resource.handlefromimplicitbinding | |
| // intrinsic by replacing it with a call to | |
| // @llvm.spv.resource.handlefrombinding. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "SPIRV.h" | |
| #include "llvm/ADT/BitVector.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/IR/IRBuilder.h" | |
| #include "llvm/IR/InstVisitor.h" | |
| #include "llvm/IR/Intrinsics.h" | |
| #include "llvm/IR/IntrinsicsSPIRV.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/Pass.h" | |
| #include <vector> | |
| using namespace llvm; | |
| namespace { | |
| class SPIRVLegalizeResourceBindingImpl { | |
| public: | |
| bool runOnModule(Module &M); | |
| private: | |
| void collectBindingInfo(Module &M); | |
| uint32_t getAndReserveFirstUnusedBinding(uint32_t DescSet); | |
| bool replaceImplicitBindingCalls(Module &M); | |
| // A map from descriptor set to a bit vector of used binding numbers. | |
| std::vector<BitVector> UsedBindings; | |
| // Set to true by collectBindingInfo() if there are any implicit binding | |
| // declarations in the module. | |
| bool MayHaveImplicitBindings = false; | |
| }; | |
| class SPIRVLegalizeResourceBindingLegacy : public ModulePass { | |
| public: | |
| static char ID; | |
| SPIRVLegalizeResourceBindingLegacy() : ModulePass(ID) {} | |
| StringRef getPassName() const override { | |
| return "SPIRV Legalize Resource Binding"; | |
| } | |
| bool runOnModule(Module &M) override { | |
| return SPIRVLegalizeResourceBindingImpl().runOnModule(M); | |
| } | |
| }; | |
| static uint32_t getDescSet(const CallInst *CI) { | |
| uint32_t DescSetArgIdx; | |
| switch (CI->getIntrinsicID()) { | |
| case Intrinsic::spv_resource_handlefromimplicitbinding: | |
| DescSetArgIdx = 1; | |
| break; | |
| case Intrinsic::spv_resource_counterhandlefromimplicitbinding: | |
| DescSetArgIdx = 2; | |
| break; | |
| default: | |
| llvm_unreachable("CallInst is not an implicit binding intrinsic"); | |
| } | |
| return cast<ConstantInt>(CI->getArgOperand(DescSetArgIdx))->getZExtValue(); | |
| } | |
| // Collect all of the bindings used by llvm.spv.resource.handlefrombinding | |
| // and llvm.spv.resource.counterhandlefrombinding calls. Also check if there | |
| // are any implicit binding calls. | |
| void SPIRVLegalizeResourceBindingImpl::collectBindingInfo(Module &M) { | |
| auto addBinding = [&](uint32_t DescSet, uint32_t Binding) { | |
| if (UsedBindings.size() <= DescSet) { | |
| UsedBindings.resize(DescSet + 1); | |
| UsedBindings[DescSet].resize(64); | |
| } | |
| if (UsedBindings[DescSet].size() <= Binding) { | |
| UsedBindings[DescSet].resize(2 * Binding + 1); | |
| } | |
| UsedBindings[DescSet].set(Binding); | |
| }; | |
| auto collectBinding = [&](Function &F, uint32_t ArgDescSetIdx, | |
| uint32_t ArgBindingIdx) { | |
| for (User *U : F.users()) { | |
| if (CallInst *CI = dyn_cast<CallInst>(U)) { | |
| const uint32_t DescSet = | |
| cast<ConstantInt>(CI->getArgOperand(ArgDescSetIdx))->getZExtValue(); | |
| const uint32_t Binding = | |
| cast<ConstantInt>(CI->getArgOperand(ArgBindingIdx))->getZExtValue(); | |
| addBinding(DescSet, Binding); | |
| } | |
| } | |
| }; | |
| for (Function &F : M) { | |
| if (!F.isDeclaration()) | |
| continue; | |
| switch (F.getIntrinsicID()) { | |
| case Intrinsic::spv_resource_handlefrombinding: | |
| collectBinding(F, /*ArgDescSetIdx*/ 0, /*ArgBindingIdx*/ 1); | |
| break; | |
| case Intrinsic::spv_resource_counterhandlefrombinding: | |
| collectBinding(F, /*ArgDescSetIdx*/ 1, /*ArgBindingIdx*/ 2); | |
| break; | |
| case Intrinsic::spv_resource_handlefromimplicitbinding: | |
| case Intrinsic::spv_resource_counterhandlefromimplicitbinding: | |
| MayHaveImplicitBindings = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| uint32_t SPIRVLegalizeResourceBindingImpl::getAndReserveFirstUnusedBinding( | |
| uint32_t DescSet) { | |
| if (UsedBindings.size() <= DescSet) { | |
| UsedBindings.resize(DescSet + 1); | |
| UsedBindings[DescSet].resize(64); | |
| } | |
| int NewBinding = UsedBindings[DescSet].find_first_unset(); | |
| if (NewBinding == -1) { | |
| NewBinding = UsedBindings[DescSet].size(); | |
| UsedBindings[DescSet].resize(2 * NewBinding + 1); | |
| } | |
| UsedBindings[DescSet].set(NewBinding); | |
| return NewBinding; | |
| } | |
| // Replace the implicit binding call with a new call using explicit binding. | |
| static void replaceWithHandleFromBinding(Module &M, CallInst *CI, | |
| uint32_t DescSet, uint32_t Binding, | |
| Value *IndexOp, Value *RangeOp, | |
| Value *Name) { | |
| assert(CI->getIntrinsicID() == | |
| Intrinsic::spv_resource_handlefromimplicitbinding && | |
| "unexpected implicit binding intrinsic"); | |
| IRBuilder<> Builder(CI); | |
| Value *DescSetOp = Builder.getInt32(DescSet); | |
| Value *BindingOp = Builder.getInt32(Binding); | |
| Function *NewFunc = Intrinsic::getOrInsertDeclaration( | |
| &M, Intrinsic::spv_resource_handlefrombinding, {CI->getType()}); | |
| CallInst *NewCI = Builder.CreateCall( | |
| NewFunc, {DescSetOp, BindingOp, IndexOp, RangeOp, Name}); | |
| NewCI->setCallingConv(CI->getCallingConv()); | |
| CI->replaceAllUsesWith(NewCI); | |
| CI->eraseFromParent(); | |
| } | |
| // Replace the implicit counter binding call with a new call using explicit | |
| // binding. | |
| static void replaceWithCounterHandleFromBinding(Module &M, CallInst *CI, | |
| uint32_t DescSet, | |
| uint32_t Binding) { | |
| assert(CI->getIntrinsicID() == | |
| Intrinsic::spv_resource_counterhandlefromimplicitbinding && | |
| "unexpected implicit binding intrinsic"); | |
| IRBuilder<> Builder(CI); | |
| Value *DescSetOp = Builder.getInt32(DescSet); | |
| Value *BindingOp = Builder.getInt32(Binding); | |
| Value *MainHandle = CI->getArgOperand(0); | |
| Type *OverloadTys[] = {CI->getType(), MainHandle->getType()}; | |
| Function *NewFunc = Intrinsic::getOrInsertDeclaration( | |
| &M, Intrinsic::spv_resource_counterhandlefrombinding, OverloadTys); | |
| CallInst *NewCI = | |
| Builder.CreateCall(NewFunc, {MainHandle, DescSetOp, BindingOp}); | |
| NewCI->setCallingConv(CI->getCallingConv()); | |
| CI->replaceAllUsesWith(NewCI); | |
| CI->eraseFromParent(); | |
| } | |
| bool SPIRVLegalizeResourceBindingImpl::replaceImplicitBindingCalls(Module &M) { | |
| // Collect all implicit binding calls. | |
| SmallVector<std::pair<uint32_t, CallInst *>> IBCalls; | |
| bool Changed = false; | |
| for (Function &F : M) { | |
| if (!F.isDeclaration()) | |
| continue; | |
| uint32_t OrderIdIdx; | |
| if (F.getIntrinsicID() == Intrinsic::spv_resource_handlefromimplicitbinding) | |
| OrderIdIdx = 0; | |
| else if (F.getIntrinsicID() == | |
| Intrinsic::spv_resource_counterhandlefromimplicitbinding) | |
| OrderIdIdx = 1; | |
| else | |
| continue; | |
| for (User *U : F.users()) { | |
| if (CallInst *CI = dyn_cast<CallInst>(U)) { | |
| ConstantInt *OrderId = cast<ConstantInt>(CI->getArgOperand(OrderIdIdx)); | |
| IBCalls.emplace_back(OrderId->getZExtValue(), CI); | |
| } | |
| } | |
| } | |
| if (IBCalls.empty()) | |
| return false; | |
| // Sort the collected calls by their order ID. | |
| llvm::sort(IBCalls, llvm::less_first()); | |
| // Assign bindings based on the order ID. Same order ID gets the same binding. | |
| // Also make sure that calls with the same order ID have the same descriptor | |
| // set. | |
| uint32_t LastOrderId = -1; | |
| uint32_t LastBinding = -1; | |
| uint32_t LastDescSet = -1; | |
| for (auto &[OrderId, CI] : IBCalls) { | |
| uint32_t Binding; | |
| uint32_t DescSet = getDescSet(CI); | |
| if (OrderId == LastOrderId) { | |
| if (DescSet != LastDescSet) | |
| report_fatal_error("Implicit binding calls with the same order ID must " | |
| "have the same descriptor set"); | |
| Binding = LastBinding; | |
| } else { | |
| Binding = getAndReserveFirstUnusedBinding(DescSet); | |
| } | |
| // Replace the implicit binding call with an explicit binding call. | |
| if (CI->getIntrinsicID() == | |
| Intrinsic::spv_resource_handlefromimplicitbinding) | |
| replaceWithHandleFromBinding(M, CI, DescSet, Binding, | |
| CI->getArgOperand(2), CI->getArgOperand(3), | |
| CI->getArgOperand(4)); | |
| else | |
| replaceWithCounterHandleFromBinding(M, CI, DescSet, Binding); | |
| Changed = true; | |
| LastOrderId = OrderId; | |
| LastBinding = Binding; | |
| LastDescSet = DescSet; | |
| } | |
| return Changed; | |
| } | |
| bool SPIRVLegalizeResourceBindingImpl::runOnModule(Module &M) { | |
| collectBindingInfo(M); | |
| bool Changed = false; | |
| if (MayHaveImplicitBindings) | |
| Changed |= replaceImplicitBindingCalls(M); | |
| return Changed; | |
| } | |
| } // namespace | |
| PreservedAnalyses | |
| SPIRVLegalizeResourceBindingPass::run(Module &M, ModuleAnalysisManager &AM) { | |
| return SPIRVLegalizeResourceBindingImpl().runOnModule(M) | |
| ? PreservedAnalyses::none() | |
| : PreservedAnalyses::all(); | |
| } | |
| char SPIRVLegalizeResourceBindingLegacy::ID = 0; | |
| INITIALIZE_PASS(SPIRVLegalizeResourceBindingLegacy, | |
| "legalize-spirv-resource-binding", | |
| "Legalize SPIR-V resource bindings", false, false) | |
| ModulePass *llvm::createSPIRVLegalizeResourceBindingPass() { | |
| return new SPIRVLegalizeResourceBindingLegacy(); | |
| } |