/////////////////////////////////////////////////////////////////////////////// // // // DxilCondenseResources.cpp // // Copyright (C) Microsoft Corporation. All rights reserved. // // This file is distributed under the University of Illinois Open Source // // License. See LICENSE.TXT for details. // // // // Provides a pass to make resource IDs zero-based and dense. // // // /////////////////////////////////////////////////////////////////////////////// #include "dxc/HLSL/DxilGenerationPass.h" #include "dxc/HLSL/DxilOperations.h" #include "dxc/HLSL/DxilSignatureElement.h" #include "dxc/HLSL/DxilModule.h" #include "dxc/Support/Global.h" #include "dxc/HLSL/DxilTypeSystem.h" #include "dxc/HLSL/DxilInstructions.h" #include "dxc/HLSL/DxilSpanAllocator.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Module.h" #include "llvm/IR/PassManager.h" #include "llvm/ADT/BitVector.h" #include "llvm/Pass.h" #include "llvm/Transforms/Utils/Local.h" #include #include using namespace llvm; using namespace hlsl; struct ResourceID { DXIL::ResourceClass Class; // Resource class. unsigned ID; // Resource ID, as specified on entry. bool operator<(const ResourceID& other) const { if (Class < other.Class) return true; if (Class > other.Class) return false; if (ID < other.ID) return true; return false; } }; struct RemapEntry { ResourceID ResID; // Resource identity, as specified on entry. DxilResourceBase *Resource; // In-memory resource representation. unsigned Index; // Index in resource vector - new ID for the resource. }; typedef std::map RemapEntryCollection; class DxilCondenseResources : public ModulePass { private: RemapEntryCollection m_rewrites; public: static char ID; // Pass identification, replacement for typeid explicit DxilCondenseResources() : ModulePass(ID) {} const char *getPassName() const override { return "DXIL Condense Resources"; } bool runOnModule(Module &M) override { DxilModule &DM = M.GetOrCreateDxilModule(); // Switch tbuffers to SRVs, as they have been treated as cbuffers up to this point. if (DM.GetCBuffers().size()) PatchTBuffers(DM); // Remove unused resource. DM.RemoveUnusedResources(); // Make sure all resource types are dense; build a map of rewrites. if (BuildRewriteMap(DM)) { // Rewrite all instructions that refer to resources in the map. ApplyRewriteMap(DM); } bool hasResource = DM.GetCBuffers().size() || DM.GetUAVs().size() || DM.GetSRVs().size() || DM.GetSamplers().size(); if (hasResource) { if (!DM.GetShaderModel()->IsLib()) { AllocateDxilResources(DM); PatchCreateHandle(DM); } else { PatchCreateHandleForLib(DM); } } return true; } // Build m_rewrites, returns 'true' if any rewrites are needed. bool BuildRewriteMap(DxilModule &DM); DxilResourceBase &GetFirstRewrite() const { DXASSERT_NOMSG(!m_rewrites.empty()); return *m_rewrites.begin()->second.Resource; } private: void ApplyRewriteMap(DxilModule &DM); void AllocateDxilResources(DxilModule &DM); // Add lowbound to create handle range index. void PatchCreateHandle(DxilModule &DM); // Add lowbound to create handle range index for library. void PatchCreateHandleForLib(DxilModule &DM); // Switch CBuffer for SRV for TBuffers. void PatchTBuffers(DxilModule &DM); }; void DxilCondenseResources::ApplyRewriteMap(DxilModule &DM) { for (Function &F : DM.GetModule()->functions()) { if (F.isDeclaration()) { continue; } for (inst_iterator iter = inst_begin(F), E = inst_end(F); iter != E; ++iter) { llvm::Instruction &I = *iter; DxilInst_CreateHandle CH(&I); if (!CH) continue; ResourceID RId; RId.Class = (DXIL::ResourceClass)CH.get_resourceClass_val(); RId.ID = (unsigned)llvm::dyn_cast(CH.get_rangeId()) ->getZExtValue(); RemapEntryCollection::iterator it = m_rewrites.find(RId); if (it == m_rewrites.end()) { continue; } CallInst *CI = cast(&I); Value *newRangeID = DM.GetOP()->GetU32Const(it->second.Index); CI->setArgOperand(DXIL::OperandIndex::kCreateHandleResIDOpIdx, newRangeID); } } for (auto &entry : m_rewrites) { entry.second.Resource->SetID(entry.second.Index); } } template static void BuildRewrites(const std::vector> &Rs, RemapEntryCollection &C) { const unsigned s = (unsigned)Rs.size(); for (unsigned i = 0; i < s; ++i) { const std::unique_ptr &R = Rs[i]; if (R->GetID() != i) { ResourceID RId = {R->GetClass(), R->GetID()}; RemapEntry RE = {RId, R.get(), i}; C[RId] = RE; } } } bool DxilCondenseResources::BuildRewriteMap(DxilModule &DM) { BuildRewrites(DM.GetCBuffers(), m_rewrites); BuildRewrites(DM.GetSRVs(), m_rewrites); BuildRewrites(DM.GetUAVs(), m_rewrites); BuildRewrites(DM.GetSamplers(), m_rewrites); return !m_rewrites.empty(); } namespace { template static void AllocateDxilResource(const std::vector > &resourceList, LLVMContext &Ctx) { SpacesAllocator SAlloc; for (auto &res : resourceList) { const unsigned space = res->GetSpaceID(); typename SpacesAllocator::Allocator &alloc = SAlloc.Get(space); if (res->IsAllocated()) { const unsigned reg = res->GetLowerBound(); const T *conflict = nullptr; if (res->IsUnbounded()) { const T *unbounded = alloc.GetUnbounded(); if (unbounded) { Ctx.emitError( Twine("more than one unbounded resource (") + unbounded->GetGlobalName() + (" and ") + res->GetGlobalName() + (") in space ") + Twine(space)); } else { conflict = alloc.Insert(res.get(), reg, res->GetUpperBound()); if (!conflict) alloc.SetUnbounded(res.get()); } } else { conflict = alloc.Insert(res.get(), reg, res->GetUpperBound()); } if (conflict) { Ctx.emitError( ((res->IsUnbounded()) ? Twine("unbounded ") : Twine("")) + Twine("resource ") + res->GetGlobalName() + Twine(" at register ") + Twine(reg) + Twine(" overlaps with resource ") + conflict->GetGlobalName() + Twine(" at register ") + Twine(conflict->GetLowerBound()) + Twine(", space ") + Twine(space)); } } } // Allocate. const unsigned space = 0; typename SpacesAllocator::Allocator &alloc0 = SAlloc.Get(space); for (auto &res : resourceList) { if (!res->IsAllocated()) { DXASSERT(res->GetSpaceID() == 0, "otherwise non-zero space has no user register assignment"); unsigned reg = 0; bool success = false; if (res->IsUnbounded()) { const T *unbounded = alloc0.GetUnbounded(); if (unbounded) { Ctx.emitError( Twine("more than one unbounded resource (") + unbounded->GetGlobalName() + Twine(" and ") + res->GetGlobalName() + Twine(") in space ") + Twine(space)); } else { success = alloc0.AllocateUnbounded(res.get(), reg); if (success) alloc0.SetUnbounded(res.get()); } } else { success = alloc0.Allocate(res.get(), res->GetRangeSize(), reg); } if (success) { res->SetLowerBound(reg); } else { Ctx.emitError( ((res->IsUnbounded()) ? Twine("unbounded ") : Twine("")) + Twine("resource ") + res->GetGlobalName() + Twine(" could not be allocated")); } } } } void PatchLowerBoundOfCreateHandle(CallInst *handle, DxilModule &DM) { DxilInst_CreateHandle createHandle(handle); DXASSERT_NOMSG(createHandle); DXIL::ResourceClass ResClass = static_cast(createHandle.get_resourceClass_val()); // Dynamic rangeId is not supported - skip and let validation report the // error. if (!isa(createHandle.get_rangeId())) return; unsigned rangeId = cast(createHandle.get_rangeId())->getLimitedValue(); DxilResourceBase *res = nullptr; switch (ResClass) { case DXIL::ResourceClass::SRV: res = &DM.GetSRV(rangeId); break; case DXIL::ResourceClass::UAV: res = &DM.GetUAV(rangeId); break; case DXIL::ResourceClass::CBuffer: res = &DM.GetCBuffer(rangeId); break; case DXIL::ResourceClass::Sampler: res = &DM.GetSampler(rangeId); break; default: DXASSERT(0, "invalid res class"); return; } IRBuilder<> Builder(handle); unsigned lowBound = res->GetLowerBound(); if (lowBound) { Value *Index = createHandle.get_index(); if (ConstantInt *cIndex = dyn_cast(Index)) { unsigned newIdx = lowBound + cIndex->getLimitedValue(); handle->setArgOperand(DXIL::OperandIndex::kCreateHandleResIndexOpIdx, Builder.getInt32(newIdx)); } else { Value *newIdx = Builder.CreateAdd(Index, Builder.getInt32(lowBound)); handle->setArgOperand(DXIL::OperandIndex::kCreateHandleResIndexOpIdx, newIdx); } } } static void PatchTBufferCreateHandle(CallInst *handle, DxilModule &DM, std::unordered_set &tbufferIDs) { DxilInst_CreateHandle createHandle(handle); DXASSERT_NOMSG(createHandle); DXIL::ResourceClass ResClass = static_cast(createHandle.get_resourceClass_val()); if (ResClass != DXIL::ResourceClass::CBuffer) return; Value *resID = createHandle.get_rangeId(); DXASSERT(isa(resID), "cannot handle dynamic resID for cbuffer CreateHandle"); if (!isa(resID)) return; unsigned rangeId = cast(resID)->getLimitedValue(); DxilResourceBase *res = &DM.GetCBuffer(rangeId); // For TBuffer, we need to switch resource type from CBuffer to SRV if (res->GetKind() == DXIL::ResourceKind::TBuffer) { // Track cbuffers IDs that are actually tbuffers tbufferIDs.insert(rangeId); hlsl::OP *hlslOP = DM.GetOP(); llvm::LLVMContext &Ctx = DM.GetCtx(); // Temporarily add SRV size to rangeID to guarantee unique new SRV ID Value *newRangeID = hlslOP->GetU32Const(rangeId + DM.GetSRVs().size()); handle->setArgOperand(DXIL::OperandIndex::kCreateHandleResIDOpIdx, newRangeID); // switch create handle to SRV handle->setArgOperand(DXIL::OperandIndex::kCreateHandleResClassOpIdx, hlslOP->GetU8Const( static_cast::type>( DXIL::ResourceClass::SRV))); Type *doubleTy = Type::getDoubleTy(Ctx); Type *i64Ty = Type::getInt64Ty(Ctx); // Replace corresponding cbuffer loads with typed buffer loads for (auto U = handle->user_begin(); U != handle->user_end(); ) { CallInst *I = cast(*(U++)); DXASSERT(I && OP::IsDxilOpFuncCallInst(I), "otherwise unexpected user of CreateHandle value"); DXIL::OpCode opcode = OP::GetDxilOpFuncCallInst(I); if (opcode == DXIL::OpCode::CBufferLoadLegacy) { DxilInst_CBufferLoadLegacy cbLoad(I); // Replace with appropriate buffer load instruction IRBuilder<> Builder(I); opcode = OP::OpCode::BufferLoad; Type *Ty = Type::getInt32Ty(Ctx); Function *BufLoad = hlslOP->GetOpFunc(opcode, Ty); Constant *opArg = hlslOP->GetU32Const((unsigned)opcode); Value *undefI = UndefValue::get(Type::getInt32Ty(Ctx)); Value *offset = cbLoad.get_regIndex(); CallInst* load = Builder.CreateCall(BufLoad, {opArg, handle, offset, undefI}); // Find extractelement uses of cbuffer load and replace + generate bitcast as necessary for (auto LU = I->user_begin(); LU != I->user_end(); ) { ExtractValueInst *evInst = dyn_cast(*(LU++)); DXASSERT(evInst && evInst->getNumIndices() == 1, "user of cbuffer load result should be extractvalue"); uint64_t idx = evInst->getIndices()[0]; Type *EltTy = evInst->getType(); IRBuilder<> EEBuilder(evInst); Value *result = nullptr; if (EltTy != Ty) { // extract two values and DXIL::OpCode::MakeDouble or construct i64 if ((EltTy == doubleTy) || (EltTy == i64Ty)) { DXASSERT(idx < 2, "64-bit component index out of range"); // This assumes big endian order in tbuffer elements (is this correct?) Value *low = EEBuilder.CreateExtractValue(load, idx * 2); Value *high = EEBuilder.CreateExtractValue(load, idx * 2 + 1); if (EltTy == doubleTy) { opcode = OP::OpCode::MakeDouble; Function *MakeDouble = hlslOP->GetOpFunc(opcode, doubleTy); Constant *opArg = hlslOP->GetU32Const((unsigned)opcode); result = EEBuilder.CreateCall(MakeDouble, {opArg, low, high}); } else { high = EEBuilder.CreateZExt(high, i64Ty); low = EEBuilder.CreateZExt(low, i64Ty); high = EEBuilder.CreateShl(high, hlslOP->GetU64Const(32)); result = EEBuilder.CreateOr(high, low); } } else { result = EEBuilder.CreateExtractValue(load, idx); result = EEBuilder.CreateBitCast(result, EltTy); } } else { result = EEBuilder.CreateExtractValue(load, idx); } evInst->replaceAllUsesWith(result); evInst->eraseFromParent(); } } else if (opcode == DXIL::OpCode::CBufferLoad) { // TODO: Handle this, or prevent this for tbuffer DXASSERT(false, "otherwise CBufferLoad used for tbuffer rather than CBufferLoadLegacy"); } else { DXASSERT(false, "otherwise unexpected user of CreateHandle value"); } I->eraseFromParent(); } } } } void DxilCondenseResources::AllocateDxilResources(DxilModule &DM) { AllocateDxilResource(DM.GetCBuffers(), DM.GetCtx()); AllocateDxilResource(DM.GetSamplers(), DM.GetCtx()); AllocateDxilResource(DM.GetUAVs(), DM.GetCtx()); AllocateDxilResource(DM.GetSRVs(), DM.GetCtx()); } void InitTBuffer(const DxilCBuffer *pSource, DxilResource *pDest) { pDest->SetKind(pSource->GetKind()); pDest->SetCompType(DXIL::ComponentType::U32); pDest->SetSampleCount(0); pDest->SetElementStride(0); pDest->SetGloballyCoherent(false); pDest->SetHasCounter(false); pDest->SetRW(false); pDest->SetROV(false); pDest->SetID(pSource->GetID()); pDest->SetSpaceID(pSource->GetSpaceID()); pDest->SetLowerBound(pSource->GetLowerBound()); pDest->SetRangeSize(pSource->GetRangeSize()); pDest->SetGlobalSymbol(pSource->GetGlobalSymbol()); pDest->SetGlobalName(pSource->GetGlobalName()); pDest->SetHandle(pSource->GetHandle()); } void DxilCondenseResources::PatchTBuffers(DxilModule &DM) { Function *createHandle = DM.GetOP()->GetOpFunc(DXIL::OpCode::CreateHandle, Type::getVoidTy(DM.GetCtx())); std::unordered_set tbufferIDs; for (User *U : createHandle->users()) { PatchTBufferCreateHandle(cast(U), DM, tbufferIDs); } // move tbuffer resources to SRVs unsigned offset = DM.GetSRVs().size(); for (auto it = DM.GetCBuffers().begin(); it != DM.GetCBuffers().end(); it++) { DxilCBuffer *CB = it->get(); unsigned resID = CB->GetID(); if (tbufferIDs.find(resID) != tbufferIDs.end()) { auto srv = make_unique(); InitTBuffer(CB, srv.get()); srv->SetID(resID + offset); DM.AddSRV(std::move(srv)); // cbuffer should get cleaned up since it's now unused. } } } void DxilCondenseResources::PatchCreateHandle(DxilModule &DM) { Function *createHandle = DM.GetOP()->GetOpFunc(DXIL::OpCode::CreateHandle, Type::getVoidTy(DM.GetCtx())); for (User *U : createHandle->users()) { PatchLowerBoundOfCreateHandle(cast(U), DM); } } static Value *PatchRangeIDForLib(DxilModule &DM, IRBuilder<> &Builder, Value *rangeIdVal, std::unordered_map &phiMap, DXIL::ResourceClass ResClass) { Value *linkRangeID = nullptr; if (isa(rangeIdVal)) { unsigned rangeId = cast(rangeIdVal)->getLimitedValue(); const DxilModule::ResourceLinkInfo &linkInfo = DM.GetResourceLinkInfo(ResClass, rangeId); linkRangeID = Builder.CreateLoad(linkInfo.ResRangeID); } else { if (PHINode *phi = dyn_cast(rangeIdVal)) { auto it = phiMap.find(phi); if (it == phiMap.end()) { unsigned numOperands = phi->getNumOperands(); PHINode *phiRangeID = Builder.CreatePHI(phi->getType(), numOperands); phiMap[phi] = phiRangeID; std::vector rangeIDs(numOperands); for (unsigned i = 0; i < numOperands; i++) { Value *V = phi->getOperand(i); BasicBlock *BB = phi->getIncomingBlock(i); IRBuilder<> Builder(BB->getTerminator()); rangeIDs[i] = PatchRangeIDForLib(DM, Builder, V, phiMap, ResClass); } for (unsigned i = 0; i < numOperands; i++) { Value *V = rangeIDs[i]; BasicBlock *BB = phi->getIncomingBlock(i); phiRangeID->addIncoming(V, BB); } linkRangeID = phiRangeID; } else { linkRangeID = it->second; } } else if (SelectInst *si = dyn_cast(rangeIdVal)) { IRBuilder<> Builder(si); Value *trueVal = PatchRangeIDForLib(DM, Builder, si->getTrueValue(), phiMap, ResClass); Value *falseVal = PatchRangeIDForLib(DM, Builder, si->getFalseValue(), phiMap, ResClass); linkRangeID = Builder.CreateSelect(si->getCondition(), trueVal, falseVal); } else if (CastInst *cast = dyn_cast(rangeIdVal)) { if (cast->getOpcode() == CastInst::CastOps::ZExt && cast->getOperand(0)->getType() == Type::getInt1Ty(DM.GetCtx())) { // select cond, 1, 0. IRBuilder<> Builder(cast); Value *trueVal = PatchRangeIDForLib( DM, Builder, ConstantInt::get(cast->getType(), 1), phiMap, ResClass); Value *falseVal = PatchRangeIDForLib( DM, Builder, ConstantInt::get(cast->getType(), 0), phiMap, ResClass); linkRangeID = Builder.CreateSelect(cast->getOperand(0), trueVal, falseVal); } } } return linkRangeID; } void DxilCondenseResources::PatchCreateHandleForLib(DxilModule &DM) { Function *createHandle = DM.GetOP()->GetOpFunc(DXIL::OpCode::CreateHandle, Type::getVoidTy(DM.GetCtx())); DM.CreateResourceLinkInfo(); for (User *U : createHandle->users()) { CallInst *handle = cast(U); DxilInst_CreateHandle createHandle(handle); DXASSERT_NOMSG(createHandle); DXIL::ResourceClass ResClass = static_cast(createHandle.get_resourceClass_val()); std::unordered_map phiMap; Value *rangeID = createHandle.get_rangeId(); IRBuilder<> Builder(handle); Value *linkRangeID = PatchRangeIDForLib( DM, Builder, rangeID, phiMap, ResClass); // Dynamic rangeId is not supported - skip and let validation report the // error. if (!linkRangeID) continue; // Update rangeID to linkinfo rangeID. handle->setArgOperand(DXIL::OperandIndex::kCreateHandleResIDOpIdx, linkRangeID); if (rangeID->user_empty() && isa(rangeID)) { cast(rangeID)->eraseFromParent(); } } } char DxilCondenseResources::ID = 0; bool llvm::AreDxilResourcesDense(llvm::Module *M, hlsl::DxilResourceBase **ppNonDense) { DxilModule &DM = M->GetOrCreateDxilModule(); DxilCondenseResources Pass; if (Pass.BuildRewriteMap(DM)) { *ppNonDense = &Pass.GetFirstRewrite(); return false; } else { *ppNonDense = nullptr; return true; } } ModulePass *llvm::createDxilCondenseResourcesPass() { return new DxilCondenseResources(); } INITIALIZE_PASS(DxilCondenseResources, "hlsl-dxil-condense", "DXIL Condense Resources", false, false)