| //===- SPIRVLegalizeImplicitBinding.cpp - Legalize implicit 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 "SPIRVLegalizeImplicitBinding.h" | |
| #include "SPIRV.h" | |
| #include "llvm/ADT/BitVector.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 <algorithm> | |
| #include <vector> | |
| using namespace llvm; | |
| namespace { | |
| class SPIRVLegalizeImplicitBindingImpl { | |
| public: | |
| bool runOnModule(Module &M); | |
| private: | |
| void collectBindingInfo(Module &M); | |
| uint32_t getAndReserveFirstUnusedBinding(uint32_t DescSet); | |
| void replaceImplicitBindingCalls(Module &M); | |
| void replaceResourceHandleCall(Module &M, CallInst *OldCI, | |
| uint32_t NewBinding); | |
| void replaceCounterHandleCall(Module &M, CallInst *OldCI, | |
| uint32_t NewBinding); | |
| void verifyUniqueOrderIdPerResource(SmallVectorImpl<CallInst *> &Calls); | |
| // A map from descriptor set to a bit vector of used binding numbers. | |
| std::vector<BitVector> UsedBindings; | |
| // A list of all implicit binding calls, to be sorted by order ID. | |
| SmallVector<CallInst *, 16> ImplicitBindingCalls; | |
| }; | |
| class SPIRVLegalizeImplicitBindingLegacy : public ModulePass { | |
| public: | |
| static char ID; | |
| SPIRVLegalizeImplicitBindingLegacy() : ModulePass(ID) {} | |
| StringRef getPassName() const override { | |
| return "SPIRV Legalize Implicit Binding"; | |
| } | |
| bool runOnModule(Module &M) override { | |
| return SPIRVLegalizeImplicitBindingImpl().runOnModule(M); | |
| } | |
| }; | |
| struct BindingInfoCollector : public InstVisitor<BindingInfoCollector> { | |
| std::vector<BitVector> &UsedBindings; | |
| SmallVector<CallInst *, 16> &ImplicitBindingCalls; | |
| BindingInfoCollector(std::vector<BitVector> &UsedBindings, | |
| SmallVector<CallInst *, 16> &ImplicitBindingCalls) | |
| : UsedBindings(UsedBindings), ImplicitBindingCalls(ImplicitBindingCalls) { | |
| } | |
| void 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); | |
| } | |
| void visitCallInst(CallInst &CI) { | |
| if (CI.getIntrinsicID() == Intrinsic::spv_resource_handlefrombinding) { | |
| const uint32_t DescSet = | |
| cast<ConstantInt>(CI.getArgOperand(0))->getZExtValue(); | |
| const uint32_t Binding = | |
| cast<ConstantInt>(CI.getArgOperand(1))->getZExtValue(); | |
| addBinding(DescSet, Binding); | |
| } else if (CI.getIntrinsicID() == | |
| Intrinsic::spv_resource_handlefromimplicitbinding) { | |
| ImplicitBindingCalls.push_back(&CI); | |
| } else if (CI.getIntrinsicID() == | |
| Intrinsic::spv_resource_counterhandlefrombinding) { | |
| const uint32_t DescSet = | |
| cast<ConstantInt>(CI.getArgOperand(2))->getZExtValue(); | |
| const uint32_t Binding = | |
| cast<ConstantInt>(CI.getArgOperand(1))->getZExtValue(); | |
| addBinding(DescSet, Binding); | |
| } else if (CI.getIntrinsicID() == | |
| Intrinsic::spv_resource_counterhandlefromimplicitbinding) { | |
| ImplicitBindingCalls.push_back(&CI); | |
| } | |
| } | |
| }; | |
| static uint32_t getOrderId(const CallInst *CI) { | |
| uint32_t OrderIdArgIdx = 0; | |
| switch (CI->getIntrinsicID()) { | |
| case Intrinsic::spv_resource_handlefromimplicitbinding: | |
| OrderIdArgIdx = 0; | |
| break; | |
| case Intrinsic::spv_resource_counterhandlefromimplicitbinding: | |
| OrderIdArgIdx = 1; | |
| break; | |
| default: | |
| llvm_unreachable("CallInst is not an implicit binding intrinsic"); | |
| } | |
| return cast<ConstantInt>(CI->getArgOperand(OrderIdArgIdx))->getZExtValue(); | |
| } | |
| static uint32_t getDescSet(const CallInst *CI) { | |
| uint32_t DescSetArgIdx; | |
| switch (CI->getIntrinsicID()) { | |
| case Intrinsic::spv_resource_handlefromimplicitbinding: | |
| case Intrinsic::spv_resource_handlefrombinding: | |
| DescSetArgIdx = 1; | |
| break; | |
| case Intrinsic::spv_resource_counterhandlefromimplicitbinding: | |
| case Intrinsic::spv_resource_counterhandlefrombinding: | |
| DescSetArgIdx = 2; | |
| break; | |
| default: | |
| llvm_unreachable("CallInst is not an implicit binding intrinsic"); | |
| } | |
| return cast<ConstantInt>(CI->getArgOperand(DescSetArgIdx))->getZExtValue(); | |
| } | |
| void SPIRVLegalizeImplicitBindingImpl::collectBindingInfo(Module &M) { | |
| BindingInfoCollector InfoCollector(UsedBindings, ImplicitBindingCalls); | |
| InfoCollector.visit(M); | |
| // Sort the collected calls by their order ID. | |
| std::sort(ImplicitBindingCalls.begin(), ImplicitBindingCalls.end(), | |
| [](const CallInst *A, const CallInst *B) { | |
| return getOrderId(A) < getOrderId(B); | |
| }); | |
| } | |
| void SPIRVLegalizeImplicitBindingImpl::verifyUniqueOrderIdPerResource( | |
| SmallVectorImpl<CallInst *> &Calls) { | |
| // Check that the order Id is unique per resource. | |
| for (uint32_t i = 1; i < Calls.size(); ++i) { | |
| const uint32_t OrderA = getOrderId(Calls[i - 1]); | |
| const uint32_t OrderB = getOrderId(Calls[i]); | |
| if (OrderA == OrderB) { | |
| const uint32_t DescSetA = getDescSet(Calls[i - 1]); | |
| const uint32_t DescSetB = getDescSet(Calls[i]); | |
| if (DescSetA != DescSetB) { | |
| report_fatal_error("Implicit binding calls with the same order ID must " | |
| "have the same descriptor set"); | |
| } | |
| } | |
| } | |
| } | |
| uint32_t SPIRVLegalizeImplicitBindingImpl::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; | |
| } | |
| void SPIRVLegalizeImplicitBindingImpl::replaceImplicitBindingCalls(Module &M) { | |
| uint32_t lastOrderId = -1; | |
| uint32_t lastBindingNumber = -1; | |
| for (CallInst *OldCI : ImplicitBindingCalls) { | |
| const uint32_t OrderId = getOrderId(OldCI); | |
| uint32_t BindingNumber; | |
| if (OrderId == lastOrderId) { | |
| BindingNumber = lastBindingNumber; | |
| } else { | |
| const uint32_t DescSet = getDescSet(OldCI); | |
| BindingNumber = getAndReserveFirstUnusedBinding(DescSet); | |
| } | |
| if (OldCI->getIntrinsicID() == | |
| Intrinsic::spv_resource_handlefromimplicitbinding) { | |
| replaceResourceHandleCall(M, OldCI, BindingNumber); | |
| } else { | |
| assert(OldCI->getIntrinsicID() == | |
| Intrinsic::spv_resource_counterhandlefromimplicitbinding && | |
| "Unexpected implicit binding intrinsic"); | |
| replaceCounterHandleCall(M, OldCI, BindingNumber); | |
| } | |
| lastOrderId = OrderId; | |
| lastBindingNumber = BindingNumber; | |
| } | |
| } | |
| bool SPIRVLegalizeImplicitBindingImpl::runOnModule(Module &M) { | |
| collectBindingInfo(M); | |
| if (ImplicitBindingCalls.empty()) { | |
| return false; | |
| } | |
| verifyUniqueOrderIdPerResource(ImplicitBindingCalls); | |
| replaceImplicitBindingCalls(M); | |
| return true; | |
| } | |
| } // namespace | |
| PreservedAnalyses SPIRVLegalizeImplicitBinding::run(Module &M, | |
| ModuleAnalysisManager &AM) { | |
| return SPIRVLegalizeImplicitBindingImpl().runOnModule(M) | |
| ? PreservedAnalyses::none() | |
| : PreservedAnalyses::all(); | |
| } | |
| char SPIRVLegalizeImplicitBindingLegacy::ID = 0; | |
| INITIALIZE_PASS(SPIRVLegalizeImplicitBindingLegacy, | |
| "legalize-spirv-implicit-binding", | |
| "Legalize SPIR-V implicit bindings", false, false) | |
| ModulePass *llvm::createSPIRVLegalizeImplicitBindingPass() { | |
| return new SPIRVLegalizeImplicitBindingLegacy(); | |
| } | |
| void SPIRVLegalizeImplicitBindingImpl::replaceResourceHandleCall( | |
| Module &M, CallInst *OldCI, uint32_t NewBinding) { | |
| IRBuilder<> Builder(OldCI); | |
| const uint32_t DescSet = | |
| cast<ConstantInt>(OldCI->getArgOperand(1))->getZExtValue(); | |
| SmallVector<Value *, 8> Args; | |
| Args.push_back(Builder.getInt32(DescSet)); | |
| Args.push_back(Builder.getInt32(NewBinding)); | |
| // Copy the remaining arguments from the old call. | |
| for (uint32_t i = 2; i < OldCI->arg_size(); ++i) { | |
| Args.push_back(OldCI->getArgOperand(i)); | |
| } | |
| Function *NewFunc = Intrinsic::getOrInsertDeclaration( | |
| &M, Intrinsic::spv_resource_handlefrombinding, OldCI->getType()); | |
| CallInst *NewCI = Builder.CreateCall(NewFunc, Args); | |
| NewCI->setCallingConv(OldCI->getCallingConv()); | |
| OldCI->replaceAllUsesWith(NewCI); | |
| OldCI->eraseFromParent(); | |
| } | |
| void SPIRVLegalizeImplicitBindingImpl::replaceCounterHandleCall( | |
| Module &M, CallInst *OldCI, uint32_t NewBinding) { | |
| IRBuilder<> Builder(OldCI); | |
| const uint32_t DescSet = | |
| cast<ConstantInt>(OldCI->getArgOperand(2))->getZExtValue(); | |
| SmallVector<Value *, 8> Args; | |
| Args.push_back(OldCI->getArgOperand(0)); | |
| Args.push_back(Builder.getInt32(NewBinding)); | |
| Args.push_back(Builder.getInt32(DescSet)); | |
| Type *Tys[] = {OldCI->getType(), OldCI->getArgOperand(0)->getType()}; | |
| Function *NewFunc = Intrinsic::getOrInsertDeclaration( | |
| &M, Intrinsic::spv_resource_counterhandlefrombinding, Tys); | |
| CallInst *NewCI = Builder.CreateCall(NewFunc, Args); | |
| NewCI->setCallingConv(OldCI->getCallingConv()); | |
| OldCI->replaceAllUsesWith(NewCI); | |
| OldCI->eraseFromParent(); | |
| } |