diff --git a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp index 50b657aab79fb..2896c68d11de7 100644 --- a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp +++ b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp @@ -12,6 +12,7 @@ #include "llvm/Analysis/DXILResource.h" #include "llvm/Frontend/HLSL/HLSLResource.h" #include "llvm/IR/BasicBlock.h" +#include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instruction.h" @@ -19,6 +20,7 @@ #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/IntrinsicsDirectX.h" +#include "llvm/IR/LLVMContext.h" #include "llvm/IR/User.h" #include "llvm/InitializePasses.h" #include "llvm/Support/FormatVariadic.h" @@ -28,6 +30,26 @@ using namespace llvm; +static void diagnoseNonUniqueResourceAccess(Instruction *I, + ArrayRef Handles) { + LLVMContext &Context = I->getContext(); + std::string InstStr; + raw_string_ostream InstOS(InstStr); + I->print(InstOS); + Context.diagnose( + DiagnosticInfoGeneric("At resource access:" + Twine(InstStr), DS_Note)); + + for (auto *Handle : Handles) { + std::string HandleStr; + raw_string_ostream HandleOS(HandleStr); + Handle->print(HandleOS); + Context.diagnose(DiagnosticInfoGeneric( + "Uses resource handle:" + Twine(HandleStr), DS_Note)); + } + Context.diagnose(DiagnosticInfoGeneric( + "Resource access is not guaranteed to map to a unique global resource")); +} + static Value *traverseGEPOffsets(const DataLayout &DL, IRBuilder<> &Builder, Value *Ptr, uint64_t AccessSize) { Value *Offset = nullptr; @@ -57,7 +79,7 @@ static Value *traverseGEPOffsets(const DataLayout &DL, IRBuilder<> &Builder, GEPOffset = *GEP->idx_begin(); } else if (NumIndices == 2) { // If we have two indices, this should be an access through a pointer. - auto IndexIt = GEP->idx_begin(); + auto *IndexIt = GEP->idx_begin(); assert(cast(IndexIt)->getZExtValue() == 0 && "GEP is not indexing through pointer"); GEPOffset = *(++IndexIt); @@ -419,6 +441,224 @@ static void createLoadIntrinsic(IntrinsicInst *II, LoadInst *LI, llvm_unreachable("Unhandled case in switch"); } +static Instruction *getStoreLoadPointerOperand(Instruction *AI) { + if (auto *LI = dyn_cast(AI)) + return dyn_cast(LI->getPointerOperand()); + if (auto *SI = dyn_cast(AI)) + return dyn_cast(SI->getPointerOperand()); + + return nullptr; +} + +static const std::array HandleIntrins = { + Intrinsic::dx_resource_handlefrombinding, + Intrinsic::dx_resource_handlefromimplicitbinding, +}; + +static SmallVector collectUsedHandles(Value *Ptr) { + SmallVector Worklist = {Ptr}; + SmallVector Handles; + + while (!Worklist.empty()) { + Value *X = Worklist.pop_back_val(); + + if (!X->getType()->isPointerTy() && !X->getType()->isTargetExtTy()) + return {}; // Early exit on store/load into non-resource + + if (auto *Phi = dyn_cast(X)) + for (Use &V : Phi->incoming_values()) + Worklist.push_back(V.get()); + else if (auto *Select = dyn_cast(X)) + for (Value *V : {Select->getTrueValue(), Select->getFalseValue()}) + Worklist.push_back(V); + else if (auto *II = dyn_cast(X)) { + Intrinsic::ID IID = II->getIntrinsicID(); + + if (IID == Intrinsic::dx_resource_getpointer) + Worklist.push_back(II->getArgOperand(/*Handle=*/0)); + + if (llvm::is_contained(HandleIntrins, IID)) + Handles.push_back(II); + } + } + + return Handles; +} + +static hlsl::Binding getHandleIntrinsicBinding(IntrinsicInst *Handle, + DXILResourceTypeMap &DRTM) { + assert(llvm::is_contained(HandleIntrins, Handle->getIntrinsicID()) && + "Only expects a Handle as determined from collectUsedHandles."); + + auto *HandleTy = cast(Handle->getType()); + dxil::ResourceClass Class = DRTM[HandleTy].getResourceClass(); + uint32_t Space = cast(Handle->getArgOperand(0))->getZExtValue(); + uint32_t LowerBound = + cast(Handle->getArgOperand(1))->getZExtValue(); + uint32_t Size = cast(Handle->getArgOperand(2))->getZExtValue(); + uint32_t UpperBound = Size == UINT32_MAX ? UINT32_MAX : LowerBound + Size - 1; + + return hlsl::Binding(Class, Space, LowerBound, UpperBound, nullptr); +} + +namespace { +/// Helper for propagating the current handle and ptr indices. +struct AccessIndices { + Value *GetPtrIdx; + Value *HandleIdx; + + bool hasGetPtrIdx() { return GetPtrIdx != nullptr; } + bool hasHandleIdx() { return HandleIdx != nullptr; } +}; +} // namespace + +// getAccessIndices traverses up the control flow that a ptr came from and +// propagates back the indicies used to access the resource (AccessIndices): +// +// - GetPtrIdx is the index of dx.resource.getpointer +// - HandleIdx is the index of dx.resource.handlefrom.* +static AccessIndices +getAccessIndices(Instruction *I, SmallSetVector &DeadInsts) { + if (auto *II = dyn_cast(I)) { + if (llvm::is_contained(HandleIntrins, II->getIntrinsicID())) { + DeadInsts.insert(II); + return {nullptr, II->getArgOperand(/*Index=*/3)}; + } + + if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) { + auto *V = dyn_cast(II->getArgOperand(/*Handle=*/0)); + auto AccessIdx = getAccessIndices(V, DeadInsts); + assert(!AccessIdx.hasGetPtrIdx() && + "Encountered multiple dx.resource.getpointers in ptr chain?"); + AccessIdx.GetPtrIdx = II->getArgOperand(1); + + DeadInsts.insert(II); + return AccessIdx; + } + } + + if (auto *Phi = dyn_cast(I)) { + unsigned NumEdges = Phi->getNumIncomingValues(); + assert(NumEdges != 0 && "Malformed Phi Node"); + + IRBuilder<> Builder(Phi); + PHINode *GetPtrPhi = PHINode::Create(Builder.getInt32Ty(), NumEdges); + PHINode *HandlePhi = PHINode::Create(Builder.getInt32Ty(), NumEdges); + + bool HasGetPtr = true; + for (unsigned Idx = 0; Idx < NumEdges; Idx++) { + auto *BB = Phi->getIncomingBlock(Idx); + auto *V = dyn_cast(Phi->getIncomingValue(Idx)); + auto AccessIdx = getAccessIndices(V, DeadInsts); + HasGetPtr &= AccessIdx.hasGetPtrIdx(); + if (HasGetPtr) + GetPtrPhi->addIncoming(AccessIdx.GetPtrIdx, BB); + HandlePhi->addIncoming(AccessIdx.HandleIdx, BB); + } + + if (HasGetPtr) + Builder.Insert(GetPtrPhi); + else + GetPtrPhi = nullptr; + + Builder.Insert(HandlePhi); + + DeadInsts.insert(Phi); + return {GetPtrPhi, HandlePhi}; + } + + if (auto *Select = dyn_cast(I)) { + auto *TrueV = dyn_cast(Select->getTrueValue()); + auto TrueAccessIdx = getAccessIndices(TrueV, DeadInsts); + + auto *FalseV = dyn_cast(Select->getFalseValue()); + auto FalseAccessIdx = getAccessIndices(FalseV, DeadInsts); + + IRBuilder<> Builder(Select); + Value *GetPtrSelect = nullptr; + + if (TrueAccessIdx.hasGetPtrIdx() && FalseAccessIdx.hasGetPtrIdx()) + GetPtrSelect = + Builder.CreateSelect(Select->getCondition(), TrueAccessIdx.GetPtrIdx, + FalseAccessIdx.GetPtrIdx); + + auto *HandleSelect = + Builder.CreateSelect(Select->getCondition(), TrueAccessIdx.HandleIdx, + FalseAccessIdx.HandleIdx); + DeadInsts.insert(Select); + return {GetPtrSelect, HandleSelect}; + } + + llvm_unreachable("collectUsedHandles should assure this does not occur"); +} + +static void +replaceHandleWithIndices(Instruction *Ptr, IntrinsicInst *OldHandle, + SmallSetVector &DeadInsts) { + auto AccessIdx = getAccessIndices(Ptr, DeadInsts); + assert(AccessIdx.hasGetPtrIdx() && AccessIdx.hasHandleIdx() && + "Couldn't retrieve indices. This is guaranteed by getAccessIndices"); + + IRBuilder<> Builder(Ptr); + IntrinsicInst *Handle = cast(OldHandle->clone()); + Handle->setArgOperand(/*Index=*/3, AccessIdx.HandleIdx); + Builder.Insert(Handle); + + auto *GetPtr = + Builder.CreateIntrinsic(Ptr->getType(), Intrinsic::dx_resource_getpointer, + {Handle, AccessIdx.GetPtrIdx}); + + Ptr->replaceAllUsesWith(GetPtr); + DeadInsts.insert(Ptr); +} + +// Try to legalize dx.resource.handlefrom.*.binding and dx.resource.getpointer +// calls with their respective index values and propagate the index values to +// be used at resource access. +// +// If it can't be transformed to be legal then: +// +// Reports an error if a resource access is not guaranteed into a unique global +// resource. +// +// Returns true if any changes are made. +static bool legalizeResourceHandles(Function &F, DXILResourceTypeMap &DRTM) { + SmallSetVector DeadInsts; + for (BasicBlock &BB : make_early_inc_range(F)) { + for (Instruction &I : BB) { + if (auto *PtrOp = getStoreLoadPointerOperand(&I)) { + SmallVector Handles = collectUsedHandles(PtrOp); + unsigned NumHandles = Handles.size(); + if (NumHandles <= 1) + continue; // Legal, no-replacement required + + bool SameGlobalBinding = true; + hlsl::Binding B = getHandleIntrinsicBinding(Handles[0], DRTM); + for (unsigned Idx = 1; Idx < NumHandles; Idx++) + SameGlobalBinding &= + (B == getHandleIntrinsicBinding(Handles[Idx], DRTM)); + + if (!SameGlobalBinding) { + diagnoseNonUniqueResourceAccess(&I, Handles); + continue; + } + + replaceHandleWithIndices(PtrOp, Handles[0], DeadInsts); + } + } + } + + bool MadeChanges = false; + + for (auto *I : llvm::reverse(DeadInsts)) + if (I->hasNUses(0)) { // Handle can still be used outside of replaced path + I->eraseFromParent(); + MadeChanges = true; + } + + return MadeChanges; +} + static void replaceAccess(IntrinsicInst *II, dxil::ResourceTypeInfo &RTI) { SmallVector Worklist; for (User *U : II->users()) @@ -475,8 +715,9 @@ PreservedAnalyses DXILResourceAccess::run(Function &F, MAMProxy.getCachedResult(*F.getParent()); assert(DRTM && "DXILResourceTypeAnalysis must be available"); - bool MadeChanges = transformResourcePointers(F, *DRTM); - if (!MadeChanges) + bool MadeHandleChanges = legalizeResourceHandles(F, *DRTM); + bool MadeResourceChanges = transformResourcePointers(F, *DRTM); + if (!(MadeHandleChanges || MadeResourceChanges)) return PreservedAnalyses::all(); PreservedAnalyses PA; @@ -491,7 +732,9 @@ class DXILResourceAccessLegacy : public FunctionPass { bool runOnFunction(Function &F) override { DXILResourceTypeMap &DRTM = getAnalysis().getResourceTypeMap(); - return transformResourcePointers(F, DRTM); + bool MadeHandleChanges = legalizeResourceHandles(F, DRTM); + bool MadeResourceChanges = transformResourcePointers(F, DRTM); + return MadeHandleChanges || MadeResourceChanges; } StringRef getPassName() const override { return "DXIL Resource Access"; } DXILResourceAccessLegacy() : FunctionPass(ID) {} diff --git a/llvm/test/CodeGen/DirectX/ResourceAccess/handle-cases.ll b/llvm/test/CodeGen/DirectX/ResourceAccess/handle-cases.ll new file mode 100644 index 0000000000000..85e8bbb5db8fb --- /dev/null +++ b/llvm/test/CodeGen/DirectX/ResourceAccess/handle-cases.ll @@ -0,0 +1,117 @@ +; RUN: opt -S -dxil-resource-type -dxil-resource-access -disable-verify \ +; RUN: -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s + +; The file contains examples of hlsl snippets that will generate invalid +; looking resource access, either through code-gen or by optimization. +; These can be legalized by replacing the handles with indicies into the +; same global resource. + +; NOTE: The below resources are generated with: +; +; RWBuffer In : register(u0); +; RWStructuredBuffer Out0 : register(u1); +; RWStructuredBuffer Out1 : register(u2); +; RWStructuredBuffer OutArr[]; + +; cbuffer c { +; bool cond; +; }; + +%__cblayout_c = type <{ i32 }> + +@.str = internal unnamed_addr constant [3 x i8] c"In\00", align 1 +@.str.2 = internal unnamed_addr constant [5 x i8] c"Out0\00", align 1 +@.str.3 = internal unnamed_addr constant [5 x i8] c"Out1\00", align 1 +@c.cb = local_unnamed_addr global target("dx.CBuffer", %__cblayout_c) poison +@c.str = internal unnamed_addr constant [2 x i8] c"c\00", align 1 +@OutArr.str = internal unnamed_addr constant [7 x i8] c"OutArr\00", align 1 + +; Local select into global resource array: +; +; RWStructuredBuffer Out = cond ? OutArr[0] : OutArr[1]; +; Out[GI] = WaveActiveMax(In[GI]); +; +; This will ensure that the index is propogated through phi branching +; correctly. We see that two different handles are generated in the different +; branches, so we will ensure that the handle generation is removed from +; the branches and that the `phi i32` node is used to distinguish the index. +; Then that index is used to generate the handle. +; +; CHECK-LABEL: @select_global_resource_array() +define void @select_global_resource_array() { +entry: + %c.cb_h.i.i = tail call target("dx.CBuffer", %__cblayout_c) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_s___cblayout_cst(i32 4, i32 0, i32 1, i32 0, ptr nonnull @c.str) + store target("dx.CBuffer", %__cblayout_c) %c.cb_h.i.i, ptr @c.cb, align 4 + %c.cb = load target("dx.CBuffer", %__cblayout_c), ptr @c.cb, align 4 + %0 = call ptr addrspace(2) @llvm.dx.resource.getpointer.p2.tdx.CBuffer_s___cblayout_cst(target("dx.CBuffer", %__cblayout_c) %c.cb, i32 0) + %1 = load i32, ptr addrspace(2) %0, align 4 + %loadedv.i = trunc nuw i32 %1 to i1 + br i1 %loadedv.i, label %cond.true.i, label %cond.false.i + +cond.true.i: +; CHECK: cond.true.i: +; CHECK-NEXT: br label %cond.end.i + %2 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 0, ptr nonnull @OutArr.str) + br label %cond.end.i + +cond.false.i: +; CHECK: cond.false.i: +; CHECK-NEXT: br label %cond.end.i + %3 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 1, ptr nonnull @OutArr.str) + br label %cond.end.i + +cond.end.i: +; CHECK: cond.end.i +; CHECK-NEXT: %[[HANDLE_IDX:.*]] = phi i32 [ 0, %cond.true.i ], [ 1, %cond.false.i ] +; CHECK: %[[TID:.*]] = tail call i32 @llvm.dx.flattened.thread.id.in.group() +; CHECK: %[[WAVE_MAX:.*]] = tail call i32 @llvm.dx.wave.reduce.max.i32(i32 %{{.*}}) +; CHECK-NEXT: %[[HANDLE:.*]] = tail call target("dx.RawBuffer", i32, 1, 0) +; CHECK-SAME: @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 %[[HANDLE_IDX]], ptr nonnull @OutArr.str) +; CHECK-NEXT: call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_i32_1_0t.i32(target("dx.RawBuffer", i32, 1, 0) %[[HANDLE]], i32 %[[TID]], i32 0, i32 %[[WAVE_MAX]]) +; CHECK-NEXT: ret void + %cond.i.sroa.speculated = phi target("dx.RawBuffer", i32, 1, 0) [ %2, %cond.true.i ], [ %3, %cond.false.i ] + %4 = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str) + %5 = tail call i32 @llvm.dx.flattened.thread.id.in.group() + %6 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0_1t(target("dx.TypedBuffer", i32, 1, 0, 1) %4, i32 %5) + %7 = load i32, ptr %6, align 4 + %hlsl.wave.active.max.i = tail call i32 @llvm.dx.wave.reduce.max.i32(i32 %7) + %8 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %cond.i.sroa.speculated, i32 %5) + store i32 %hlsl.wave.active.max.i, ptr %8, align 4 + ret void +} + +; Using a local array of global resources +; +; RWStructuredBuffer Outs[2] = {OutArr[0], OutArr[1]}; +; Outs[cond ? 0 : 1][GI] = In[GI]; +; +; This will ensure that the index is propogated through the select of two +; resources. So we want to check that the `select i1` is based on the handle +; indices, and that this index is used to generate the handle. +; +; CHECK-LABEL: @local_array_of_global_resources() +define void @local_array_of_global_resources() { +entry: + %0 = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str) + %c.cb_h.i.i = tail call target("dx.CBuffer", %__cblayout_c) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_s___cblayout_cst(i32 4, i32 0, i32 1, i32 0, ptr nonnull @c.str) + store target("dx.CBuffer", %__cblayout_c) %c.cb_h.i.i, ptr @c.cb, align 4 + %1 = tail call i32 @llvm.dx.flattened.thread.id.in.group() + %2 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 0, ptr nonnull @OutArr.str) + %3 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 1, ptr nonnull @OutArr.str) + %4 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0_1t(target("dx.TypedBuffer", i32, 1, 0, 1) %0, i32 %1) + %5 = load i32, ptr %4, align 4 + %c.cb = load target("dx.CBuffer", %__cblayout_c), ptr @c.cb, align 4 + %6 = call ptr addrspace(2) @llvm.dx.resource.getpointer.p2.tdx.CBuffer_s___cblayout_cst(target("dx.CBuffer", %__cblayout_c) %c.cb, i32 0) + %7 = load i32, ptr addrspace(2) %6, align 4 + %loadedv.i = trunc nuw i32 %7 to i1 + +; CHECK: %[[TID:.*]] = tail call i32 @llvm.dx.flattened.thread.id.in.group() +; CHECK: %[[HANDLE_IDX:.*]] = select i1 %loadedv.i, i32 0, i32 1 +; CHECK-NEXT: %[[HANDLE:.*]] = tail call target("dx.RawBuffer", i32, 1, 0) +; CHECK-SAME: @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 %[[HANDLE_IDX]], ptr nonnull @OutArr.str) +; CHECK-NEXT: call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_i32_1_0t.i32(target("dx.RawBuffer", i32, 1, 0) %[[HANDLE]], i32 %[[TID]], i32 0, i32 {{.*}}) + %.sroa.speculated = select i1 %loadedv.i, target("dx.RawBuffer", i32, 1, 0) %2, target("dx.RawBuffer", i32, 1, 0) %3 + %8 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %.sroa.speculated, i32 %1) + store i32 %5, ptr %8, align 4 + ret void +} diff --git a/llvm/test/CodeGen/DirectX/ResourceAccess/handle-to-index.ll b/llvm/test/CodeGen/DirectX/ResourceAccess/handle-to-index.ll new file mode 100644 index 0000000000000..74384436222c8 --- /dev/null +++ b/llvm/test/CodeGen/DirectX/ResourceAccess/handle-to-index.ll @@ -0,0 +1,156 @@ +; RUN: opt -S -dxil-resource-type -dxil-resource-access -disable-verify \ +; RUN: -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s + +@OutArr.str = internal unnamed_addr constant [7 x i8] c"OutArr\00", align 1 + +; CHECK-LABEL: handle_phi_load( +; CHECK-SAME: i1 %[[COND:.*]], i32 %[[A:.*]], i32 %[[B:.*]]) +define i32 @handle_phi_load(i1 %cond, i32 %a, i32 %b) { +; CHECK-NOT: handlefromimplicitbinding +; CHECK: main: +; CHECK-NEXT: %[[IDX:.*]] = phi i32 [ 0, %entry ], [ 1, %if.then.i ] +; CHECK-NEXT: %[[C:.*]] = phi i32 [ %[[A]], %entry ], [ %[[B]], %if.then.i ] +; CHECK-NEXT: %[[HANDLE:.*]] = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 %[[IDX]], ptr nonnull @OutArr.str) +; CHECK-NEXT: %[[LOAD:.*]] = call { i32, i1 } @llvm.dx.resource.load.rawbuffer.i32.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %[[HANDLE]], i32 %[[C]], i32 0) +; CHECK-NEXT: %[[X:.*]] = extractvalue { i32, i1 } %[[LOAD]], 0 +; CHECK-NEXT: ret i32 %[[X]] +entry: + %handle0 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 0, ptr nonnull @OutArr.str) + br i1 %cond, label %if.then.i, label %main + +if.then.i: + %handle1 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 1, ptr nonnull @OutArr.str) + br label %main + +main: + %handle_phi = phi target("dx.RawBuffer", i32, 1, 0) [ %handle0, %entry ], [ %handle1, %if.then.i ] + %c = phi i32 [ %a, %entry ], [ %b, %if.then.i ] + %ptr = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %handle_phi, i32 %c) + %x = load i32, ptr %ptr, align 4 + ret i32 %x +} + +; CHECK-LABEL: handle_select_store( +; CHECK-SAME: i32 %[[X:.*]], i1 %[[COND:.*]], i32 %[[A:.*]], i32 %[[B:.*]]) +define void @handle_select_store(i32 %x, i1 %cond, i32 %a, i32 %b) { +; CHECK-NOT: handlefromimplicitbinding +; CHECK: entry: +; CHECK-NEXT: %[[IDX:.*]] = select i1 %[[COND]], i32 0, i32 1 +; CHECK-NEXT: %[[C:.*]] = select i1 %cond, i32 %[[A]], i32 %[[B]] +; CHECK-NEXT: %[[HANDLE:.*]] = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 %[[IDX]], ptr nonnull @OutArr.str) +; CHECK-NEXT: call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_i32_1_0t.i32(target("dx.RawBuffer", i32, 1, 0) %[[HANDLE]], i32 %[[C]], i32 0, i32 %[[X]]) +; CHECK-NEXT: ret void +entry: + %handle0 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 0, ptr nonnull @OutArr.str) + %handle1 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 1, ptr nonnull @OutArr.str) + %handle = select i1 %cond, target("dx.RawBuffer", i32, 1, 0) %handle0, target("dx.RawBuffer", i32, 1, 0) %handle1 + %c = select i1 %cond, i32 %a, i32 %b + %ptr = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %handle, i32 %c) + store i32 %x, ptr %ptr, align 4 + ret void +} + +; CHECK-LABEL: ptr_phi_store( +; CHECK-SAME: i32 %[[X:.*]], i1 %[[COND:.*]], i32 %[[A:.*]], i32 %[[B:.*]]) +define void @ptr_phi_store(i32 %x, i1 %cond, i32 %a, i32 %b) { +; CHECK-NOT: handlefromimplicitbinding +; CHECK: main: +; CHECK-NEXT: %[[C:.*]] = phi i32 [ %[[A]], %entry ], [ %[[B]], %if.then.i ] +; CHECK-NEXT: %[[IDX:.*]] = phi i32 [ 0, %entry ], [ 1, %if.then.i ] +; CHECK-NEXT: %[[HANDLE:.*]] = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 %[[IDX]], ptr nonnull @OutArr.str) +; CHECK-NEXT: call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_i32_1_0t.i32(target("dx.RawBuffer", i32, 1, 0) %[[HANDLE]], i32 %[[C]], i32 0, i32 %[[X]]) +; CHECK-NEXT: ret void +entry: + %handle0 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 0, ptr nonnull @OutArr.str) + %ptr0 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %handle0, i32 %a) + br i1 %cond, label %if.then.i, label %main + +if.then.i: + %handle1 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 1, ptr nonnull @OutArr.str) + %ptr1 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %handle1, i32 %b) + br label %main + +main: + %ptr_phi = phi ptr [ %ptr0, %entry ], [ %ptr1, %if.then.i ] + store i32 %x, ptr %ptr_phi, align 4 + ret void +} + +; CHECK-LABEL: ptr_select_load( +; CHECK-SAME: i1 %[[COND:.*]], i32 %[[A:.*]], i32 %[[B:.*]]) +define i32 @ptr_select_load(i1 %cond, i32 %a, i32 %b) { +; CHECK-NOT: handlefromimplicitbinding +; CHECK: entry: +; CHECK-NEXT: %[[C:.*]] = select i1 %[[COND]], i32 %[[A]], i32 %[[B]] +; CHECK-NEXT: %[[IDX:.*]] = select i1 %[[COND]], i32 0, i32 1 +; CHECK-NEXT: %[[HANDLE:.*]] = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 %[[IDX]], ptr nonnull @OutArr.str) +; CHECK-NEXT: %[[LOAD:.*]] = call { i32, i1 } @llvm.dx.resource.load.rawbuffer.i32.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %[[HANDLE]], i32 %[[C]], i32 0) +; CHECK-NEXT: %[[X:.*]] = extractvalue { i32, i1 } %[[LOAD]], 0 +; CHECK-NEXT: ret i32 %[[X]] +entry: + %handle0 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 0, ptr nonnull @OutArr.str) + %ptr0 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %handle0, i32 %a) + %handle1 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_i32_1_0t(i32 2, i32 0, i32 -1, i32 1, ptr nonnull @OutArr.str) + %ptr1 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %handle1, i32 %b) + %ptr = select i1 %cond, ptr %ptr0, ptr %ptr1 + %x = load i32, ptr %ptr, align 4 + ret i32 %x +} + +; CHECK-LABEL: gvn_ptr_store +; CHECK-SAME: i32 %[[X:.*]], i1 %[[COND:.*]], i32 %[[A:.*]], i32 %[[B:.*]]) +define void @gvn_ptr_store(i32 %x, i1 %cond, i32 %a, i32 %b) { +; CHECK-NOT: handlefromimplicitbinding +; CHECK: main: +; CHECK-NEXT: %[[C:.*]] = phi i32 [ %a, %entry ], [ %b, %if.then.i ] +; CHECK-NEXT: %[[IDX:.*]] = phi i32 [ 0, %entry ], [ 0, %if.then.i ] +; CHECK-NEXT: %[[HANDLE:.*]] = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0_1t(i32 2, i32 0, i32 1, i32 %[[IDX]], ptr nonnull @OutArr.str) +; CHECK-NEXT: call void @llvm.dx.resource.store.typedbuffer.tdx.TypedBuffer_i32_1_0_1t.i32(target("dx.TypedBuffer", i32, 1, 0, 1) %[[HANDLE]], i32 %[[C]], i32 %[[X]]) +; CHECK-NEXT: ret void +entry: + %handle0 = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @OutArr.str) + %ptr0 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0t(target("dx.TypedBuffer", i32, 1, 0, 1) %handle0, i32 %a) + br i1 %cond, label %if.then.i, label %main + +if.then.i: + %handle1 = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @OutArr.str) + %ptr1 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0t(target("dx.TypedBuffer", i32, 1, 0, 1) %handle1, i32 %b) + br label %main + +main: + %ptr = phi ptr [ %ptr0, %entry ], [ %ptr1, %if.then.i ] + store i32 %x, ptr %ptr, align 4 + ret void +} + +; CHECK-LABEL: multiple_use_handle +; CHECK-SAME: i32 %[[X:.*]], i1 %[[COND:.*]], i32 %[[A:.*]], i32 %[[B:.*]]) +define void @multiple_use_handle(i32 %x, i1 %cond, i32 %a, i32 %b) { +; CHECK: entry: +; CHECK-NEXT: %[[HANDLE0:.*]] = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0_1t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @OutArr.str) +; CHECK: main: +; CHECK-NEXT: %[[C:.*]] = phi i32 [ %[[A]], %entry ], [ %[[B]], %if.then.i ] +; CHECK-NEXT: %[[IDX:.*]] = phi i32 [ 0, %entry ], [ 0, %if.then.i ] +; CHECK-NEXT: %[[HANDLE1:.*]] = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0_1t(i32 2, i32 0, i32 1, i32 %[[IDX]], ptr nonnull @OutArr.str) +; CHECK-NEXT: %[[LOAD:.*]] = call { i32, i1 } @llvm.dx.resource.load.typedbuffer.i32.tdx.TypedBuffer_i32_1_0_1t(target("dx.TypedBuffer", i32, 1, 0, 1) %[[HANDLE1]], i32 %[[C]]) +; CHECK-NEXT: %[[Y:.*]] = extractvalue { i32, i1 } %[[LOAD]], 0 +; CHECK-NEXT: %[[ADD:.*]] = add i32 %[[Y]], %[[X]] +; CHECK-NEXT: call void @llvm.dx.resource.store.typedbuffer.tdx.TypedBuffer_i32_1_0_1t.i32(target("dx.TypedBuffer", i32, 1, 0, 1) %[[HANDLE0]], i32 %[[A]], i32 %[[ADD]]) +; CHECK-NEXT: ret void +entry: + %handle0 = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @OutArr.str) + %ptr0 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0t(target("dx.TypedBuffer", i32, 1, 0, 1) %handle0, i32 %a) + br i1 %cond, label %if.then.i, label %main + +if.then.i: + %handle1 = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @OutArr.str) + %ptr1 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0t(target("dx.TypedBuffer", i32, 1, 0, 1) %handle1, i32 %b) + br label %main + +main: + %ptr = phi ptr [ %ptr0, %entry ], [ %ptr1, %if.then.i ] + %y = load i32, ptr %ptr, align 4 + %add = add i32 %y, %x + store i32 %add, ptr %ptr0, align 4 + ret void +} diff --git a/llvm/test/CodeGen/DirectX/ResourceAccess/non-unique.ll b/llvm/test/CodeGen/DirectX/ResourceAccess/non-unique.ll new file mode 100644 index 0000000000000..9f62197e805cd --- /dev/null +++ b/llvm/test/CodeGen/DirectX/ResourceAccess/non-unique.ll @@ -0,0 +1,37 @@ +; RUN: not opt -S -dxil-resource-access -mtriple=dxil--shadermodel6.3-library %s 2>&1 | FileCheck %s + +; Test that a error message is generated for illegal resource accesses that do +; not access a unique global resource. + +; CHECK: note: At resource access: store i32 %7, ptr %8, align 4 +; CHECK: note: Uses resource handle: %1 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_i32_1_0t(i32 0, i32 1, i32 1, i32 0, ptr nonnull @.str.2) +; CHECK: note: Uses resource handle: %2 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_i32_1_0t(i32 0, i32 2, i32 1, i32 0, ptr nonnull @.str.4) +; CHECK: error: Resource access is not guaranteed to map to a unique global resource + +%__cblayout_c = type <{ i32 }> + +@.str = internal unnamed_addr constant [3 x i8] c"In\00", align 1 +@.str.2 = internal unnamed_addr constant [5 x i8] c"Out0\00", align 1 +@.str.4 = internal unnamed_addr constant [5 x i8] c"Out1\00", align 1 +@c.cb = local_unnamed_addr global target("dx.CBuffer", %__cblayout_c) poison +@c.str = internal unnamed_addr constant [2 x i8] c"c\00", align 1 + +define void @main() local_unnamed_addr { +entry: + %0 = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str) + %1 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_i32_1_0t(i32 0, i32 1, i32 1, i32 0, ptr nonnull @.str.2) + %2 = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_i32_1_0t(i32 0, i32 2, i32 1, i32 0, ptr nonnull @.str.4) + %c.cb_h.i.i = tail call target("dx.CBuffer", %__cblayout_c) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_s___cblayout_cst(i32 4, i32 0, i32 1, i32 0, ptr nonnull @c.str) + store target("dx.CBuffer", %__cblayout_c) %c.cb_h.i.i, ptr @c.cb, align 4 + %3 = tail call i32 @llvm.dx.flattened.thread.id.in.group() + %c.cb = load target("dx.CBuffer", %__cblayout_c), ptr @c.cb, align 4 + %4 = call ptr addrspace(2) @llvm.dx.resource.getpointer.p2.tdx.CBuffer_s___cblayout_cst(target("dx.CBuffer", %__cblayout_c) %c.cb, i32 0) + %5 = load i32, ptr addrspace(2) %4, align 4 + %loadedv.i = trunc nuw i32 %5 to i1 + %spec.select = select i1 %loadedv.i, target("dx.RawBuffer", i32, 1, 0) %2, target("dx.RawBuffer", i32, 1, 0) %1 + %6 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0_1t(target("dx.TypedBuffer", i32, 1, 0, 1) %0, i32 %3) + %7 = load i32, ptr %6, align 4 + %8 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) %spec.select, i32 %3) + store i32 %7, ptr %8, align 4 + ret void +}