Skip to content

[DirectX] Mark dx.resource.getpointer as convergent#182099

Merged
inbelic merged 3 commits into
llvm:mainfrom
inbelic:inbelic/getptr-conv
Feb 20, 2026
Merged

[DirectX] Mark dx.resource.getpointer as convergent#182099
inbelic merged 3 commits into
llvm:mainfrom
inbelic:inbelic/getptr-conv

Conversation

@inbelic

@inbelic inbelic commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

By marking dx.resource.getpointer as convergent, we are able to prevent unwanted code transforms that make resource access look illegal. Namely, this prevents the creation of a ptr into a resource handle to be moved throughout control flow (eg in SimplifyCFG), further preventing sink/hoist optimizations on the returned ptr during InstCombine.

Previously, these transforms were undone by the phiNodeReplacement function in dxil-resource-access. However, in general, we would like to follow a policy of preventing these transforms from occurring rather than having to undo the work. This change is aligned with this.

This is a pre-requisite to resolving #165288.

- This prevents unwanted optimizations occuring around resource access
- Add tests to show prevention of sinking handle retrieval for
GVN/SimplifyCFG cases
- this code and hence test cases are now unreachable from code
generation
@llvmbot

llvmbot commented Feb 18, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-backend-directx

Author: Finn Plummer (inbelic)

Changes

By marking dx.resource.getpointer as convergent, we are able to prevent unwanted code transforms that make resource access look illegal. Namely, this prevents the creation of a ptr into a resource handle to be moved throughout control flow (eg in SimplifyCFG), further preventing sink/hoist optimizations on the returned ptr during InstCombine.

Previously, these transforms were undone by the phiNodeReplacement function in dxil-resource-access. However, in general, we would like to follow a policy of preventing these transforms from occurring rather than having to undo the work. This change is aligned with this.

This is a pre-requisite to resolving #165288.


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

6 Files Affected:

  • (modified) llvm/include/llvm/IR/IntrinsicsDirectX.td (+1-1)
  • (modified) llvm/lib/Target/DirectX/DXILResourceAccess.cpp (+1-121)
  • (removed) llvm/test/CodeGen/DirectX/issue-152348.ll (-158)
  • (removed) llvm/test/CodeGen/DirectX/phi-node-replacement.ll (-42)
  • (added) llvm/test/Transforms/GVN/no-sink-dxgetpointer.ll (+100)
  • (added) llvm/test/Transforms/SimplifyCFG/DirectX/no-sink-dxgetpointer.ll (+131)
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index 5bf8a785d528c..79f1586d12ead 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -38,7 +38,7 @@ def int_dx_resource_handlefromimplicitbinding
 
 def int_dx_resource_getpointer
     : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_i32_ty],
-                            [IntrNoMem]>;
+                            [IntrConvergent, IntrNoMem]>;
 
 def int_dx_resource_nonuniformindex
     : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_i32_ty], [IntrNoMem]>;
diff --git a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
index 3d75d7455101f..50b657aab79fb 100644
--- a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
+++ b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
@@ -419,112 +419,6 @@ static void createLoadIntrinsic(IntrinsicInst *II, LoadInst *LI,
   llvm_unreachable("Unhandled case in switch");
 }
 
