| //===- DXILResourceAccess.cpp - Resource access via load/store ------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "DXILResourceAccess.h" |
| #include "DirectX.h" |
| #include "llvm/ADT/SetVector.h" |
| #include "llvm/Analysis/DXILResource.h" |
| #include "llvm/IR/BasicBlock.h" |
| #include "llvm/IR/Dominators.h" |
| #include "llvm/IR/IRBuilder.h" |
| #include "llvm/IR/Instruction.h" |
| #include "llvm/IR/Instructions.h" |
| #include "llvm/IR/IntrinsicInst.h" |
| #include "llvm/IR/Intrinsics.h" |
| #include "llvm/IR/IntrinsicsDirectX.h" |
| #include "llvm/IR/User.h" |
| #include "llvm/InitializePasses.h" |
| #include "llvm/Transforms/Utils/ValueMapper.h" |
| |
| #define DEBUG_TYPE "dxil-resource-access" |
| |
| using namespace llvm; |
| |
| static Value *calculateGEPOffset(GetElementPtrInst *GEP, Value *PrevOffset, |
| dxil::ResourceTypeInfo &RTI) { |
| assert(!PrevOffset && "Non-constant GEP chains not handled yet"); |
| |
| const DataLayout &DL = GEP->getDataLayout(); |
| |
| uint64_t ScalarSize = 1; |
| if (RTI.isTyped()) { |
| Type *ContainedType = RTI.getHandleTy()->getTypeParameter(0); |
| // We need the size of an element in bytes so that we can calculate the |
| // offset in elements given a total offset in bytes. |
| Type *ScalarType = ContainedType->getScalarType(); |
| ScalarSize = DL.getTypeSizeInBits(ScalarType) / 8; |
| } |
| |
| APInt ConstantOffset(DL.getIndexTypeSizeInBits(GEP->getType()), 0); |
| if (GEP->accumulateConstantOffset(DL, ConstantOffset)) { |
| APInt Scaled = ConstantOffset.udiv(ScalarSize); |
| return ConstantInt::get(Type::getInt32Ty(GEP->getContext()), Scaled); |
| } |
| |
| auto IndexIt = GEP->idx_begin(); |
| assert(cast<ConstantInt>(IndexIt)->getZExtValue() == 0 && |
| "GEP is not indexing through pointer"); |
| ++IndexIt; |
| Value *Offset = *IndexIt; |
| assert(++IndexIt == GEP->idx_end() && "Too many indices in GEP"); |
| return Offset; |
| } |
| |
| static void createTypedBufferStore(IntrinsicInst *II, StoreInst *SI, |
| Value *Offset, dxil::ResourceTypeInfo &RTI) { |
| IRBuilder<> Builder(SI); |
| Type *ContainedType = RTI.getHandleTy()->getTypeParameter(0); |
| Type *LoadType = StructType::get(ContainedType, Builder.getInt1Ty()); |
| |
| Value *V = SI->getValueOperand(); |
| if (V->getType() == ContainedType) { |
| // V is already the right type. |
| assert(!Offset && "store of whole element has offset?"); |
| } else if (V->getType() == ContainedType->getScalarType()) { |
| // We're storing a scalar, so we need to load the current value and only |
| // replace the relevant part. |
| auto *Load = Builder.CreateIntrinsic( |
| LoadType, Intrinsic::dx_resource_load_typedbuffer, |
| {II->getOperand(0), II->getOperand(1)}); |
| auto *Struct = Builder.CreateExtractValue(Load, {0}); |
| |
| // If we have an offset from seeing a GEP earlier, use that. Otherwise, 0. |
| if (!Offset) |
| Offset = ConstantInt::get(Builder.getInt32Ty(), 0); |
| V = Builder.CreateInsertElement(Struct, V, Offset); |
| } else { |
| llvm_unreachable("Store to typed resource has invalid type"); |
| } |
| |
| auto *Inst = Builder.CreateIntrinsic( |
| Builder.getVoidTy(), Intrinsic::dx_resource_store_typedbuffer, |
| {II->getOperand(0), II->getOperand(1), V}); |
| SI->replaceAllUsesWith(Inst); |
| } |
| |
| static void createRawStore(IntrinsicInst *II, StoreInst *SI, Value *Offset) { |
| IRBuilder<> Builder(SI); |
| |
| if (!Offset) |
| Offset = ConstantInt::get(Builder.getInt32Ty(), 0); |
| Value *V = SI->getValueOperand(); |
| // TODO: break up larger types |
| auto *Inst = Builder.CreateIntrinsic( |
| Builder.getVoidTy(), Intrinsic::dx_resource_store_rawbuffer, |
| {II->getOperand(0), II->getOperand(1), Offset, V}); |
| SI->replaceAllUsesWith(Inst); |
| } |
| |
| static void createStoreIntrinsic(IntrinsicInst *II, StoreInst *SI, |
| Value *Offset, dxil::ResourceTypeInfo &RTI) { |
| switch (RTI.getResourceKind()) { |
| case dxil::ResourceKind::TypedBuffer: |
| return createTypedBufferStore(II, SI, Offset, RTI); |
| case dxil::ResourceKind::RawBuffer: |
| case dxil::ResourceKind::StructuredBuffer: |
| return createRawStore(II, SI, Offset); |
| case dxil::ResourceKind::Texture1D: |
| case dxil::ResourceKind::Texture2D: |
| case dxil::ResourceKind::Texture2DMS: |
| case dxil::ResourceKind::Texture3D: |
| case dxil::ResourceKind::TextureCube: |
| case dxil::ResourceKind::Texture1DArray: |
| case dxil::ResourceKind::Texture2DArray: |
| case dxil::ResourceKind::Texture2DMSArray: |
| case dxil::ResourceKind::TextureCubeArray: |
| case dxil::ResourceKind::FeedbackTexture2D: |
| case dxil::ResourceKind::FeedbackTexture2DArray: |
| reportFatalUsageError("DXIL Load not implemented yet"); |
| return; |
| case dxil::ResourceKind::CBuffer: |
| case dxil::ResourceKind::Sampler: |
| case dxil::ResourceKind::TBuffer: |
| case dxil::ResourceKind::RTAccelerationStructure: |
| case dxil::ResourceKind::Invalid: |
| case dxil::ResourceKind::NumEntries: |
| llvm_unreachable("Invalid resource kind for store"); |
| } |
| llvm_unreachable("Unhandled case in switch"); |
| } |
| |
| static void createTypedBufferLoad(IntrinsicInst *II, LoadInst *LI, |
| Value *Offset, dxil::ResourceTypeInfo &RTI) { |
| IRBuilder<> Builder(LI); |
| Type *ContainedType = RTI.getHandleTy()->getTypeParameter(0); |
| Type *LoadType = StructType::get(ContainedType, Builder.getInt1Ty()); |
| |
| Value *V = |
| Builder.CreateIntrinsic(LoadType, Intrinsic::dx_resource_load_typedbuffer, |
| {II->getOperand(0), II->getOperand(1)}); |
| V = Builder.CreateExtractValue(V, {0}); |
| |
| if (Offset) |
| V = Builder.CreateExtractElement(V, Offset); |
| |
| // If we loaded a <1 x ...> instead of a scalar (presumably to feed a |
| // shufflevector), then make sure we're maintaining the resulting type. |
| if (auto *VT = dyn_cast<FixedVectorType>(LI->getType())) |
| if (VT->getNumElements() == 1 && !isa<FixedVectorType>(V->getType())) |
| V = Builder.CreateInsertElement(PoisonValue::get(VT), V, |
| Builder.getInt32(0)); |
| |
| LI->replaceAllUsesWith(V); |
| } |
| |
| static void createRawLoad(IntrinsicInst *II, LoadInst *LI, Value *Offset) { |
| IRBuilder<> Builder(LI); |
| // TODO: break up larger types |
| Type *LoadType = StructType::get(LI->getType(), Builder.getInt1Ty()); |
| if (!Offset) |
| Offset = ConstantInt::get(Builder.getInt32Ty(), 0); |
| Value *V = |
| Builder.CreateIntrinsic(LoadType, Intrinsic::dx_resource_load_rawbuffer, |
| {II->getOperand(0), II->getOperand(1), Offset}); |
| V = Builder.CreateExtractValue(V, {0}); |
| |
| LI->replaceAllUsesWith(V); |
| } |
| |
| static void createLoadIntrinsic(IntrinsicInst *II, LoadInst *LI, Value *Offset, |
| dxil::ResourceTypeInfo &RTI) { |
| switch (RTI.getResourceKind()) { |
| case dxil::ResourceKind::TypedBuffer: |
| return createTypedBufferLoad(II, LI, Offset, RTI); |
| case dxil::ResourceKind::RawBuffer: |
| case dxil::ResourceKind::StructuredBuffer: |
| return createRawLoad(II, LI, Offset); |
| case dxil::ResourceKind::Texture1D: |
| case dxil::ResourceKind::Texture2D: |
| case dxil::ResourceKind::Texture2DMS: |
| case dxil::ResourceKind::Texture3D: |
| case dxil::ResourceKind::TextureCube: |
| case dxil::ResourceKind::Texture1DArray: |
| case dxil::ResourceKind::Texture2DArray: |
| case dxil::ResourceKind::Texture2DMSArray: |
| case dxil::ResourceKind::TextureCubeArray: |
| case dxil::ResourceKind::FeedbackTexture2D: |
| case dxil::ResourceKind::FeedbackTexture2DArray: |
| case dxil::ResourceKind::CBuffer: |
| case dxil::ResourceKind::TBuffer: |
| // TODO: handle these |
| return; |
| case dxil::ResourceKind::Sampler: |
| case dxil::ResourceKind::RTAccelerationStructure: |
| case dxil::ResourceKind::Invalid: |
| case dxil::ResourceKind::NumEntries: |
| llvm_unreachable("Invalid resource kind for load"); |
| } |
| llvm_unreachable("Unhandled case in switch"); |
| } |
| |
| static SmallVector<Instruction *> collectBlockUseDef(Instruction *Start) { |
| SmallPtrSet<Instruction *, 32> Visited; |
| SmallVector<Instruction *, 32> Worklist; |
| SmallVector<Instruction *> Out; |
| auto *BB = Start->getParent(); |
| |
| // Seed with direct users in this block. |
| for (User *U : Start->users()) { |
| if (auto *I = dyn_cast<Instruction>(U)) { |
| if (I->getParent() == BB) |
| Worklist.push_back(I); |
| } |
| } |
| |
| // BFS over transitive users, constrained to the same block. |
| while (!Worklist.empty()) { |
| Instruction *I = Worklist.pop_back_val(); |
| if (!Visited.insert(I).second) |
| continue; |
| Out.push_back(I); |
| |
| for (User *U : I->users()) { |
| if (auto *J = dyn_cast<Instruction>(U)) { |
| if (J->getParent() == BB) |
| Worklist.push_back(J); |
| } |
| } |
| for (Use &V : I->operands()) { |
| if (auto *J = dyn_cast<Instruction>(V)) { |
| if (J->getParent() == BB && V != Start) |
| Worklist.push_back(J); |
| } |
| } |
| } |
| |
| // Order results in program order. |
| DenseMap<const Instruction *, unsigned> Ord; |
| unsigned Idx = 0; |
| for (Instruction &I : *BB) |
| Ord[&I] = Idx++; |
| |
| llvm::sort(Out, [&](Instruction *A, Instruction *B) { |
| return Ord.lookup(A) < Ord.lookup(B); |
| }); |
| |
| return Out; |
| } |
| |
| static void phiNodeRemapHelper(PHINode *Phi, BasicBlock *BB, |
| IRBuilder<> &Builder, |
| SmallVector<Instruction *> &UsesInBlock) { |
| |
| ValueToValueMapTy VMap; |
| Value *Val = Phi->getIncomingValueForBlock(BB); |
| VMap[Phi] = Val; |
| Builder.SetInsertPoint(&BB->back()); |
| for (Instruction *I : UsesInBlock) { |
| // don't clone over the Phi just remap them |
| if (auto *PhiNested = dyn_cast<PHINode>(I)) { |
| VMap[PhiNested] = PhiNested->getIncomingValueForBlock(BB); |
| continue; |
| } |
| Instruction *Clone = I->clone(); |
| RemapInstruction(Clone, VMap, |
| RF_NoModuleLevelChanges | RF_IgnoreMissingLocals); |
| Builder.Insert(Clone); |
| VMap[I] = Clone; |
| } |
| } |
| |
| static void phiNodeReplacement(IntrinsicInst *II, |
| SmallVectorImpl<Instruction *> &PrevBBDeadInsts, |
| SetVector<BasicBlock *> &DeadBB) { |
| SmallVector<Instruction *> CurrBBDeadInsts; |
| for (User *U : II->users()) { |
| auto *Phi = dyn_cast<PHINode>(U); |
| if (!Phi) |
| continue; |
| |
| IRBuilder<> Builder(Phi); |
| SmallVector<Instruction *> UsesInBlock = collectBlockUseDef(Phi); |
| bool HasReturnUse = isa<ReturnInst>(UsesInBlock.back()); |
| |
| for (unsigned I = 0, E = Phi->getNumIncomingValues(); I < E; I++) { |
| auto *CurrIncomingBB = Phi->getIncomingBlock(I); |
| phiNodeRemapHelper(Phi, CurrIncomingBB, Builder, UsesInBlock); |
| if (HasReturnUse) |
| PrevBBDeadInsts.push_back(&CurrIncomingBB->back()); |
| } |
| |
| CurrBBDeadInsts.push_back(Phi); |
| |
| for (Instruction *I : UsesInBlock) { |
| CurrBBDeadInsts.push_back(I); |
| } |
| if (HasReturnUse) { |
| BasicBlock *PhiBB = Phi->getParent(); |
| DeadBB.insert(PhiBB); |
| } |
| } |
| // Traverse the now-dead instructions in RPO and remove them. |
| for (Instruction *Dead : llvm::reverse(CurrBBDeadInsts)) |
| Dead->eraseFromParent(); |
| CurrBBDeadInsts.clear(); |
| } |
| |
| static void replaceAccess(IntrinsicInst *II, dxil::ResourceTypeInfo &RTI) { |
| // Process users keeping track of indexing accumulated from GEPs. |
| struct AccessAndOffset { |
| User *Access; |
| Value *Offset; |
| }; |
| SmallVector<AccessAndOffset> Worklist; |
| for (User *U : II->users()) |
| Worklist.push_back({U, nullptr}); |
| |
| SmallVector<Instruction *> DeadInsts; |
| while (!Worklist.empty()) { |
| AccessAndOffset Current = Worklist.back(); |
| Worklist.pop_back(); |
| |
| if (auto *GEP = dyn_cast<GetElementPtrInst>(Current.Access)) { |
| IRBuilder<> Builder(GEP); |
| |
| Value *Offset = calculateGEPOffset(GEP, Current.Offset, RTI); |
| for (User *U : GEP->users()) |
| Worklist.push_back({U, Offset}); |
| DeadInsts.push_back(GEP); |
| |
| } else if (auto *SI = dyn_cast<StoreInst>(Current.Access)) { |
| assert(SI->getValueOperand() != II && "Pointer escaped!"); |
| createStoreIntrinsic(II, SI, Current.Offset, RTI); |
| DeadInsts.push_back(SI); |
| |
| } else if (auto *LI = dyn_cast<LoadInst>(Current.Access)) { |
| createLoadIntrinsic(II, LI, Current.Offset, RTI); |
| DeadInsts.push_back(LI); |
| } else |
| llvm_unreachable("Unhandled instruction - pointer escaped?"); |
| } |
| |
| // Traverse the now-dead instructions in RPO and remove them. |
| for (Instruction *Dead : llvm::reverse(DeadInsts)) |
| Dead->eraseFromParent(); |
| II->eraseFromParent(); |
| } |
| |
| static bool transformResourcePointers(Function &F, DXILResourceTypeMap &DRTM) { |
| SmallVector<std::pair<IntrinsicInst *, dxil::ResourceTypeInfo>> Resources; |
| SetVector<BasicBlock *> DeadBB; |
| SmallVector<Instruction *> PrevBBDeadInsts; |
| for (BasicBlock &BB : make_early_inc_range(F)) { |
| for (Instruction &I : make_early_inc_range(BB)) |
| if (auto *II = dyn_cast<IntrinsicInst>(&I)) |
| if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) |
| phiNodeReplacement(II, PrevBBDeadInsts, DeadBB); |
| |
| for (Instruction &I : BB) |
| if (auto *II = dyn_cast<IntrinsicInst>(&I)) |
| if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) { |
| auto *HandleTy = cast<TargetExtType>(II->getArgOperand(0)->getType()); |
| Resources.emplace_back(II, DRTM[HandleTy]); |
| } |
| } |
| for (auto *Dead : PrevBBDeadInsts) |
| Dead->eraseFromParent(); |
| PrevBBDeadInsts.clear(); |
| for (auto *Dead : DeadBB) |
| Dead->eraseFromParent(); |
| DeadBB.clear(); |
| |
| for (auto &[II, RI] : Resources) |
| replaceAccess(II, RI); |
| |
| return !Resources.empty(); |
| } |
| |
| PreservedAnalyses DXILResourceAccess::run(Function &F, |
| FunctionAnalysisManager &FAM) { |
| auto &MAMProxy = FAM.getResult<ModuleAnalysisManagerFunctionProxy>(F); |
| DXILResourceTypeMap *DRTM = |
| MAMProxy.getCachedResult<DXILResourceTypeAnalysis>(*F.getParent()); |
| assert(DRTM && "DXILResourceTypeAnalysis must be available"); |
| |
| bool MadeChanges = transformResourcePointers(F, *DRTM); |
| if (!MadeChanges) |
| return PreservedAnalyses::all(); |
| |
| PreservedAnalyses PA; |
| PA.preserve<DXILResourceTypeAnalysis>(); |
| PA.preserve<DominatorTreeAnalysis>(); |
| return PA; |
| } |
| |
| namespace { |
| class DXILResourceAccessLegacy : public FunctionPass { |
| public: |
| bool runOnFunction(Function &F) override { |
| DXILResourceTypeMap &DRTM = |
| getAnalysis<DXILResourceTypeWrapperPass>().getResourceTypeMap(); |
| return transformResourcePointers(F, DRTM); |
| } |
| StringRef getPassName() const override { return "DXIL Resource Access"; } |
| DXILResourceAccessLegacy() : FunctionPass(ID) {} |
| |
| static char ID; // Pass identification. |
| void getAnalysisUsage(llvm::AnalysisUsage &AU) const override { |
| AU.addRequired<DXILResourceTypeWrapperPass>(); |
| AU.addPreserved<DominatorTreeWrapperPass>(); |
| } |
| }; |
| char DXILResourceAccessLegacy::ID = 0; |
| } // end anonymous namespace |
| |
| INITIALIZE_PASS_BEGIN(DXILResourceAccessLegacy, DEBUG_TYPE, |
| "DXIL Resource Access", false, false) |
| INITIALIZE_PASS_DEPENDENCY(DXILResourceTypeWrapperPass) |
| INITIALIZE_PASS_END(DXILResourceAccessLegacy, DEBUG_TYPE, |
| "DXIL Resource Access", false, false) |
| |
| FunctionPass *llvm::createDXILResourceAccessLegacyPass() { |
| return new DXILResourceAccessLegacy(); |
| } |