[llvm-branch-commits] [llvm] [DX][ResourceAccess] Resolve resource handles at access (PR #182106)

Finn Plummer via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Feb 18 11:13:53 PST 2026


https://github.com/inbelic created https://github.com/llvm/llvm-project/pull/182106

This change resolves handles (or corresponding ptr) that all point into a unique global resource by propagating an index into that global resource through control flow.

If a unique global resource can't be resolved, an error is reported instead.

This specifically resolves all handles that point into the same global resource array.

By reporting an error, this is part of resolving https://github.com/llvm/llvm-project/issues/179303.

>From b4f712d79fb8fc39da1bde318353f5687b23bf99 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Tue, 17 Feb 2026 15:03:32 -0800
Subject: [PATCH 1/2] [DX] Create an error when handles don't map to a unique
 global resource

---
 .../lib/Target/DirectX/DXILResourceAccess.cpp | 107 +++++++++++++++++-
 .../DirectX/ResourceAccess/non-unique.ll      |  31 +++++
 2 files changed, 134 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/CodeGen/DirectX/ResourceAccess/non-unique.ll

diff --git a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
index 50b657aab79fb..656cdec9b69b2 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,13 @@
 
 using namespace llvm;
 
+static void diagnoseNonUniqueResourceAccess(Instruction *I,
+                                            ArrayRef<IntrinsicInst *> Handles) {
+  LLVMContext &Context = I->getContext();
+  Context.diagnose(DiagnosticInfoGeneric(
+      "Resource access is not guarenteed 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 +66,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<ConstantInt>(IndexIt)->getZExtValue() == 0 &&
              "GEP is not indexing through pointer");
       GEPOffset = *(++IndexIt);
@@ -419,6 +428,93 @@ static void createLoadIntrinsic(IntrinsicInst *II, LoadInst *LI,
   llvm_unreachable("Unhandled case in switch");
 }
 
+static Instruction *getPointerOperand(Instruction *AI) {
+  if (auto *LI = dyn_cast<LoadInst>(AI))
+    return dyn_cast<Instruction>(LI->getPointerOperand());
+  if (auto *SI = dyn_cast<StoreInst>(AI))
+    return dyn_cast<Instruction>(SI->getPointerOperand());
+
+  return nullptr;
+}
+
+static const std::array<Intrinsic::ID, 2> HandleIntrins = {
+    Intrinsic::dx_resource_handlefrombinding,
+    Intrinsic::dx_resource_handlefromimplicitbinding,
+};
+
+static SmallVector<IntrinsicInst *> collectUsedHandles(Value *Ptr) {
+  SmallVector<Value *> Worklist = {Ptr};
+  SmallVector<IntrinsicInst *> 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<PHINode>(X))
+      for (Use &V : Phi->incoming_values())
+        Worklist.push_back(V.get());
+    else if (auto *Select = dyn_cast<SelectInst>(X))
+      for (Value *V : {Select->getTrueValue(), Select->getFalseValue()})
+        Worklist.push_back(V);
+    else if (auto *II = dyn_cast<IntrinsicInst>(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<TargetExtType>(Handle->getType());
+  dxil::ResourceClass Class = DRTM[HandleTy].getResourceClass();
+  uint32_t Space = cast<ConstantInt>(Handle->getArgOperand(0))->getZExtValue();
+  uint32_t LowerBound =
+      cast<ConstantInt>(Handle->getArgOperand(1))->getZExtValue();
+  int32_t Size = cast<ConstantInt>(Handle->getArgOperand(2))->getZExtValue();
+  uint32_t UpperBound = Size < 0 ? UINT32_MAX : LowerBound + Size - 1;
+
+  return hlsl::Binding(Class, Space, LowerBound, UpperBound, nullptr);
+}
+
+// Reports an error if a resource access is not guarenteed to be into a unique
+// global resource.
+//
+// Returns true if any changes are made.
+static bool legalizeResourceHandles(Function &F, DXILResourceTypeMap &DRTM) {
+  for (BasicBlock &BB : make_early_inc_range(F))
+    for (Instruction &I : BB)
+      if (auto *PtrOp = getPointerOperand(&I)) {
+        SmallVector<IntrinsicInst *> 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 I = 1; I < NumHandles; I++)
+          SameGlobalBinding &=
+              (B == getHandleIntrinsicBinding(Handles[I], DRTM));
+
+        if (!SameGlobalBinding) {
+          diagnoseNonUniqueResourceAccess(&I, Handles);
+        }
+      }
+
+  return false;
+}
+
 static void replaceAccess(IntrinsicInst *II, dxil::ResourceTypeInfo &RTI) {
   SmallVector<User *> Worklist;
   for (User *U : II->users())
@@ -475,8 +571,9 @@ PreservedAnalyses DXILResourceAccess::run(Function &F,
       MAMProxy.getCachedResult<DXILResourceTypeAnalysis>(*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 +588,9 @@ class DXILResourceAccessLegacy : public FunctionPass {
   bool runOnFunction(Function &F) override {
     DXILResourceTypeMap &DRTM =
         getAnalysis<DXILResourceTypeWrapperPass>().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/non-unique.ll b/llvm/test/CodeGen/DirectX/ResourceAccess/non-unique.ll
new file mode 100644
index 0000000000000..795e7f84c3cc2
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ResourceAccess/non-unique.ll
@@ -0,0 +1,31 @@
+; RUN: not opt -S -dxil-resource-access -mtriple=dxil--shadermodel6.3-library %s 2>&1 | FileCheck %s
+
+; CHECK: error: Resource access is not guarenteed to map to a unique global resource
+
+%__cblayout_c = type <{ i32 }>
+
+ at .str = internal unnamed_addr constant [3 x i8] c"In\00", align 1
+ at .str.2 = internal unnamed_addr constant [5 x i8] c"Out0\00", align 1
+ at .str.4 = internal unnamed_addr constant [5 x i8] c"Out1\00", align 1
+ at c.cb = local_unnamed_addr global target("dx.CBuffer", %__cblayout_c) poison
+ at 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
+}

>From 2ee79c4cbc3725b99d204f9ddad48d4ab5d4cf3d Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Tue, 17 Feb 2026 15:12:08 -0800
Subject: [PATCH 2/2] [DX] Replace handles with indicies into unique global
 resource

---
 .../lib/Target/DirectX/DXILResourceAccess.cpp | 126 +++++++++++++-
 .../DirectX/ResourceAccess/handle-cases.ll    | 107 ++++++++++++
 .../DirectX/ResourceAccess/handle-to-index.ll | 161 ++++++++++++++++++
 3 files changed, 393 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/CodeGen/DirectX/ResourceAccess/handle-cases.ll
 create mode 100644 llvm/test/CodeGen/DirectX/ResourceAccess/handle-to-index.ll

diff --git a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
index 656cdec9b69b2..8c6d2f8a14af9 100644
--- a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
+++ b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
@@ -487,12 +487,127 @@ static hlsl::Binding getHandleIntrinsicBinding(IntrinsicInst *Handle,
 
   return hlsl::Binding(Class, Space, LowerBound, UpperBound, nullptr);
 }
+namespace {
+/// Helper for propogating the current handle and ptr indicies.
+struct AccessIndicies {
+  Value *GetPtrIdx;
+  Value *HandleIdx;
+
+  bool hasGetPtrIdx() { return GetPtrIdx != nullptr; }
+};
+} // namespace
+
+// getAccessIndicies traverses up the control flow that a ptr came from and
+// propagates back the indicies used to access the resource (AccessIndicies):
+//
+//  - GetPtrIdx is the index of dx.resource.getpointer
+//  - HandleIdx is the index of dx.resource.handlefrom.*
+static AccessIndicies
+getAccessIndicies(Instruction *I,
+                  SmallSetVector<Instruction *, 16> &DeadInsts) {
+  if (auto *II = dyn_cast<IntrinsicInst>(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<Instruction>(II->getArgOperand(/*Handle=*/0));
+      auto AccessIdx = getAccessIndicies(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<PHINode>(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 I = 0; I < NumEdges; I++) {
+      auto *BB = Phi->getIncomingBlock(I);
+      auto *V = dyn_cast<Instruction>(Phi->getIncomingValue(I));
+      auto AccessIdx = getAccessIndicies(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<SelectInst>(I)) {
+    auto *TrueV = dyn_cast<Instruction>(Select->getTrueValue());
+    auto TrueAccessIdx = getAccessIndicies(TrueV, DeadInsts);
 
+    auto *FalseV = dyn_cast<Instruction>(Select->getFalseValue());
+    auto FalseAccessIdx = getAccessIndicies(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
+replaceHandleWithIndicies(Instruction *Ptr, IntrinsicInst *OldHandle,
+                          SmallSetVector<Instruction *, 16> &DeadInsts) {
+  auto AccessIdx = getAccessIndicies(Ptr, DeadInsts);
+
+  IRBuilder<> Builder(Ptr);
+  IntrinsicInst *Handle = cast<IntrinsicInst>(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 propogate 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 guarenteed to be into a unique
 // global resource.
 //
 // Returns true if any changes are made.
 static bool legalizeResourceHandles(Function &F, DXILResourceTypeMap &DRTM) {
+  SmallSetVector<Instruction *, 16> DeadInsts;
   for (BasicBlock &BB : make_early_inc_range(F))
     for (Instruction &I : BB)
       if (auto *PtrOp = getPointerOperand(&I)) {
@@ -509,10 +624,19 @@ static bool legalizeResourceHandles(Function &F, DXILResourceTypeMap &DRTM) {
 
         if (!SameGlobalBinding) {
           diagnoseNonUniqueResourceAccess(&I, Handles);
+          continue;
         }
+
+        replaceHandleWithIndicies(PtrOp, Handles[0], DeadInsts);
       }
 
-  return false;
+  bool MadeChanges = DeadInsts.size() > 0;
+
+  for (auto *I : llvm::reverse(DeadInsts))
+    if (I->hasNUses(0)) // Handle maybe used elsewhere aside from replaced path
+      I->eraseFromParent();
+
+  return MadeChanges;
 }
 
 static void replaceAccess(IntrinsicInst *II, dxil::ResourceTypeInfo &RTI) {
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..5b73dc528aff6
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ResourceAccess/handle-cases.ll
@@ -0,0 +1,107 @@
+; 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<int> In : register(u0);
+;   RWStructuredBuffer<int> Out0 : register(u1);
+;   RWStructuredBuffer<int> Out1 : register(u2);
+;   RWStructuredBuffer<int> OutArr[];
+
+;   cbuffer c {
+;       bool cond;
+;   };
+
+%__cblayout_c = type <{ i32 }>
+
+ at .str = internal unnamed_addr constant [3 x i8] c"In\00", align 1
+ at .str.2 = internal unnamed_addr constant [5 x i8] c"Out0\00", align 1
+ at .str.3 = internal unnamed_addr constant [5 x i8] c"Out1\00", align 1
+ at c.cb = local_unnamed_addr global target("dx.CBuffer", %__cblayout_c) poison
+ at c.str = internal unnamed_addr constant [2 x i8] c"c\00", align 1
+ at OutArr.str = internal unnamed_addr constant [7 x i8] c"OutArr\00", align 1
+
+; Local select into global resource array:
+;
+;   RWStructuredBuffer<int> Out = cond ? OutArr[0] : OutArr[1];
+;   Out[GI] = WaveActiveMax(In[GI]);
+;
+; 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<int> Outs[2] = {OutArr[0], OutArr[1]};
+;   Outs[cond ? 0 : 1][GI] = In[GI];
+;
+; 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..1e7304a7232fc
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ResourceAccess/handle-to-index.ll
@@ -0,0 +1,161 @@
+; RUN: opt -S -dxil-resource-type -dxil-resource-access -disable-verify \
+; RUN:  -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
+
+ at 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) {
+;   %3 = call { i32, i1 } @llvm.dx.resource.load.typedbuffer.i32.tdx.TypedBuffer_i32_1_0_1t(target("dx.TypedBuffer", i32, 1, 0, 1) %2, i32 %1)
+;   %4 = extractvalue { i32, i1 } %3, 0
+;   %add = add i32 %4, %x
+;   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)
+;   ret void
+; 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
+}



More information about the llvm-branch-commits mailing list