-static SmallVector<Instruction *> collectBlockUseDef(Instruction *Start) {
-  SmallPtrSet<Instruction *, 32> Visited;
-  SmallVector<Instruction *, 32> Worklist;
-  SmallVector<Instruction *> Out;
-  auto *BB = Start->getParent();
-
-  // Seed with direct users in this block.
-  for (User *U : Start->users()) {
-    if (auto *I = dyn_cast<Instruction>(U)) {
-      if (I->getParent() == BB)
-        Worklist.push_back(I);
-    }
-  }
-
-  // BFS over transitive users, constrained to the same block.
-  while (!Worklist.empty()) {
-    Instruction *I = Worklist.pop_back_val();
-    if (!Visited.insert(I).second)
-      continue;
-    Out.push_back(I);
-
-    for (User *U : I->users()) {
-      if (auto *J = dyn_cast<Instruction>(U)) {
-        if (J->getParent() == BB)
-          Worklist.push_back(J);
-      }
-    }
-    for (Use &V : I->operands()) {
-      if (auto *J = dyn_cast<Instruction>(V)) {
-        if (J->getParent() == BB && V != Start)
-          Worklist.push_back(J);
-      }
-    }
-  }
-
-  // Order results in program order.
-  DenseMap<const Instruction *, unsigned> Ord;
-  unsigned Idx = 0;
-  for (Instruction &I : *BB)
-    Ord[&I] = Idx++;
-
-  llvm::sort(Out, [&](Instruction *A, Instruction *B) {
-    return Ord.lookup(A) < Ord.lookup(B);
-  });
-
-  return Out;
-}
-
-static void phiNodeRemapHelper(PHINode *Phi, BasicBlock *BB,
-                               IRBuilder<> &Builder,
-                               SmallVector<Instruction *> &UsesInBlock) {
-
-  ValueToValueMapTy VMap;
-  Value *Val = Phi->getIncomingValueForBlock(BB);
-  VMap[Phi] = Val;
-  Builder.SetInsertPoint(&BB->back());
-  for (Instruction *I : UsesInBlock) {
-    // don't clone over the Phi just remap them
-    if (auto *PhiNested = dyn_cast<PHINode>(I)) {
-      VMap[PhiNested] = PhiNested->getIncomingValueForBlock(BB);
-      continue;
-    }
-    Instruction *Clone = I->clone();
-    RemapInstruction(Clone, VMap,
-                     RF_NoModuleLevelChanges | RF_IgnoreMissingLocals);
-    Builder.Insert(Clone);
-    VMap[I] = Clone;
-  }
-}
-
-static void phiNodeReplacement(IntrinsicInst *II,
-                               SmallVectorImpl<Instruction *> &PrevBBDeadInsts,
-                               SetVector<BasicBlock *> &DeadBB) {
-  SmallVector<Instruction *> CurrBBDeadInsts;
-  for (User *U : II->users()) {
-    auto *Phi = dyn_cast<PHINode>(U);
-    if (!Phi)
-      continue;
-
-    IRBuilder<> Builder(Phi);
-    SmallVector<Instruction *> UsesInBlock = collectBlockUseDef(Phi);
-    bool HasReturnUse = isa<ReturnInst>(UsesInBlock.back());
-
-    for (unsigned I = 0, E = Phi->getNumIncomingValues(); I < E; I++) {
-      auto *CurrIncomingBB = Phi->getIncomingBlock(I);
-      phiNodeRemapHelper(Phi, CurrIncomingBB, Builder, UsesInBlock);
-      if (HasReturnUse)
-        PrevBBDeadInsts.push_back(&CurrIncomingBB->back());
-    }
-
-    CurrBBDeadInsts.push_back(Phi);
-
-    for (Instruction *I : UsesInBlock) {
-      CurrBBDeadInsts.push_back(I);
-    }
-    if (HasReturnUse) {
-      BasicBlock *PhiBB = Phi->getParent();
-      DeadBB.insert(PhiBB);
-    }
-  }
-  // Traverse the now-dead instructions in RPO and remove them.
-  for (Instruction *Dead : llvm::reverse(CurrBBDeadInsts))
-    Dead->eraseFromParent();
-  CurrBBDeadInsts.clear();
-}
-
 static void replaceAccess(IntrinsicInst *II, dxil::ResourceTypeInfo &RTI) {
   SmallVector<User *> Worklist;
   for (User *U : II->users())
@@ -560,27 +454,13 @@ static void replaceAccess(IntrinsicInst *II, dxil::ResourceTypeInfo &RTI) {
 
 static bool transformResourcePointers(Function &F, DXILResourceTypeMap &DRTM) {
   SmallVector<std::pair<IntrinsicInst *, dxil::ResourceTypeInfo>> Resources;
-  SetVector<BasicBlock *> DeadBB;
-  SmallVector<Instruction *> PrevBBDeadInsts;
-  for (BasicBlock &BB : make_early_inc_range(F)) {
-    for (Instruction &I : make_early_inc_range(BB))
-      if (auto *II = dyn_cast<IntrinsicInst>(&I))
-        if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer)
-          phiNodeReplacement(II, PrevBBDeadInsts, DeadBB);
-
+  for (BasicBlock &BB : make_early_inc_range(F))
     for (Instruction &I : BB)
       if (auto *II = dyn_cast<IntrinsicInst>(&I))
         if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) {
           auto *HandleTy = cast<TargetExtType>(II->getArgOperand(0)->getType());
           Resources.emplace_back(II, DRTM[HandleTy]);
         }
-  }
-  for (auto *Dead : PrevBBDeadInsts)
-    Dead->eraseFromParent();
-  PrevBBDeadInsts.clear();
-  for (auto *Dead : DeadBB)
-    Dead->eraseFromParent();
-  DeadBB.clear();
 
   for (auto &[II, RI] : Resources)
     replaceAccess(II, RI);
diff --git a/llvm/test/CodeGen/DirectX/issue-152348.ll b/llvm/test/CodeGen/DirectX/issue-152348.ll
deleted file mode 100644
index aa0179d82b09e..0000000000000
--- a/llvm/test/CodeGen/DirectX/issue-152348.ll
+++ /dev/null
@@ -1,158 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -S -dxil-resource-type -dxil-resource-access -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
-
-; NOTE: The two LLVM IR functions below are simplified versions of this HLSL
-;      RWStructuredBuffer<float16_t> a;
-;      RWStructuredBuffer<float16_t> b;
-;      RWStructuredBuffer<float16_t> c;
-;      cbuffer d {
-;        uint e;
-;        uint f;
-;        uint g;
-;        uint h;
-;      }
-;      [numthreads(6, 8, 1)] void CSMain() {
-;        if (h) {
-;          float16_t i = b[f];
-;          c[g] = i;
-;        } else if(h == g) {
-;          float16_t i = b[g];
-;           c[h] = i;
-;        } else {
-;          float16_t i = a[e];
-;          c[g] = i;
-;        }
-;      }
-
-%__cblayout_d = type <{ i32, i32, i32, i32 }>
-
-@.str = internal unnamed_addr constant [2 x i8] c"a\00", align 1
-@d.cb = local_unnamed_addr global target("dx.CBuffer", target("dx.Layout", %__cblayout_d, 16, 0, 4, 8, 12)) poison
-@e = external hidden local_unnamed_addr addrspace(2) global i32, align 4
-@d.str = internal unnamed_addr constant [2 x i8] c"d\00", align 1
-
-define void @CSMain() local_unnamed_addr {
-; CHECK-LABEL: define void @CSMain() local_unnamed_addr {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[CALLRAWBUFFERBINDING:%.*]] = tail call target("dx.CBuffer", target("dx.Layout", [[__CBLAYOUT_D:%.*]], 16, 0, 4, 8, 12)) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_tdx.Layout_s___cblayout_ds_16_0_4_8_12tt(i32 3, i32 0, i32 1, i32 0, ptr nonnull @d.str)
-; CHECK-NEXT:    store target("dx.CBuffer", target("dx.Layout", [[__CBLAYOUT_D]], 16, 0, 4, 8, 12)) [[CALLRAWBUFFERBINDING]], ptr @d.cb, align 4
-; CHECK-NEXT:    [[LOADE:%.*]] = load i32, ptr addrspace(2) @e, align 4
-; CHECK-NEXT:    [[TOBOOL_NOT_I:%.*]] = icmp eq i32 [[LOADE]], 0
-; CHECK-NEXT:    br i1 [[TOBOOL_NOT_I]], label %[[IF_ELSE_I:.*]], label %[[IF_THEN_I:.*]]
-; CHECK:       [[IF_THEN_I]]:
-; CHECK-NEXT:    [[IFSTMTCALLRAWBUFFERBINDING:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP0:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP1:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[IFSTMTCALLRAWBUFFERBINDING]], i32 [[LOADE]], i32 0)
-; CHECK-NEXT:    [[TMP2:%.*]] = extractvalue { half, i1 } [[TMP1]], 0
-; CHECK-NEXT:    call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f16_1_0t.f16(target("dx.RawBuffer", half, 1, 0) [[TMP0]], i32 [[LOADE]], i32 0, half [[TMP2]])
-; CHECK-NEXT:    br label %[[_Z6CSMAINV_EXIT:.*]]
-; CHECK:       [[IF_ELSE_I]]:
-; CHECK-NEXT:    [[CALL2NDRAWBUFFERBINDING:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP3:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP4:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[CALL2NDRAWBUFFERBINDING]], i32 [[LOADE]], i32 0)
-; CHECK-NEXT:    [[TMP5:%.*]] = extractvalue { half, i1 } [[TMP4]], 0
-; CHECK-NEXT:    call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f16_1_0t.f16(target("dx.RawBuffer", half, 1, 0) [[TMP3]], i32 [[LOADE]], i32 0, half [[TMP5]])
-; CHECK-NEXT:    br label %[[_Z6CSMAINV_EXIT]]
-; CHECK:       [[_Z6CSMAINV_EXIT]]:
-; CHECK-NEXT:    ret void
-;
-entry:
-  %callCBufferBinding = tail call target("dx.CBuffer", target("dx.Layout", %__cblayout_d, 16, 0, 4, 8, 12)) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_tdx.Layout_s___cblayout_ds_16_0_4_8_12tt(i32 3, i32 0, i32 1, i32 0, ptr nonnull @d.str)
-  store target("dx.CBuffer", target("dx.Layout", %__cblayout_d, 16, 0, 4, 8, 12)) %callCBufferBinding, ptr @d.cb, align 4
-  %loadE = load i32, ptr addrspace(2) @e, align 4
-  %tobool.not.i = icmp eq i32 %loadE, 0
-  br i1 %tobool.not.i, label %if.else.i, label %if.then.i
-
-if.then.i:                                        ; preds = %entry
-  %ifStmtcallRawBufferBinding = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %ifStmtCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %ifStmtcallRawBufferBinding, i32 %loadE)
-  br label %_Z6CSMainv.exit
-
-if.else.i:                                        ; preds = %entry
-  %call2ndRawBufferBinding = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %elseStmtCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %call2ndRawBufferBinding, i32 %loadE)
-  br label %_Z6CSMainv.exit
-
-_Z6CSMainv.exit:                                  ; preds = %if.else.i, %if.then.i
-  %.sink1 = phi ptr [ %ifStmtCallResourceGEP, %if.then.i ], [ %elseStmtCallResourceGEP, %if.else.i ]
-  %call3rdRawBufferBinding = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %sinkCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %call3rdRawBufferBinding, i32 %loadE)
-  %loadSink = load half, ptr %.sink1, align 2
-  store half %loadSink, ptr %sinkCallResourceGEP, align 2
-  ret void
-}
-
-define void @Main() local_unnamed_addr  {
-; CHECK-LABEL: define void @Main() local_unnamed_addr {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[CALLRAWBUFFERBINDING1:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[CALLRAWBUFFERBINDING0:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[CALLRAWBUFFERBINDING:%.*]] = tail call target("dx.CBuffer", target("dx.Layout", [[__CBLAYOUT_D:%.*]], 16, 0, 4, 8, 12)) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_tdx.Layout_s___cblayout_ds_16_0_4_8_12tt(i32 3, i32 0, i32 1, i32 0, ptr nonnull @d.str)
-; CHECK-NEXT:    store target("dx.CBuffer", target("dx.Layout", [[__CBLAYOUT_D]], 16, 0, 4, 8, 12)) [[CALLRAWBUFFERBINDING]], ptr @d.cb, align 4
-; CHECK-NEXT:    [[LOADE:%.*]] = load i32, ptr addrspace(2) @e, align 4
-; CHECK-NEXT:    [[TOBOOL_NOT_I:%.*]] = icmp eq i32 [[LOADE]], 0
-; CHECK-NEXT:    br i1 [[TOBOOL_NOT_I]], label %[[IF_ELSE_I:.*]], label %[[IF_THEN_I:.*]]
-; CHECK:       [[IF_THEN_I]]:
-; CHECK-NEXT:    [[IFSTMTLOADE:%.*]] = load i32, ptr addrspace(2) @e, align 4
-; CHECK-NEXT:    [[TMP0:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP1:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[CALLRAWBUFFERBINDING0]], i32 [[LOADE]], i32 0)
-; CHECK-NEXT:    [[TMP2:%.*]] = extractvalue { half, i1 } [[TMP1]], 0
-; CHECK-NEXT:    call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f16_1_0t.f16(target("dx.RawBuffer", half, 1, 0) [[TMP0]], i32 [[IFSTMTLOADE]], i32 0, half [[TMP2]])
-; CHECK-NEXT:    br label %[[_Z6MAINV_EXIT:.*]]
-; CHECK:       [[IF_ELSE_I]]:
-; CHECK-NEXT:    [[ELSESTMTLOADE:%.*]] = load i32, ptr addrspace(2) @e, align 4
-; CHECK-NEXT:    [[CMP_I:%.*]] = icmp eq i32 [[ELSESTMTLOADE]], 0
-; CHECK-NEXT:    br i1 [[CMP_I]], label %[[IF_THEN2_I:.*]], label %[[IF_ELSE6_I:.*]]
-; CHECK:       [[IF_THEN2_I]]:
-; CHECK-NEXT:    [[TMP3:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP4:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[CALLRAWBUFFERBINDING0]], i32 0, i32 0)
-; CHECK-NEXT:    [[TMP5:%.*]] = extractvalue { half, i1 } [[TMP4]], 0
-; CHECK-NEXT:    call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f16_1_0t.f16(target("dx.RawBuffer", half, 1, 0) [[TMP3]], i32 0, i32 0, half [[TMP5]])
-; CHECK-NEXT:    br label %[[_Z6MAINV_EXIT]]
-; CHECK:       [[IF_ELSE6_I]]:
-; CHECK-NEXT:    [[ELSESTMTLOADE2:%.*]] = load i32, ptr addrspace(2) @e, align 4
-; CHECK-NEXT:    [[TMP6:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP7:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[CALLRAWBUFFERBINDING1]], i32 [[ELSESTMTLOADE2]], i32 0)
-; CHECK-NEXT:    [[TMP8:%.*]] = extractvalue { half, i1 } [[TMP7]], 0
-; CHECK-NEXT:    call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f16_1_0t.f16(target("dx.RawBuffer", half, 1, 0) [[TMP6]], i32 [[ELSESTMTLOADE]], i32 0, half [[TMP8]])
-; CHECK-NEXT:    br label %[[_Z6MAINV_EXIT]]
-; CHECK:       [[_Z6MAINV_EXIT]]:
-; CHECK-NEXT:    ret void
-;
-entry:
-  %callRawBufferBinding1 = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %callRawBufferBinding0 = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %callCBufferBinding = tail call target("dx.CBuffer", target("dx.Layout", %__cblayout_d, 16, 0, 4, 8, 12)) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_tdx.Layout_s___cblayout_ds_16_0_4_8_12tt(i32 3, i32 0, i32 1, i32 0, ptr nonnull @d.str)
-  store target("dx.CBuffer", target("dx.Layout", %__cblayout_d, 16, 0, 4, 8, 12)) %callCBufferBinding, ptr @d.cb, align 4
-  %loadE = load i32, ptr addrspace(2) @e, align 4
-  %tobool.not.i = icmp eq i32 %loadE, 0
-  br i1 %tobool.not.i, label %if.else.i, label %if.then.i
-
-if.then.i:                                        ; preds = %entry
-  %ifStmtCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %callRawBufferBinding0, i32 %loadE)
-  %ifStmtLoadE = load i32, ptr addrspace(2) @e, align 4
-  br label %_Z6Mainv.exit
-
-if.else.i:                                        ; preds = %entry
-  %elseStmtLoadE = load i32, ptr addrspace(2) @e, align 4
-  %cmp.i = icmp eq i32 %elseStmtLoadE, 0
-  br i1 %cmp.i, label %if.then2.i, label %if.else6.i
-
-if.then2.i:                                       ; preds = %if.else.i
-  %elseifStmtCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %callRawBufferBinding0, i32 0)
-  br label %_Z6Mainv.exit
-
-if.else6.i:                                       ; preds = %if.else.i
-  %elseStmtLoadE2 = load i32, ptr addrspace(2) @e, align 4
-  %elseStmtCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %callRawBufferBinding1, i32 %elseStmtLoadE2)
-  br label %_Z6Mainv.exit
-
-_Z6Mainv.exit:                                    ; preds = %if.else6.i, %if.then2.i, %if.then.i
-  %.sink2 = phi i32 [ %ifStmtLoadE, %if.then.i ], [ 0, %if.then2.i ], [ %elseStmtLoadE, %if.else6.i ]
-  %.sink.in = phi ptr [ %ifStmtCallResourceGEP, %if.then.i ], [ %elseifStmtCallResourceGEP, %if.then2.i ], [ %elseStmtCallResourceGEP, %if.else6.i ]
-  %callRawBufferBindingSink = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %.sink = load half, ptr %.sink.in, align 2
-  %i11 = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %callRawBufferBindingSink, i32 %.sink2)
-  store half %.sink, ptr %i11, align 2
-  ret void
-}
diff --git a/llvm/test/CodeGen/DirectX/phi-node-replacement.ll b/llvm/test/CodeGen/DirectX/phi-node-replacement.ll
deleted file mode 100644
index 6aef126cb5ecd..0000000000000
--- a/llvm/test/CodeGen/DirectX/phi-node-replacement.ll
+++ /dev/null
@@ -1,42 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -S -dxil-resource-type -dxil-resource-access -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
-
-%"$Globals" = type { i32 }
-@CBV = external constant %"$Globals"
-@.str = internal unnamed_addr constant [2 x i8] c"a\00", align 1
-
-define half @CSMain() local_unnamed_addr {
-; CHECK-LABEL: define half @CSMain() local_unnamed_addr {
-; CHECK-NEXT:    [[LOADGLOBAL:%.*]] = load i32, ptr @CBV, align 4
-; CHECK-NEXT:    [[TOBOOL_NOT_I:%.*]] = icmp eq i32 [[LOADGLOBAL]], 0
-; CHECK-NEXT:    br i1 [[TOBOOL_NOT_I]], label %[[IF_ELSE_I:.*]], label %[[IF_THEN_I:.*]]
-; CHECK:       [[IF_THEN_I]]:
-; CHECK-NEXT:    [[IFSTMTCALLRAWBUFFERBINDING:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP1:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[IFSTMTCALLRAWBUFFERBINDING]], i32 [[LOADGLOBAL]], i32 0)
-; CHECK-NEXT:    [[TMP2:%.*]] = extractvalue { half, i1 } [[TMP1]], 0
-; CHECK-NEXT:    ret half [[TMP2]]
-; CHECK:  ...
[truncated]

@llvmbot

llvmbot commented Feb 18, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-llvm-ir

Author: Finn Plummer (inbelic)

Changes

By marking dx.resource.getpointer as convergent, we are able to prevent unwanted code transforms that make resource access look illegal. Namely, this prevents the creation of a ptr into a resource handle to be moved throughout control flow (eg in SimplifyCFG), further preventing sink/hoist optimizations on the returned ptr during InstCombine.

Previously, these transforms were undone by the phiNodeReplacement function in dxil-resource-access. However, in general, we would like to follow a policy of preventing these transforms from occurring rather than having to undo the work. This change is aligned with this.

This is a pre-requisite to resolving #165288.


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

6 Files Affected:

  • (modified) llvm/include/llvm/IR/IntrinsicsDirectX.td (+1-1)
  • (modified) llvm/lib/Target/DirectX/DXILResourceAccess.cpp (+1-121)
  • (removed) llvm/test/CodeGen/DirectX/issue-152348.ll (-158)
  • (removed) llvm/test/CodeGen/DirectX/phi-node-replacement.ll (-42)
  • (added) llvm/test/Transforms/GVN/no-sink-dxgetpointer.ll (+100)
  • (added) llvm/test/Transforms/SimplifyCFG/DirectX/no-sink-dxgetpointer.ll (+131)
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index 5bf8a785d528c..79f1586d12ead 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -38,7 +38,7 @@ def int_dx_resource_handlefromimplicitbinding
 
 def int_dx_resource_getpointer
     : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_i32_ty],
-                            [IntrNoMem]>;
+                            [IntrConvergent, IntrNoMem]>;
 
 def int_dx_resource_nonuniformindex
     : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_i32_ty], [IntrNoMem]>;
