Skip to content

[DirectX][ResourceAccess] Resolve resource handles at access#182106

Merged
inbelic merged 10 commits into
llvm:mainfrom
inbelic:inbelic/legalize-dynamic-indexing
Mar 3, 2026
Merged

[DirectX][ResourceAccess] Resolve resource handles at access#182106
inbelic merged 10 commits into
llvm:mainfrom
inbelic:inbelic/legalize-dynamic-indexing

Conversation

@inbelic

@inbelic inbelic commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

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.

Resolves: #165288

By reporting an error, this is part of resolving #179303.

@llvmbot

llvmbot commented Feb 18, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-backend-directx

Author: Finn Plummer (inbelic)

Changes

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 #179303.


Patch is 31.09 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/182106.diff

4 Files Affected:

  • (modified) llvm/lib/Target/DirectX/DXILResourceAccess.cpp (+227-4)
  • (added) llvm/test/CodeGen/DirectX/ResourceAccess/handle-cases.ll (+107)
  • (added) llvm/test/CodeGen/DirectX/ResourceAccess/handle-to-index.ll (+161)
  • (added) llvm/test/CodeGen/DirectX/ResourceAccess/non-unique.ll (+31)
diff --git a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
index 50b657aab79fb..8c6d2f8a14af9 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,217 @@ 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);
+}
+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)) {
+        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);
+          continue;
+        }
+
+        replaceHandleWithIndicies(PtrOp, Handles[0], DeadInsts);
+      }
+
+  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) {
   SmallVector<User *> Worklist;
   for (User *U : II->users())
@@ -475,8 +695,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 +712,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/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 }>
+
+@.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<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
+
+@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:.*]] = ta...
[truncated]

@bob80905 bob80905 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couple nits

Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp Outdated
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp Outdated

@hekota hekota left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few nits, otherwise LGTM! :)

Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp Outdated
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp Outdated
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp Outdated
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp
@inbelic inbelic changed the title [DX][ResourceAccess] Resolve resource handles at access [DirectX][ResourceAccess] Resolve resource handles at access Feb 19, 2026
@inbelic inbelic changed the base branch from users/inbelic/pr-182099 to main February 20, 2026 18:51
@inbelic inbelic force-pushed the inbelic/legalize-dynamic-indexing branch from 2ee79c4 to 38b631c Compare February 20, 2026 18:52
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing Size against -1 here would be ideal if I understand correctly.

Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp Outdated
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp
Comment thread llvm/lib/Target/DirectX/DXILResourceAccess.cpp Outdated
Comment thread llvm/test/CodeGen/DirectX/ResourceAccess/handle-cases.ll
Comment thread llvm/test/CodeGen/DirectX/ResourceAccess/handle-to-index.ll Outdated
@inbelic inbelic force-pushed the inbelic/legalize-dynamic-indexing branch from 40af406 to 61fc32d Compare March 2, 2026 17:57
inbelic added a commit that referenced this pull request Mar 2, 2026
…obal resource (#182101)

Generate a warning whenever a local resource is (re-)assigned such that
it is not guaranteed to map to a single unique global resource.

An error is not generated during sema because simple DCE or constant
folding might resolve the assignment to be to a unique global resource.
Instead, an error will be reported when trying to resolve the resource
access in the `dxil-resource-access` pass, implemented
[here](#182106).

Resolves #179303.
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request Mar 2, 2026
…n-unique global resource (#182101)

Generate a warning whenever a local resource is (re-)assigned such that
it is not guaranteed to map to a single unique global resource.

An error is not generated during sema because simple DCE or constant
folding might resolve the assignment to be to a unique global resource.
Instead, an error will be reported when trying to resolve the resource
access in the `dxil-resource-access` pass, implemented
[here](llvm/llvm-project#182106).

Resolves llvm/llvm-project#179303.
@inbelic inbelic merged commit 9081ac2 into llvm:main Mar 3, 2026
11 checks passed
@inbelic inbelic deleted the inbelic/legalize-dynamic-indexing branch March 3, 2026 22:43
sahas3 pushed a commit to sahas3/llvm-project that referenced this pull request Mar 4, 2026
…obal resource (llvm#182101)

Generate a warning whenever a local resource is (re-)assigned such that
it is not guaranteed to map to a single unique global resource.

An error is not generated during sema because simple DCE or constant
folding might resolve the assignment to be to a unique global resource.
Instead, an error will be reported when trying to resolve the resource
access in the `dxil-resource-access` pass, implemented
[here](llvm#182106).

Resolves llvm#179303.
sahas3 pushed a commit to sahas3/llvm-project that referenced this pull request Mar 4, 2026
…2106)

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.

Resolves: llvm#165288

By reporting an error, this is part of resolving
llvm#179303.
sujianIBM pushed a commit to sujianIBM/llvm-project that referenced this pull request Mar 5, 2026
…obal resource (llvm#182101)

Generate a warning whenever a local resource is (re-)assigned such that
it is not guaranteed to map to a single unique global resource.

An error is not generated during sema because simple DCE or constant
folding might resolve the assignment to be to a unique global resource.
Instead, an error will be reported when trying to resolve the resource
access in the `dxil-resource-access` pass, implemented
[here](llvm#182106).

Resolves llvm#179303.
sujianIBM pushed a commit to sujianIBM/llvm-project that referenced this pull request Mar 5, 2026
…2106)

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.

Resolves: llvm#165288

By reporting an error, this is part of resolving
llvm#179303.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[HLSL][DX] Let dxil-resource-access handle resource access into unique global resources

4 participants