diff --git a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
index 3d75d7455101f..50b657aab79fb 100644
--- a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
+++ b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
@@ -419,112 +419,6 @@ static void createLoadIntrinsic(IntrinsicInst *II, LoadInst *LI,
   llvm_unreachable("Unhandled case in switch");
 }
 
-static SmallVector<Instruction *> collectBlockUseDef(Instruction *Start) {
-  SmallPtrSet<Instruction *, 32> Visited;
-  SmallVector<Instruction *, 32> Worklist;
-  SmallVector<Instruction *> Out;
-  auto *BB = Start->getParent();
-
-  // Seed with direct users in this block.
-  for (User *U : Start->users()) {
-    if (auto *I = dyn_cast<Instruction>(U)) {
-      if (I->getParent() == BB)
-        Worklist.push_back(I);
-    }
-  }
-
-  // BFS over transitive users, constrained to the same block.
-  while (!Worklist.empty()) {
-    Instruction *I = Worklist.pop_back_val();
-    if (!Visited.insert(I).second)
-      continue;
-    Out.push_back(I);
-
-    for (User *U : I->users()) {
-      if (auto *J = dyn_cast<Instruction>(U)) {
-        if (J->getParent() == BB)
-          Worklist.push_back(J);
-      }
-    }
-    for (Use &V : I->operands()) {
-      if (auto *J = dyn_cast<Instruction>(V)) {
-        if (J->getParent() == BB && V != Start)
-          Worklist.push_back(J);
-      }
-    }
-  }
-
-  // Order results in program order.
-  DenseMap<const Instruction *, unsigned> Ord;
-  unsigned Idx = 0;
-  for (Instruction &I : *BB)
-    Ord[&I] = Idx++;
-
-  llvm::sort(Out, [&](Instruction *A, Instruction *B) {
-    return Ord.lookup(A) < Ord.lookup(B);
-  });
-
-  return Out;
-}
-
-static void phiNodeRemapHelper(PHINode *Phi, BasicBlock *BB,
-                               IRBuilder<> &Builder,
-                               SmallVector<Instruction *> &UsesInBlock) {
-
-  ValueToValueMapTy VMap;
-  Value *Val = Phi->getIncomingValueForBlock(BB);
-  VMap[Phi] = Val;
-  Builder.SetInsertPoint(&BB->back());
-  for (Instruction *I : UsesInBlock) {
-    // don't clone over the Phi just remap them
-    if (auto *PhiNested = dyn_cast<PHINode>(I)) {
-      VMap[PhiNested] = PhiNested->getIncomingValueForBlock(BB);
-      continue;
-    }
-    Instruction *Clone = I->clone();
-    RemapInstruction(Clone, VMap,
-                     RF_NoModuleLevelChanges | RF_IgnoreMissingLocals);
-    Builder.Insert(Clone);
-    VMap[I] = Clone;
-  }
-}
-
-static void phiNodeReplacement(IntrinsicInst *II,
-                               SmallVectorImpl<Instruction *> &PrevBBDeadInsts,
-                               SetVector<BasicBlock *> &DeadBB) {
-  SmallVector<Instruction *> CurrBBDeadInsts;
-  for (User *U : II->users()) {
-    auto *Phi = dyn_cast<PHINode>(U);
-    if (!Phi)
-      continue;
-
-    IRBuilder<> Builder(Phi);
-    SmallVector<Instruction *> UsesInBlock = collectBlockUseDef(Phi);
-    bool HasReturnUse = isa<ReturnInst>(UsesInBlock.back());
-
-    for (unsigned I = 0, E = Phi->getNumIncomingValues(); I < E; I++) {
-      auto *CurrIncomingBB = Phi->getIncomingBlock(I);
-      phiNodeRemapHelper(Phi, CurrIncomingBB, Builder, UsesInBlock);
-      if (HasReturnUse)
-        PrevBBDeadInsts.push_back(&CurrIncomingBB->back());
-    }
-
-    CurrBBDeadInsts.push_back(Phi);
-
-    for (Instruction *I : UsesInBlock) {
-      CurrBBDeadInsts.push_back(I);
-    }
-    if (HasReturnUse) {
-      BasicBlock *PhiBB = Phi->getParent();
-      DeadBB.insert(PhiBB);
-    }
-  }
-  // Traverse the now-dead instructions in RPO and remove them.
-  for (Instruction *Dead : llvm::reverse(CurrBBDeadInsts))
-    Dead->eraseFromParent();
-  CurrBBDeadInsts.clear();
-}
-
 static void replaceAccess(IntrinsicInst *II, dxil::ResourceTypeInfo &RTI) {
   SmallVector<User *> Worklist;
   for (User *U : II->users())
@@ -560,27 +454,13 @@ static void replaceAccess(IntrinsicInst *II, dxil::ResourceTypeInfo &RTI) {
 
 static bool transformResourcePointers(Function &F, DXILResourceTypeMap &DRTM) {
   SmallVector<std::pair<IntrinsicInst *, dxil::ResourceTypeInfo>> Resources;
-  SetVector<BasicBlock *> DeadBB;
-  SmallVector<Instruction *> PrevBBDeadInsts;
-  for (BasicBlock &BB : make_early_inc_range(F)) {
-    for (Instruction &I : make_early_inc_range(BB))
-      if (auto *II = dyn_cast<IntrinsicInst>(&I))
-        if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer)
-          phiNodeReplacement(II, PrevBBDeadInsts, DeadBB);
-
+  for (BasicBlock &BB : make_early_inc_range(F))
     for (Instruction &I : BB)
       if (auto *II = dyn_cast<IntrinsicInst>(&I))
         if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) {
           auto *HandleTy = cast<TargetExtType>(II->getArgOperand(0)->getType());
           Resources.emplace_back(II, DRTM[HandleTy]);
         }
-  }
-  for (auto *Dead : PrevBBDeadInsts)
-    Dead->eraseFromParent();
-  PrevBBDeadInsts.clear();
-  for (auto *Dead : DeadBB)
-    Dead->eraseFromParent();
-  DeadBB.clear();
 
   for (auto &[II, RI] : Resources)
     replaceAccess(II, RI);
diff --git a/llvm/test/CodeGen/DirectX/issue-152348.ll b/llvm/test/CodeGen/DirectX/issue-152348.ll
deleted file mode 100644
index aa0179d82b09e..0000000000000
--- a/llvm/test/CodeGen/DirectX/issue-152348.ll
+++ /dev/null
@@ -1,158 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -S -dxil-resource-type -dxil-resource-access -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
-
-; NOTE: The two LLVM IR functions below are simplified versions of this HLSL
-;      RWStructuredBuffer<float16_t> a;
-;      RWStructuredBuffer<float16_t> b;
-;      RWStructuredBuffer<float16_t> c;
-;      cbuffer d {
-;        uint e;
-;        uint f;
-;        uint g;
-;        uint h;
-;      }
-;      [numthreads(6, 8, 1)] void CSMain() {
-;        if (h) {
-;          float16_t i = b[f];
-;          c[g] = i;
-;        } else if(h == g) {
-;          float16_t i = b[g];
-;           c[h] = i;
-;        } else {
-;          float16_t i = a[e];
-;          c[g] = i;
-;        }
-;      }
-
-%__cblayout_d = type <{ i32, i32, i32, i32 }>
-
-@.str = internal unnamed_addr constant [2 x i8] c"a\00", align 1
-@d.cb = local_unnamed_addr global target("dx.CBuffer", target("dx.Layout", %__cblayout_d, 16, 0, 4, 8, 12)) poison
-@e = external hidden local_unnamed_addr addrspace(2) global i32, align 4
-@d.str = internal unnamed_addr constant [2 x i8] c"d\00", align 1
-
-define void @CSMain() local_unnamed_addr {
-; CHECK-LABEL: define void @CSMain() local_unnamed_addr {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[CALLRAWBUFFERBINDING:%.*]] = tail call target("dx.CBuffer", target("dx.Layout", [[__CBLAYOUT_D:%.*]], 16, 0, 4, 8, 12)) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_tdx.Layout_s___cblayout_ds_16_0_4_8_12tt(i32 3, i32 0, i32 1, i32 0, ptr nonnull @d.str)
-; CHECK-NEXT:    store target("dx.CBuffer", target("dx.Layout", [[__CBLAYOUT_D]], 16, 0, 4, 8, 12)) [[CALLRAWBUFFERBINDING]], ptr @d.cb, align 4
-; CHECK-NEXT:    [[LOADE:%.*]] = load i32, ptr addrspace(2) @e, align 4
-; CHECK-NEXT:    [[TOBOOL_NOT_I:%.*]] = icmp eq i32 [[LOADE]], 0
-; CHECK-NEXT:    br i1 [[TOBOOL_NOT_I]], label %[[IF_ELSE_I:.*]], label %[[IF_THEN_I:.*]]
-; CHECK:       [[IF_THEN_I]]:
-; CHECK-NEXT:    [[IFSTMTCALLRAWBUFFERBINDING:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP0:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP1:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[IFSTMTCALLRAWBUFFERBINDING]], i32 [[LOADE]], i32 0)
-; CHECK-NEXT:    [[TMP2:%.*]] = extractvalue { half, i1 } [[TMP1]], 0
-; CHECK-NEXT:    call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f16_1_0t.f16(target("dx.RawBuffer", half, 1, 0) [[TMP0]], i32 [[LOADE]], i32 0, half [[TMP2]])
-; CHECK-NEXT:    br label %[[_Z6CSMAINV_EXIT:.*]]
-; CHECK:       [[IF_ELSE_I]]:
-; CHECK-NEXT:    [[CALL2NDRAWBUFFERBINDING:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP3:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP4:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[CALL2NDRAWBUFFERBINDING]], i32 [[LOADE]], i32 0)
-; CHECK-NEXT:    [[TMP5:%.*]] = extractvalue { half, i1 } [[TMP4]], 0
-; CHECK-NEXT:    call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f16_1_0t.f16(target("dx.RawBuffer", half, 1, 0) [[TMP3]], i32 [[LOADE]], i32 0, half [[TMP5]])
-; CHECK-NEXT:    br label %[[_Z6CSMAINV_EXIT]]
-; CHECK:       [[_Z6CSMAINV_EXIT]]:
-; CHECK-NEXT:    ret void
-;
-entry:
-  %callCBufferBinding = tail call target("dx.CBuffer", target("dx.Layout", %__cblayout_d, 16, 0, 4, 8, 12)) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_tdx.Layout_s___cblayout_ds_16_0_4_8_12tt(i32 3, i32 0, i32 1, i32 0, ptr nonnull @d.str)
-  store target("dx.CBuffer", target("dx.Layout", %__cblayout_d, 16, 0, 4, 8, 12)) %callCBufferBinding, ptr @d.cb, align 4
-  %loadE = load i32, ptr addrspace(2) @e, align 4
-  %tobool.not.i = icmp eq i32 %loadE, 0
-  br i1 %tobool.not.i, label %if.else.i, label %if.then.i
-
-if.then.i:                                        ; preds = %entry
-  %ifStmtcallRawBufferBinding = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %ifStmtCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %ifStmtcallRawBufferBinding, i32 %loadE)
-  br label %_Z6CSMainv.exit
-
-if.else.i:                                        ; preds = %entry
-  %call2ndRawBufferBinding = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %elseStmtCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %call2ndRawBufferBinding, i32 %loadE)
-  br label %_Z6CSMainv.exit
-
-_Z6CSMainv.exit:                                  ; preds = %if.else.i, %if.then.i
-  %.sink1 = phi ptr [ %ifStmtCallResourceGEP, %if.then.i ], [ %elseStmtCallResourceGEP, %if.else.i ]
-  %call3rdRawBufferBinding = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %sinkCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %call3rdRawBufferBinding, i32 %loadE)
-  %loadSink = load half, ptr %.sink1, align 2
-  store half %loadSink, ptr %sinkCallResourceGEP, align 2
-  ret void
-}
-
-define void @Main() local_unnamed_addr  {
-; CHECK-LABEL: define void @Main() local_unnamed_addr {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[CALLRAWBUFFERBINDING1:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[CALLRAWBUFFERBINDING0:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[CALLRAWBUFFERBINDING:%.*]] = tail call target("dx.CBuffer", target("dx.Layout", [[__CBLAYOUT_D:%.*]], 16, 0, 4, 8, 12)) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_tdx.Layout_s___cblayout_ds_16_0_4_8_12tt(i32 3, i32 0, i32 1, i32 0, ptr nonnull @d.str)
-; CHECK-NEXT:    store target("dx.CBuffer", target("dx.Layout", [[__CBLAYOUT_D]], 16, 0, 4, 8, 12)) [[CALLRAWBUFFERBINDING]], ptr @d.cb, align 4
-; CHECK-NEXT:    [[LOADE:%.*]] = load i32, ptr addrspace(2) @e, align 4
-; CHECK-NEXT:    [[TOBOOL_NOT_I:%.*]] = icmp eq i32 [[LOADE]], 0
-; CHECK-NEXT:    br i1 [[TOBOOL_NOT_I]], label %[[IF_ELSE_I:.*]], label %[[IF_THEN_I:.*]]
-; CHECK:       [[IF_THEN_I]]:
-; CHECK-NEXT:    [[IFSTMTLOADE:%.*]] = load i32, ptr addrspace(2) @e, align 4
-; CHECK-NEXT:    [[TMP0:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP1:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[CALLRAWBUFFERBINDING0]], i32 [[LOADE]], i32 0)
-; CHECK-NEXT:    [[TMP2:%.*]] = extractvalue { half, i1 } [[TMP1]], 0
-; CHECK-NEXT:    call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f16_1_0t.f16(target("dx.RawBuffer", half, 1, 0) [[TMP0]], i32 [[IFSTMTLOADE]], i32 0, half [[TMP2]])
-; CHECK-NEXT:    br label %[[_Z6MAINV_EXIT:.*]]
-; CHECK:       [[IF_ELSE_I]]:
-; CHECK-NEXT:    [[ELSESTMTLOADE:%.*]] = load i32, ptr addrspace(2) @e, align 4
-; CHECK-NEXT:    [[CMP_I:%.*]] = icmp eq i32 [[ELSESTMTLOADE]], 0
-; CHECK-NEXT:    br i1 [[CMP_I]], label %[[IF_THEN2_I:.*]], label %[[IF_ELSE6_I:.*]]
-; CHECK:       [[IF_THEN2_I]]:
-; CHECK-NEXT:    [[TMP3:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP4:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[CALLRAWBUFFERBINDING0]], i32 0, i32 0)
-; CHECK-NEXT:    [[TMP5:%.*]] = extractvalue { half, i1 } [[TMP4]], 0
-; CHECK-NEXT:    call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f16_1_0t.f16(target("dx.RawBuffer", half, 1, 0) [[TMP3]], i32 0, i32 0, half [[TMP5]])
-; CHECK-NEXT:    br label %[[_Z6MAINV_EXIT]]
-; CHECK:       [[IF_ELSE6_I]]:
-; CHECK-NEXT:    [[ELSESTMTLOADE2:%.*]] = load i32, ptr addrspace(2) @e, align 4
-; CHECK-NEXT:    [[TMP6:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP7:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[CALLRAWBUFFERBINDING1]], i32 [[ELSESTMTLOADE2]], i32 0)
-; CHECK-NEXT:    [[TMP8:%.*]] = extractvalue { half, i1 } [[TMP7]], 0
-; CHECK-NEXT:    call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f16_1_0t.f16(target("dx.RawBuffer", half, 1, 0) [[TMP6]], i32 [[ELSESTMTLOADE]], i32 0, half [[TMP8]])
-; CHECK-NEXT:    br label %[[_Z6MAINV_EXIT]]
-; CHECK:       [[_Z6MAINV_EXIT]]:
-; CHECK-NEXT:    ret void
-;
-entry:
-  %callRawBufferBinding1 = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %callRawBufferBinding0 = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %callCBufferBinding = tail call target("dx.CBuffer", target("dx.Layout", %__cblayout_d, 16, 0, 4, 8, 12)) @llvm.dx.resource.handlefromimplicitbinding.tdx.CBuffer_tdx.Layout_s___cblayout_ds_16_0_4_8_12tt(i32 3, i32 0, i32 1, i32 0, ptr nonnull @d.str)
-  store target("dx.CBuffer", target("dx.Layout", %__cblayout_d, 16, 0, 4, 8, 12)) %callCBufferBinding, ptr @d.cb, align 4
-  %loadE = load i32, ptr addrspace(2) @e, align 4
-  %tobool.not.i = icmp eq i32 %loadE, 0
-  br i1 %tobool.not.i, label %if.else.i, label %if.then.i
-
-if.then.i:                                        ; preds = %entry
-  %ifStmtCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %callRawBufferBinding0, i32 %loadE)
-  %ifStmtLoadE = load i32, ptr addrspace(2) @e, align 4
-  br label %_Z6Mainv.exit
-
-if.else.i:                                        ; preds = %entry
-  %elseStmtLoadE = load i32, ptr addrspace(2) @e, align 4
-  %cmp.i = icmp eq i32 %elseStmtLoadE, 0
-  br i1 %cmp.i, label %if.then2.i, label %if.else6.i
-
-if.then2.i:                                       ; preds = %if.else.i
-  %elseifStmtCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %callRawBufferBinding0, i32 0)
-  br label %_Z6Mainv.exit
-
-if.else6.i:                                       ; preds = %if.else.i
-  %elseStmtLoadE2 = load i32, ptr addrspace(2) @e, align 4
-  %elseStmtCallResourceGEP = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %callRawBufferBinding1, i32 %elseStmtLoadE2)
-  br label %_Z6Mainv.exit
-
-_Z6Mainv.exit:                                    ; preds = %if.else6.i, %if.then2.i, %if.then.i
-  %.sink2 = phi i32 [ %ifStmtLoadE, %if.then.i ], [ 0, %if.then2.i ], [ %elseStmtLoadE, %if.else6.i ]
-  %.sink.in = phi ptr [ %ifStmtCallResourceGEP, %if.then.i ], [ %elseifStmtCallResourceGEP, %if.then2.i ], [ %elseStmtCallResourceGEP, %if.else6.i ]
-  %callRawBufferBindingSink = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str)
-  %.sink = load half, ptr %.sink.in, align 2
-  %i11 = tail call noundef nonnull align 2 dereferenceable(2) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) %callRawBufferBindingSink, i32 %.sink2)
-  store half %.sink, ptr %i11, align 2
-  ret void
-}
diff --git a/llvm/test/CodeGen/DirectX/phi-node-replacement.ll b/llvm/test/CodeGen/DirectX/phi-node-replacement.ll
deleted file mode 100644
index 6aef126cb5ecd..0000000000000
--- a/llvm/test/CodeGen/DirectX/phi-node-replacement.ll
+++ /dev/null
@@ -1,42 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -S -dxil-resource-type -dxil-resource-access -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
-
-%"$Globals" = type { i32 }
-@CBV = external constant %"$Globals"
-@.str = internal unnamed_addr constant [2 x i8] c"a\00", align 1
-
-define half @CSMain() local_unnamed_addr {
-; CHECK-LABEL: define half @CSMain() local_unnamed_addr {
-; CHECK-NEXT:    [[LOADGLOBAL:%.*]] = load i32, ptr @CBV, align 4
-; CHECK-NEXT:    [[TOBOOL_NOT_I:%.*]] = icmp eq i32 [[LOADGLOBAL]], 0
-; CHECK-NEXT:    br i1 [[TOBOOL_NOT_I]], label %[[IF_ELSE_I:.*]], label %[[IF_THEN_I:.*]]
-; CHECK:       [[IF_THEN_I]]:
-; CHECK-NEXT:    [[IFSTMTCALLRAWBUFFERBINDING:%.*]] = tail call target("dx.RawBuffer", half, 1, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f16_1_0t(i32 1, i32 0, i32 1, i32 0, ptr nonnull @.str)
-; CHECK-NEXT:    [[TMP1:%.*]] = call { half, i1 } @llvm.dx.resource.load.rawbuffer.f16.tdx.RawBuffer_f16_1_0t(target("dx.RawBuffer", half, 1, 0) [[IFSTMTCALLRAWBUFFERBINDING]], i32 [[LOADGLOBAL]], i32 0)
-; CHECK-NEXT:    [[TMP2:%.*]] = extractvalue { half, i1 } [[TMP1]], 0
-; CHECK-NEXT:    ret half [[TMP2]]
-; CHECK:  ...
[truncated]

@bogner bogner 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.

Generally looks great. Love to see an all-deletion PR.

Two small things:

  • For the title, I don't think [DX] is used frequently and might be unclear. Better to use [DirectX]
  • It's probably worth running opt -instnamer or opt -metarenamer (or however you spell those with the new PM) so that all of the instructions are named rather than numbered. This can make things much easier to update if someone needs to do it by hand.

@inbelic inbelic changed the title [DX] Mark dx.resource.getpointer as convergent [DirectX] Mark dx.resource.getpointer as convergent Feb 19, 2026
@inbelic inbelic merged commit fa1cda8 into llvm:main Feb 20, 2026
11 checks passed
@inbelic inbelic deleted the inbelic/getptr-conv branch March 3, 2026 22:43
@inbelic

inbelic commented Mar 5, 2026

Copy link
Copy Markdown
Contributor Author

@s-perron: from the meeting today, this is the pr used to prevent the optimization passes from generating a phi in the gvn/instcombine passes

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.

4 participants