[clang] 5726f6e - [DirectX][SPIRV] Consolidate the use of resource pointers across backends (#205433)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 30 12:54:57 PDT 2026
Author: Finn Plummer
Date: 2026-06-30T12:54:51-07:00
New Revision: 5726f6ee8140fa49d5cd6bc961e6a404f6bb1873
URL: https://github.com/llvm/llvm-project/commit/5726f6ee8140fa49d5cd6bc961e6a404f6bb1873
DIFF: https://github.com/llvm/llvm-project/commit/5726f6ee8140fa49d5cd6bc961e6a404f6bb1873.diff
LOG: [DirectX][SPIRV] Consolidate the use of resource pointers across backends (#205433)
This pr consolidates how the hlsl backends represent ptr/handles of
resources, and counters for spir-v, to ensure the instructions do not
get optimized into illegal looking phi nodes of resource accesses.
This was initially addressed for directx in
https://github.com/llvm/llvm-project/pull/182099, however, equivalent
behaviour was not added to spir-v. It is the case that the spir-v
backend also crashed due to similar illegal looking instructions, as
reported by https://github.com/llvm/llvm-project/issues/162841.
By marking the spirv instructions as convergent it follows the precedent
to prevent the problematic transforms rather than undo it in the
backends. This prevents it from moving through code-flow in sink/hoist
optimizations.
https://github.com/llvm/llvm-project/pull/193593 attempted to remove
some of the restrictions of convergent but failed to account for the
case when the resource is written to instead of read from and introduced
a regression, as reported by
https://github.com/llvm/llvm-project/issues/201901.
As such, we revert the previous commit and mark each of these intrinsics
as convergent.
Testing for these cases are added.
Resolves: https://github.com/llvm/llvm-project/issues/201901 and
https://github.com/llvm/llvm-project/issues/162841.
Assisted by: Claude Opus 4.8
Added:
llvm/test/Transforms/GVN/no-sink-dxgetbasepointer.ll
llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll
llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll
llvm/test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll
llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll
Modified:
clang/lib/CodeGen/CGHLSLBuiltins.cpp
clang/test/CodeGenHLSL/resources/StructuredBuffers-constructors.hlsl
clang/test/CodeGenHLSL/resources/StructuredBuffers-subscripts.hlsl
llvm/include/llvm/IR/IntrinsicsDirectX.td
llvm/include/llvm/IR/IntrinsicsSPIRV.td
llvm/test/Transforms/GVN/no-sink-dxgetpointer.ll
llvm/test/Transforms/SimplifyCFG/DirectX/no-sink-dxgetpointer.ll
Removed:
llvm/test/Transforms/DirectX/getpointer-sink-behavior.ll
################################################################################
diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
index 20a2119e28ce1..ebf2c0cd5ed16 100644
--- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp
+++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
@@ -950,7 +950,8 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
llvm::Intrinsic::ID IntrinsicID =
llvm::Intrinsic::spv_resource_counterhandlefromimplicitbinding;
SmallVector<Value *> Args{MainHandle, OrderID, SpaceOp};
- return Builder.CreateIntrinsic(HandleTy, IntrinsicID, Args);
+ return EmitIntrinsicCall(IntrinsicID, {HandleTy, MainHandle->getType()},
+ Args);
}
case Builtin::BI__builtin_hlsl_resource_nonuniformindex: {
Value *IndexOp = EmitScalarExpr(E->getArg(0));
diff --git a/clang/test/CodeGenHLSL/resources/StructuredBuffers-constructors.hlsl b/clang/test/CodeGenHLSL/resources/StructuredBuffers-constructors.hlsl
index 96c2d950b50d7..cd0d2ec0c4c2a 100644
--- a/clang/test/CodeGenHLSL/resources/StructuredBuffers-constructors.hlsl
+++ b/clang/test/CodeGenHLSL/resources/StructuredBuffers-constructors.hlsl
@@ -57,6 +57,7 @@ export void foo() {
// CHECK: define linkonce_odr hidden void @hlsl::RWStructuredBuffer<float>::__createFromImplicitBindingWithImplicitCounter(unsigned int, unsigned int, int, unsigned int, char const*, unsigned int)
// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWStructuredBuffer") align {{(4|8)}} %[[RetValue2:.*]], i32 noundef %orderId,
// CHECK-SAME: i32 noundef %spaceNo, i32 noundef %range, i32 noundef %index, ptr noundef %name, i32 noundef %counterOrderId)
+// CHECK-SPV: %[[ConvTok:.*]] = call token @llvm.experimental.convergence.entry()
// CHECK: %[[Tmp2:.*]] = alloca %"class.hlsl::RWStructuredBuffer"
// CHECK-DXIL: %[[Handle2:.*]] = call target("dx.RawBuffer", float, 1, 0)
// CHECK-DXIL-SAME: @llvm.dx.resource.handlefromimplicitbinding.tdx.RawBuffer_f32_1_0t(
@@ -73,6 +74,7 @@ export void foo() {
// CHECK-SPV: %[[HandlePtr:.*]] = getelementptr inbounds nuw %"class.hlsl::RWStructuredBuffer", ptr %[[Tmp2]], i32 0, i32 0
// CHECK-SPV: %[[LoadedHandle:.*]] = load target("spirv.VulkanBuffer", [0 x float], 12, 1), ptr %[[HandlePtr]], align 8
// CHECK-SPV: %[[CounterHandle:.*]] = call target("spirv.VulkanBuffer", i32, 12, 1) @llvm.spv.resource.counterhandlefromimplicitbinding
+// CHECK-SPV-SAME: [ "convergencectrl"(token %[[ConvTok]]) ]
// CHECK-SPV: %[[CounterHandlePtr:.*]] = getelementptr inbounds nuw %"class.hlsl::RWStructuredBuffer", ptr %[[Tmp2]], i32 0, i32 1
// CHECK-SPV-NEXT: store target("spirv.VulkanBuffer", i32, 12, 1) %[[CounterHandle]], ptr %[[CounterHandlePtr]], align 8
// CHECK: call void @hlsl::RWStructuredBuffer<float>::RWStructuredBuffer(hlsl::RWStructuredBuffer<float> const&)(ptr {{.*}} %[[RetValue2]], ptr {{.*}} %[[Tmp2]])
diff --git a/clang/test/CodeGenHLSL/resources/StructuredBuffers-subscripts.hlsl b/clang/test/CodeGenHLSL/resources/StructuredBuffers-subscripts.hlsl
index 4e1c1b7b55984..83a3aac79e18e 100644
--- a/clang/test/CodeGenHLSL/resources/StructuredBuffers-subscripts.hlsl
+++ b/clang/test/CodeGenHLSL/resources/StructuredBuffers-subscripts.hlsl
@@ -20,11 +20,13 @@ void main(unsigned GI : SV_GroupIndex) {
// CHECK: %[[TMP:.*]] = alloca %struct.S, align 1
- // DXIL: %[[INPTR:.*]] = call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_0_0t.i32(target("dx.RawBuffer", i32, 0, 0) %{{.*}}, i32 %{{.*}})
- // SPV: %[[INPTR:.*]] = call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_0t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 0) %{{.*}}, i32 %{{.*}})
+ // CHECK: %[[CONVTOK:.*]] = call token @llvm.experimental.convergence.entry()
+
+ // DXIL: %[[INPTR:.*]] = call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_0_0t.i32(target("dx.RawBuffer", i32, 0, 0) %{{.*}}, i32 %{{.*}}) [ "convergencectrl"(token %[[CONVTOK]]) ]
+ // SPV: %[[INPTR:.*]] = call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_0t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 0) %{{.*}}, i32 %{{.*}}) [ "convergencectrl"(token %[[CONVTOK]]) ]
// CHECK: %[[LOAD:.*]] = load i32, ptr {{.*}}%[[INPTR]]
- // DXIL: %[[OUT1PTR:.*]] = call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t.i32(target("dx.RawBuffer", i32, 1, 0) %{{.*}}, i32 %{{.*}})
- // SPV: %[[OUT1PTR:.*]] = call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %{{.*}}, i32 %{{.*}})
+ // DXIL: %[[OUT1PTR:.*]] = call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t.i32(target("dx.RawBuffer", i32, 1, 0) %{{.*}}, i32 %{{.*}}) [ "convergencectrl"(token %[[CONVTOK]]) ]
+ // SPV: %[[OUT1PTR:.*]] = call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %{{.*}}, i32 %{{.*}}) [ "convergencectrl"(token %[[CONVTOK]]) ]
// CHECK: store i32 %[[LOAD]], ptr {{.*}}%[[OUT1PTR]]
Out1[GI] = In[GI];
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index af360dfc78965..357950c8682bb 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -38,11 +38,11 @@ def int_dx_resource_handlefromimplicitbinding
def int_dx_resource_getpointer
: DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_any_ty],
- [IntrReadMem, IntrInaccessibleMemOnly]>;
+ [IntrConvergent, IntrNoMem]>;
def int_dx_resource_getbasepointer
: DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty],
- [IntrReadMem, IntrInaccessibleMemOnly]>;
+ [IntrConvergent, IntrNoMem]>;
def int_dx_resource_nonuniformindex
: DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_i32_ty], [IntrNoMem]>;
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 6e4cf8f7e72dc..5fb1e16197b57 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -207,11 +207,11 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
def int_spv_resource_counterhandlefromimplicitbinding
: DefaultAttrsIntrinsic<[llvm_any_ty],
[llvm_any_ty, llvm_i32_ty, llvm_i32_ty],
- [IntrNoMem]>;
+ [IntrNoMem, IntrConvergent]>;
def int_spv_resource_counterhandlefrombinding
: DefaultAttrsIntrinsic<[llvm_any_ty],
[llvm_any_ty, llvm_i32_ty, llvm_i32_ty],
- [IntrNoMem]>;
+ [IntrNoMem, IntrConvergent]>;
def int_spv_firstbituhigh : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>;
def int_spv_firstbitshigh : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>;
@@ -328,11 +328,11 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
def int_spv_resource_getpointer
: DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_any_ty],
- [IntrNoMem]>;
+ [IntrNoMem, IntrConvergent]>;
def int_spv_resource_getbasepointer
: DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty],
- [IntrNoMem]>;
+ [IntrNoMem, IntrConvergent]>;
def int_spv_pushconstant_getpointer
: DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty], [IntrNoMem]>;
diff --git a/llvm/test/Transforms/DirectX/getpointer-sink-behavior.ll b/llvm/test/Transforms/DirectX/getpointer-sink-behavior.ll
deleted file mode 100644
index de509fd0ca9fa..0000000000000
--- a/llvm/test/Transforms/DirectX/getpointer-sink-behavior.ll
+++ /dev/null
@@ -1,31 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; RUN: opt -passes=sink -S %s | FileCheck %s
-
-; Verify that dx.resource.getpointer can be sunk into a branch where it is
-; only used.
-
-define void @can_sink_into_branch(i1 %cond) {
-; CHECK-LABEL: define void @can_sink_into_branch(
-; CHECK-SAME: i1 [[COND:%.*]]) {
-; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[IF_END:.*]]
-; CHECK: [[IF_THEN]]:
-; CHECK-NEXT: [[BUF:%.*]] = call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_i32_1_0t(i32 0, i32 0, i32 1, i32 0, ptr null)
-; CHECK-NEXT: [[PTR:%.*]] = call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t.i32(target("dx.RawBuffer", i32, 1, 0) [[BUF]], i32 0)
-; CHECK-NEXT: store i32 42, ptr [[PTR]], align 4
-; CHECK-NEXT: br label %[[IF_END]]
-; CHECK: [[IF_END]]:
-; CHECK-NEXT: ret void
-;
-entry:
- %buf = call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_i32_1_0t(i32 0, i32 0, i32 1, i32 0, ptr null)
- %ptr = call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i32_1_0t.i32(target("dx.RawBuffer", i32, 1, 0) %buf, i32 0)
- br i1 %cond, label %if.then, label %if.end
-
-if.then:
- store i32 42, ptr %ptr, align 4
- br label %if.end
-
-if.end:
- ret void
-}
diff --git a/llvm/test/Transforms/GVN/no-sink-dxgetbasepointer.ll b/llvm/test/Transforms/GVN/no-sink-dxgetbasepointer.ll
new file mode 100644
index 0000000000000..09a6c95022439
--- /dev/null
+++ b/llvm/test/Transforms/GVN/no-sink-dxgetbasepointer.ll
@@ -0,0 +1,52 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=gvn,instnamer -S %s | FileCheck %s
+
+; This test ensures that GVN does not sink the pointer returned by
+; dx.resource.getbasepointer out of the branches and into the common successor.
+
+ at Out.str = private unnamed_addr constant [4 x i8] c"Out\00", align 1
+ at cond = external hidden local_unnamed_addr addrspace(2) global i32, align 4
+
+define void @main() local_unnamed_addr {
+; CHECK-LABEL: define void @main() local_unnamed_addr {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[H:%.*]] = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_i32_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @Out.str)
+; CHECK-NEXT: [[C:%.*]] = load i32, ptr addrspace(2) @cond, align 4
+; CHECK-NEXT: [[LOADEDV:%.*]] = trunc nuw i32 [[C]] to i1
+; CHECK-NEXT: br i1 [[LOADEDV]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
+; CHECK: [[IF_THEN]]:
+; CHECK-NEXT: [[P1:%.*]] = tail call ptr @llvm.dx.resource.getbasepointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) [[H]])
+; CHECK-NEXT: store i32 1, ptr [[P1]], align 4
+; CHECK-NEXT: br label %[[EXIT:.*]]
+; CHECK: [[IF_ELSE]]:
+; CHECK-NEXT: [[P2:%.*]] = tail call ptr @llvm.dx.resource.getbasepointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) [[H]])
+; CHECK-NEXT: store i32 2, ptr [[P2]], align 4
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[P3:%.*]] = tail call ptr @llvm.dx.resource.getbasepointer.p0.tdx.RawBuffer_i32_1_0t(target("dx.RawBuffer", i32, 1, 0) [[H]])
+; CHECK-NEXT: [[V:%.*]] = load i32, ptr [[P3]], align 4
+; CHECK-NEXT: store i32 [[V]], ptr [[P3]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %h = tail call target("dx.RawBuffer", i32, 1, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr nonnull @Out.str)
+ %c = load i32, ptr addrspace(2) @cond, align 4
+ %loadedv = trunc nuw i32 %c to i1
+ br i1 %loadedv, label %if.then, label %if.else
+
+if.then:
+ %p1 = tail call ptr @llvm.dx.resource.getbasepointer(target("dx.RawBuffer", i32, 1, 0) %h)
+ store i32 1, ptr %p1, align 4
+ br label %exit
+
+if.else:
+ %p2 = tail call ptr @llvm.dx.resource.getbasepointer(target("dx.RawBuffer", i32, 1, 0) %h)
+ store i32 2, ptr %p2, align 4
+ br label %exit
+
+exit:
+ %p3 = tail call ptr @llvm.dx.resource.getbasepointer(target("dx.RawBuffer", i32, 1, 0) %h)
+ %v = load i32, ptr %p3, align 4
+ store i32 %v, ptr %p3, align 4
+ ret void
+}
diff --git a/llvm/test/Transforms/GVN/no-sink-dxgetpointer.ll b/llvm/test/Transforms/GVN/no-sink-dxgetpointer.ll
index eeaead53f893a..641160a78ead3 100644
--- a/llvm/test/Transforms/GVN/no-sink-dxgetpointer.ll
+++ b/llvm/test/Transforms/GVN/no-sink-dxgetpointer.ll
@@ -1,9 +1,8 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; RUN: opt -passes=gvn -passes=instnamer -S %s | FileCheck %s
+; RUN: opt -passes=gvn,instnamer -S %s | FileCheck %s
-; This test ensures that given dx.resource.getpointer reads inaccessible memory,
-; the GVN pass is prevented from sinking these intrinsics out of branches which
-; would create phi nodes on the returned ptr.
+; This test ensures that given dx.resource.getpointer is marked convergent, the
+; GVN pass is prevented from sinking these intrinsics.
;
; NOTE: The following ir represents case F and G from:
; https://godbolt.org/z/cK4xh1P49.
diff --git a/llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll b/llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll
new file mode 100644
index 0000000000000..dd89dfd31babc
--- /dev/null
+++ b/llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll
@@ -0,0 +1,73 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=gvn,instnamer -S %s | FileCheck %s
+
+; This test ensures that given spv.resource.counterhandlefrombinding is marked
+; convergent, the GVN pass is prevented from sinking these intrinsics out of
+; branches which would create phi nodes on the returned counter handle.
+;
+; The CHECK lines below match the input IR exactly, so this test verifies that
+; the pass makes no changes to the IR.
+;
+; It models the following HLSL:
+;
+; RWStructuredBuffer<int> Out[4] : register(u0);
+;
+; [numthreads(4,1,1)]
+; void main(uint GI : SV_GroupIndex) {
+; for (int i = 0; i < GI; i++)
+; Out[NonUniformResourceIndex(GI)].IncrementCounter();
+;
+; Out[NonUniformResourceIndex(GI)][0] = Out[NonUniformResourceIndex(GI)].IncrementCounter();
+; }
+
+target datalayout = "e-ve-i64:64-n8:16:32:64-G10"
+target triple = "spirv1.6-unknown-vulkan1.3-compute"
+
+ at Out.str = private unnamed_addr constant [4 x i8] c"Out\00", align 1
+ at cond = external hidden local_unnamed_addr addrspace(2) global i32, align 4
+
+define void @main() local_unnamed_addr {
+; CHECK-LABEL: define void @main() local_unnamed_addr {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[I:%.*]] = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 0, i32 0, i32 4, i32 0, ptr nonnull @Out.str)
+; CHECK-NEXT: [[I1:%.*]] = load i32, ptr addrspace(2) @cond, align 4
+; CHECK-NEXT: [[LOADEDV:%.*]] = trunc nuw i32 [[I1]] to i1
+; CHECK-NEXT: br i1 [[LOADEDV]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
+; CHECK: [[IF_THEN]]:
+; CHECK-NEXT: [[I2:%.*]] = tail call target("spirv.VulkanBuffer", i32, 12, 1) @llvm.spv.resource.counterhandlefrombinding.tspirv.VulkanBuffer_i32_12_1t.tspirv.VulkanBuffer_a0i32_12_1t(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I]], i32 0, i32 0)
+; CHECK-NEXT: [[I3:%.*]] = tail call i32 @llvm.spv.resource.updatecounter.tspirv.VulkanBuffer_i32_12_1t(target("spirv.VulkanBuffer", i32, 12, 1) [[I2]], i8 1)
+; CHECK-NEXT: br label %[[EXIT:.*]]
+; CHECK: [[IF_ELSE]]:
+; CHECK-NEXT: [[I4:%.*]] = tail call target("spirv.VulkanBuffer", i32, 12, 1) @llvm.spv.resource.counterhandlefrombinding.tspirv.VulkanBuffer_i32_12_1t.tspirv.VulkanBuffer_a0i32_12_1t(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I]], i32 0, i32 0)
+; CHECK-NEXT: [[I5:%.*]] = tail call i32 @llvm.spv.resource.updatecounter.tspirv.VulkanBuffer_i32_12_1t(target("spirv.VulkanBuffer", i32, 12, 1) [[I4]], i8 1)
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[I6:%.*]] = tail call target("spirv.VulkanBuffer", i32, 12, 1) @llvm.spv.resource.counterhandlefrombinding.tspirv.VulkanBuffer_i32_12_1t.tspirv.VulkanBuffer_a0i32_12_1t(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I]], i32 0, i32 0)
+; CHECK-NEXT: [[I7:%.*]] = tail call i32 @llvm.spv.resource.updatecounter.tspirv.VulkanBuffer_i32_12_1t(target("spirv.VulkanBuffer", i32, 12, 1) [[I6]], i8 1)
+; CHECK-NEXT: [[I8:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I]], i32 0)
+; CHECK-NEXT: store i32 [[I7]], ptr addrspace(11) [[I8]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %0 = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 0, i32 0, i32 4, i32 0, ptr nonnull @Out.str)
+ %1 = load i32, ptr addrspace(2) @cond, align 4
+ %loadedv = trunc nuw i32 %1 to i1
+ br i1 %loadedv, label %if.then, label %if.else
+
+if.then: ; preds = %entry
+ %2 = tail call target("spirv.VulkanBuffer", i32, 12, 1) @llvm.spv.resource.counterhandlefrombinding.tspirv.VulkanBuffer_i32_12_1t.tspirv.VulkanBuffer_a0i32_12_1t(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %0, i32 0, i32 0)
+ %3 = tail call i32 @llvm.spv.resource.updatecounter.tspirv.VulkanBuffer_i32_12_1t(target("spirv.VulkanBuffer", i32, 12, 1) %2, i8 1)
+ br label %exit
+
+if.else: ; preds = %entry
+ %4 = tail call target("spirv.VulkanBuffer", i32, 12, 1) @llvm.spv.resource.counterhandlefrombinding.tspirv.VulkanBuffer_i32_12_1t.tspirv.VulkanBuffer_a0i32_12_1t(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %0, i32 0, i32 0)
+ %5 = tail call i32 @llvm.spv.resource.updatecounter.tspirv.VulkanBuffer_i32_12_1t(target("spirv.VulkanBuffer", i32, 12, 1) %4, i8 1)
+ br label %exit
+
+exit: ; preds = %if.else, %if.then
+ %6 = tail call target("spirv.VulkanBuffer", i32, 12, 1) @llvm.spv.resource.counterhandlefrombinding.tspirv.VulkanBuffer_i32_12_1t.tspirv.VulkanBuffer_a0i32_12_1t(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %0, i32 0, i32 0)
+ %7 = tail call i32 @llvm.spv.resource.updatecounter.tspirv.VulkanBuffer_i32_12_1t(target("spirv.VulkanBuffer", i32, 12, 1) %6, i8 1)
+ %8 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %0, i32 0)
+ store i32 %7, ptr addrspace(11) %8, align 4
+ ret void
+}
diff --git a/llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll b/llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll
new file mode 100644
index 0000000000000..9a202f9c319ad
--- /dev/null
+++ b/llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll
@@ -0,0 +1,101 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=gvn,instnamer -S %s | FileCheck %s
+
+; This test ensures that given spv.resource.getpointer is marked convergent,
+; the GVN pass is prevented from sinking these intrinsics out of branches which
+; would create phi nodes on the returned ptr.
+;
+; The CHECK lines below match the input IR exactly, so this test verifies that
+; the pass makes no changes to the IR.
+
+%"class.hlsl::RWBuffer" = type { target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) }
+%"class.hlsl::RWStructuredBuffer" = type { target("spirv.VulkanBuffer", [0 x i32], 12, 1), target("spirv.VulkanBuffer", [0 x i32], 12, 1) }
+%__cblayout_c = type <{ i32 }>
+
+ at In = internal global %"class.hlsl::RWBuffer" poison, align 4
+ at .str = private unnamed_addr constant [3 x i8] c"In\00", align 1
+ at Out0 = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4
+ at .str.2 = private unnamed_addr constant [5 x i8] c"Out0\00", align 1
+ at Out1 = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4
+ at .str.4 = private unnamed_addr constant [5 x i8] c"Out1\00", align 1
+ at c.cb = local_unnamed_addr global target("spirv.VulkanBuffer", %__cblayout_c, 2, 0) poison
+ at cond = external hidden local_unnamed_addr addrspace(2) global i32, align 4
+ at c.str = private unnamed_addr constant [2 x i8] c"c\00", align 1
+
+define void @main() local_unnamed_addr {
+; CHECK-LABEL: define void @main() local_unnamed_addr {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[I:%.*]] = tail call target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) @llvm.spv.resource.handlefrombinding.tspirv.Image_i32_5_2_0_0_2_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+; CHECK-NEXT: store target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) [[I]], ptr @In, align 4
+; CHECK-NEXT: [[I1:%.*]] = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 0, i32 1, i32 1, i32 0, ptr nonnull @.str.2)
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I1]], ptr @Out0, align 4
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I1]], ptr getelementptr inbounds nuw (i8, ptr @Out0, i32 8), align 4
+; CHECK-NEXT: [[I2:%.*]] = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 0, i32 2, i32 1, i32 0, ptr nonnull @.str.4)
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I2]], ptr @Out1, align 4
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I2]], ptr getelementptr inbounds nuw (i8, ptr @Out1, i32 8), align 4
+; CHECK-NEXT: [[C_CB_H_I_I:%.*]] = tail call target("spirv.VulkanBuffer", [[__CBLAYOUT_C:%.*]], 2, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_s___cblayout_cs_2_0t(i32 4, i32 0, i32 1, i32 0, ptr nonnull @c.str)
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [[__CBLAYOUT_C]], 2, 0) [[C_CB_H_I_I]], ptr @c.cb, align 8
+; CHECK-NEXT: [[I3:%.*]] = tail call i32 @llvm.spv.flattened.thread.id.in.group()
+; CHECK-NEXT: [[I4:%.*]] = load i32, ptr addrspace(2) @cond, align 4
+; CHECK-NEXT: [[LOADEDV_I:%.*]] = trunc nuw i32 [[I4]] to i1
+; CHECK-NEXT: br i1 [[LOADEDV_I]], label %[[IF_THEN_I:.*]], label %[[IF_ELSE_I:.*]]
+; CHECK: [[IF_THEN_I]]:
+; CHECK-NEXT: [[I5:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.spv.resource.getpointer.p0.tspirv.Image_i32_5_2_0_0_2_1t.i32(target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) [[I]], i32 [[I3]])
+; CHECK-NEXT: [[I6:%.*]] = load i32, ptr [[I5]], align 4
+; CHECK-NEXT: [[HLSL_WAVE_ACTIVE_SUM_I:%.*]] = tail call i32 @llvm.spv.wave.reduce.sum.i32(i32 [[I6]])
+; CHECK-NEXT: [[I7:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I1]], i32 [[I3]])
+; CHECK-NEXT: store i32 [[HLSL_WAVE_ACTIVE_SUM_I]], ptr addrspace(11) [[I7]], align 4
+; CHECK-NEXT: br label %[[MAIN_EXIT:.*]]
+; CHECK: [[IF_ELSE_I]]:
+; CHECK-NEXT: [[I8:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.spv.resource.getpointer.p0.tspirv.Image_i32_5_2_0_0_2_1t.i32(target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) [[I]], i32 [[I3]])
+; CHECK-NEXT: [[I9:%.*]] = load i32, ptr [[I8]], align 4
+; CHECK-NEXT: [[I10:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I1]], i32 0)
+; CHECK-NEXT: store i32 [[I9]], ptr addrspace(11) [[I10]], align 4
+; CHECK-NEXT: br label %[[MAIN_EXIT]]
+; CHECK: [[MAIN_EXIT]]:
+; CHECK-NEXT: [[I11:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.spv.resource.getpointer.p0.tspirv.Image_i32_5_2_0_0_2_1t.i32(target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) [[I]], i32 [[I3]])
+; CHECK-NEXT: [[I12:%.*]] = load i32, ptr [[I11]], align 4
+; CHECK-NEXT: [[HLSL_WAVE_ACTIVE_SUM5_I:%.*]] = tail call i32 @llvm.spv.wave.reduce.sum.i32(i32 [[I12]])
+; CHECK-NEXT: [[I13:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I1]], i32 [[I3]])
+; CHECK-NEXT: store i32 [[HLSL_WAVE_ACTIVE_SUM5_I]], ptr addrspace(11) [[I13]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %0 = tail call target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) @llvm.spv.resource.handlefrombinding.tspirv.Image_i32_5_2_0_0_2_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ store target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) %0, ptr @In, align 4
+ %1 = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 0, i32 1, i32 1, i32 0, ptr nonnull @.str.2)
+ store target("spirv.VulkanBuffer", [0 x i32], 12, 1) %1, ptr @Out0, align 4
+ store target("spirv.VulkanBuffer", [0 x i32], 12, 1) %1, ptr getelementptr inbounds nuw (i8, ptr @Out0, i32 8), align 4
+ %2 = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 0, i32 2, i32 1, i32 0, ptr nonnull @.str.4)
+ store target("spirv.VulkanBuffer", [0 x i32], 12, 1) %2, ptr @Out1, align 4
+ store target("spirv.VulkanBuffer", [0 x i32], 12, 1) %2, ptr getelementptr inbounds nuw (i8, ptr @Out1, i32 8), align 4
+ %c.cb_h.i.i = tail call target("spirv.VulkanBuffer", %__cblayout_c, 2, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_s___cblayout_cs_2_0t(i32 4, i32 0, i32 1, i32 0, ptr nonnull @c.str)
+ store target("spirv.VulkanBuffer", %__cblayout_c, 2, 0) %c.cb_h.i.i, ptr @c.cb, align 8
+ %3 = tail call i32 @llvm.spv.flattened.thread.id.in.group()
+ %4 = load i32, ptr addrspace(2) @cond, align 4
+ %loadedv.i = trunc nuw i32 %4 to i1
+ br i1 %loadedv.i, label %if.then.i, label %if.else.i
+
+if.then.i: ; preds = %entry
+ %5 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.spv.resource.getpointer.p0.tspirv.Image_i32_5_2_0_0_2_1t.i32(target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) %0, i32 %3)
+ %6 = load i32, ptr %5, align 4
+ %hlsl.wave.active.sum.i = tail call i32 @llvm.spv.wave.reduce.sum.i32(i32 %6)
+ %7 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %1, i32 %3)
+ store i32 %hlsl.wave.active.sum.i, ptr addrspace(11) %7, align 4
+ br label %main.exit
+
+if.else.i: ; preds = %entry
+ %8 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.spv.resource.getpointer.p0.tspirv.Image_i32_5_2_0_0_2_1t.i32(target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) %0, i32 %3)
+ %9 = load i32, ptr %8, align 4
+ %10 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %1, i32 0)
+ store i32 %9, ptr addrspace(11) %10, align 4
+ br label %main.exit
+
+main.exit: ; preds = %if.then.i, %if.else.i
+ %11 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.spv.resource.getpointer.p0.tspirv.Image_i32_5_2_0_0_2_1t.i32(target("spirv.Image", i32, 5, 2, 0, 0, 2, 1) %0, i32 %3)
+ %12 = load i32, ptr %11, align 4
+ %hlsl.wave.active.sum5.i = tail call i32 @llvm.spv.wave.reduce.sum.i32(i32 %12)
+ %13 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %1, i32 %3)
+ store i32 %hlsl.wave.active.sum5.i, ptr addrspace(11) %13, align 4
+ ret void
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll b/llvm/test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll
new file mode 100644
index 0000000000000..0452bac3c8959
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll
@@ -0,0 +1,91 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes='simplifycfg<sink-common-insts>,instnamer' -S %s | FileCheck %s
+
+; This test ensures that given dx.resource.getpointer is marked convergent, the
+; SimplifyCFG pass is prevented from sinking the resource accesses out of their
+; branches, which would otherwise create an (illegal) phi node selecting between
+; two resource pointers.
+;
+; Without the convergent marking, SimplifyCFG's common-code sinking would sink
+; the load/store from both branches into the common successor and introduce:
+; %ptr = phi ptr [ %bufA.ptr, %if.then ], [ %bufB.ptr, %if.else ]
+; which is an illegal phi node on a resource pointer.
+;
+; The CHECK lines below match the input IR exactly, so this test verifies that
+; the pass makes no changes to the IR.
+;
+; NOTE: The following IR corresponds to the (post-inlining) lowering of:
+;
+; RWStructuredBuffer<float> bufA : register(u0);
+; RWStructuredBuffer<float> bufB : register(u1);
+; RWStructuredBuffer<float> output : register(u2);
+;
+; cbuffer Constants : register(b0) {
+; uint condition;
+; };
+;
+; [numthreads(64, 1, 1)]
+; void main(uint tid : SV_DispatchThreadID) {
+; if (condition == 0) {
+; output[tid] = bufA[tid];
+; } else {
+; output[tid] = bufB[tid];
+; }
+; }
+
+ at condition = external hidden addrspace(2) global i32, align 4
+ at .str = private unnamed_addr constant [5 x i8] c"bufA\00", align 1
+ at .str.2 = private unnamed_addr constant [5 x i8] c"bufB\00", align 1
+ at .str.4 = private unnamed_addr constant [7 x i8] c"output\00", align 1
+
+define void @main() local_unnamed_addr {
+; CHECK-LABEL: define void @main() local_unnamed_addr {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[BUFA:%.*]] = tail call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+; CHECK-NEXT: [[BUFB:%.*]] = tail call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 0, i32 1, i32 1, i32 0, ptr nonnull @.str.2)
+; CHECK-NEXT: [[OUTPUT:%.*]] = tail call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 0, i32 2, i32 1, i32 0, ptr nonnull @.str.4)
+; CHECK-NEXT: [[TID:%.*]] = tail call i32 @llvm.dx.thread.id(i32 0)
+; CHECK-NEXT: [[COND:%.*]] = load i32, ptr addrspace(2) @condition, align 4
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[COND]], 0
+; CHECK-NEXT: br i1 [[CMP]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
+; CHECK: [[IF_THEN]]:
+; CHECK-NEXT: [[PA:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f32_1_0t.i32(target("dx.RawBuffer", float, 1, 0) [[BUFA]], i32 [[TID]])
+; CHECK-NEXT: [[VA:%.*]] = load float, ptr [[PA]], align 4
+; CHECK-NEXT: [[PO1:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f32_1_0t.i32(target("dx.RawBuffer", float, 1, 0) [[OUTPUT]], i32 [[TID]])
+; CHECK-NEXT: store float [[VA]], ptr [[PO1]], align 4
+; CHECK-NEXT: br label %[[IF_END:.*]]
+; CHECK: [[IF_ELSE]]:
+; CHECK-NEXT: [[PB:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f32_1_0t.i32(target("dx.RawBuffer", float, 1, 0) [[BUFB]], i32 [[TID]])
+; CHECK-NEXT: [[VB:%.*]] = load float, ptr [[PB]], align 4
+; CHECK-NEXT: [[PO2:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f32_1_0t.i32(target("dx.RawBuffer", float, 1, 0) [[OUTPUT]], i32 [[TID]])
+; CHECK-NEXT: store float [[VB]], ptr [[PO2]], align 4
+; CHECK-NEXT: br label %[[IF_END]]
+; CHECK: [[IF_END]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ %bufA = tail call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ %bufB = tail call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 0, i32 1, i32 1, i32 0, ptr nonnull @.str.2)
+ %output = tail call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_f32_1_0t(i32 0, i32 2, i32 1, i32 0, ptr nonnull @.str.4)
+ %tid = tail call i32 @llvm.dx.thread.id(i32 0)
+ %cond = load i32, ptr addrspace(2) @condition, align 4
+ %cmp = icmp eq i32 %cond, 0
+ br i1 %cmp, label %if.then, label %if.else
+
+if.then: ; preds = %entry
+ %pA = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f32_1_0t.i32(target("dx.RawBuffer", float, 1, 0) %bufA, i32 %tid)
+ %vA = load float, ptr %pA, align 4
+ %pO1 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f32_1_0t.i32(target("dx.RawBuffer", float, 1, 0) %output, i32 %tid)
+ store float %vA, ptr %pO1, align 4
+ br label %if.end
+
+if.else: ; preds = %entry
+ %pB = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f32_1_0t.i32(target("dx.RawBuffer", float, 1, 0) %bufB, i32 %tid)
+ %vB = load float, ptr %pB, align 4
+ %pO2 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_f32_1_0t.i32(target("dx.RawBuffer", float, 1, 0) %output, i32 %tid)
+ store float %vB, ptr %pO2, align 4
+ br label %if.end
+
+if.end: ; preds = %if.else, %if.then
+ ret void
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/DirectX/no-sink-dxgetpointer.ll b/llvm/test/Transforms/SimplifyCFG/DirectX/no-sink-dxgetpointer.ll
index 576acce2b8dd2..36a87ad00ea9e 100644
--- a/llvm/test/Transforms/SimplifyCFG/DirectX/no-sink-dxgetpointer.ll
+++ b/llvm/test/Transforms/SimplifyCFG/DirectX/no-sink-dxgetpointer.ll
@@ -1,9 +1,9 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; RUN: opt -passes=simplifycfg -passes=instnamer -S %s | FileCheck %s
+; RUN: opt -passes=simplifycfg,instnamer -S %s | FileCheck %s
-; This test ensures that given dx.resource.getpointer reads inaccessible memory,
-; the SimplifyCFG pass will be prevented from sinking these intrinsics out of
-; branches which would create phi nodes on the returned ptr.
+; This test ensures that given dx.resource.getpointer is marked convergent, the
+; SimplifyCFG pass will be prevented from moving these intrinsics into the
+; branches required for sinking handle retrieve before resource access.
;
; NOTE: The following test ir is generated from:
; https://godbolt.org/z/1EdGTbscE.
diff --git a/llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll b/llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll
new file mode 100644
index 0000000000000..cd94a2100f82b
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll
@@ -0,0 +1,131 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=simplifycfg,instnamer -S %s | FileCheck %s
+
+; This test ensures that given spv.resource.getpointer is marked convergent,
+; the SimplifyCFG pass will be prevented from sinking these intrinsics out of
+; branches which would create phi nodes on the returned ptr.
+;
+; The CHECK lines below match the input IR exactly, so this test verifies that
+; the pass makes no changes to the IR.
+
+%"class.hlsl::RWStructuredBuffer" = type { target("spirv.VulkanBuffer", [0 x i32], 12, 1), target("spirv.VulkanBuffer", [0 x i32], 12, 1) }
+%__cblayout_d = type <{ i32, i32, i32, i32 }>
+
+ at a = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4
+ at .str = private unnamed_addr constant [2 x i8] c"a\00", align 1
+ at b = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4
+ at .str.2 = private unnamed_addr constant [2 x i8] c"b\00", align 1
+ at c = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4
+ at .str.4 = private unnamed_addr constant [2 x i8] c"c\00", align 1
+ at d.cb = local_unnamed_addr global target("spirv.VulkanBuffer", %__cblayout_d, 2, 0) poison
+ at e = external hidden local_unnamed_addr addrspace(2) global i32, align 4
+ at f = external hidden local_unnamed_addr addrspace(2) global i32, align 4
+ at g = external hidden local_unnamed_addr addrspace(2) global i32, align 4
+ at h = external hidden local_unnamed_addr addrspace(2) global i32, align 4
+ at d.str = private unnamed_addr constant [2 x i8] c"d\00", align 1
+
+define void @main() local_unnamed_addr {
+; CHECK-LABEL: define void @main() local_unnamed_addr {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[I:%.*]] = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I]], ptr @a, align 4
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I]], ptr getelementptr inbounds nuw (i8, ptr @a, i32 8), align 4
+; CHECK-NEXT: [[I1:%.*]] = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str.2)
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I1]], ptr @b, align 4
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I1]], ptr getelementptr inbounds nuw (i8, ptr @b, i32 8), align 4
+; CHECK-NEXT: [[I2:%.*]] = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 4, i32 0, i32 1, i32 0, ptr nonnull @.str.4)
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I2]], ptr @c, align 4
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I2]], ptr getelementptr inbounds nuw (i8, ptr @c, i32 8), align 4
+; CHECK-NEXT: [[D_CB_H_I_I:%.*]] = tail call target("spirv.VulkanBuffer", [[__CBLAYOUT_D:%.*]], 2, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_s___cblayout_ds_2_0t(i32 6, i32 0, i32 1, i32 0, ptr nonnull @d.str)
+; CHECK-NEXT: store target("spirv.VulkanBuffer", [[__CBLAYOUT_D]], 2, 0) [[D_CB_H_I_I]], ptr @d.cb, align 8
+; CHECK-NEXT: [[I3:%.*]] = load i32, ptr addrspace(2) @h, align 4
+; CHECK-NEXT: [[TOBOOL_NOT_I:%.*]] = icmp eq i32 [[I3]], 0
+; CHECK-NEXT: br i1 [[TOBOOL_NOT_I]], label %[[IF_ELSE_I:.*]], label %[[IF_THEN_I:.*]]
+; CHECK: [[IF_THEN_I]]:
+; CHECK-NEXT: [[I4:%.*]] = load i32, ptr addrspace(2) @f, align 4
+; CHECK-NEXT: [[I5:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I1]], i32 [[I4]])
+; CHECK-NEXT: [[I6:%.*]] = load i32, ptr addrspace(11) [[I5]], align 4
+; CHECK-NEXT: [[I7:%.*]] = load i32, ptr addrspace(2) @g, align 4
+; CHECK-NEXT: [[I8:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I2]], i32 [[I7]])
+; CHECK-NEXT: store i32 [[I6]], ptr addrspace(11) [[I8]], align 4
+; CHECK-NEXT: br label %[[MAIN_EXIT:.*]]
+; CHECK: [[IF_ELSE_I]]:
+; CHECK-NEXT: [[I9:%.*]] = load i32, ptr addrspace(2) @g, align 4
+; CHECK-NEXT: [[CMP_I:%.*]] = icmp eq i32 [[I9]], 0
+; CHECK-NEXT: br i1 [[CMP_I]], label %[[IF_THEN2_I:.*]], label %[[IF_ELSE6_I:.*]]
+; CHECK: [[IF_THEN2_I]]:
+; CHECK-NEXT: [[I10:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I1]], i32 0)
+; CHECK-NEXT: [[I11:%.*]] = load i32, ptr addrspace(11) [[I10]], align 4
+; CHECK-NEXT: [[I12:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I2]], i32 0)
+; CHECK-NEXT: store i32 [[I11]], ptr addrspace(11) [[I12]], align 4
+; CHECK-NEXT: br label %[[MAIN_EXIT]]
+; CHECK: [[IF_ELSE6_I]]:
+; CHECK-NEXT: [[I13:%.*]] = load i32, ptr addrspace(2) @e, align 4
+; CHECK-NEXT: [[I14:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I]], i32 [[I13]])
+; CHECK-NEXT: [[I15:%.*]] = load i32, ptr addrspace(11) [[I14]], align 4
+; CHECK-NEXT: [[I16:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I2]], i32 [[I9]])
+; CHECK-NEXT: store i32 [[I15]], ptr addrspace(11) [[I16]], align 4
+; CHECK-NEXT: br label %[[MAIN_EXIT]]
+; CHECK: [[MAIN_EXIT]]:
+; CHECK-NEXT: [[I17:%.*]] = load i32, ptr addrspace(2) @f, align 4
+; CHECK-NEXT: [[I18:%.*]] = load i32, ptr addrspace(2) @g, align 4
+; CHECK-NEXT: [[I19:%.*]] = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[I2]], i32 [[I18]])
+; CHECK-NEXT: [[I20:%.*]] = load i32, ptr addrspace(11) [[I19]], align 4
+; CHECK-NEXT: [[ADD_I:%.*]] = add i32 [[I20]], [[I17]]
+; CHECK-NEXT: store i32 [[ADD_I]], ptr addrspace(11) [[I19]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %0 = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str)
+ store target("spirv.VulkanBuffer", [0 x i32], 12, 1) %0, ptr @a, align 4
+ store target("spirv.VulkanBuffer", [0 x i32], 12, 1) %0, ptr getelementptr inbounds nuw (i8, ptr @a, i32 8), align 4
+ %1 = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 2, i32 0, i32 1, i32 0, ptr nonnull @.str.2)
+ store target("spirv.VulkanBuffer", [0 x i32], 12, 1) %1, ptr @b, align 4
+ store target("spirv.VulkanBuffer", [0 x i32], 12, 1) %1, ptr getelementptr inbounds nuw (i8, ptr @b, i32 8), align 4
+ %2 = tail call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_a0i32_12_1t(i32 4, i32 0, i32 1, i32 0, ptr nonnull @.str.4)
+ store target("spirv.VulkanBuffer", [0 x i32], 12, 1) %2, ptr @c, align 4
+ store target("spirv.VulkanBuffer", [0 x i32], 12, 1) %2, ptr getelementptr inbounds nuw (i8, ptr @c, i32 8), align 4
+ %d.cb_h.i.i = tail call target("spirv.VulkanBuffer", %__cblayout_d, 2, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_s___cblayout_ds_2_0t(i32 6, i32 0, i32 1, i32 0, ptr nonnull @d.str)
+ store target("spirv.VulkanBuffer", %__cblayout_d, 2, 0) %d.cb_h.i.i, ptr @d.cb, align 8
+ %3 = load i32, ptr addrspace(2) @h, align 4
+ %tobool.not.i = icmp eq i32 %3, 0
+ br i1 %tobool.not.i, label %if.else.i, label %if.then.i
+
+if.then.i: ; preds = %entry
+ %4 = load i32, ptr addrspace(2) @f, align 4
+ %5 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %1, i32 %4)
+ %6 = load i32, ptr addrspace(11) %5, align 4
+ %7 = load i32, ptr addrspace(2) @g, align 4
+ %8 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %2, i32 %7)
+ store i32 %6, ptr addrspace(11) %8, align 4
+ br label %main.exit
+
+if.else.i: ; preds = %entry
+ %9 = load i32, ptr addrspace(2) @g, align 4
+ %cmp.i = icmp eq i32 %9, 0
+ br i1 %cmp.i, label %if.then2.i, label %if.else6.i
+
+if.then2.i: ; preds = %if.else.i
+ %10 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %1, i32 0)
+ %11 = load i32, ptr addrspace(11) %10, align 4
+ %12 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %2, i32 0)
+ store i32 %11, ptr addrspace(11) %12, align 4
+ br label %main.exit
+
+if.else6.i: ; preds = %if.else.i
+ %13 = load i32, ptr addrspace(2) @e, align 4
+ %14 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %0, i32 %13)
+ %15 = load i32, ptr addrspace(11) %14, align 4
+ %16 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %2, i32 %9)
+ store i32 %15, ptr addrspace(11) %16, align 4
+ br label %main.exit
+
+main.exit: ; preds = %if.then.i, %if.then2.i, %if.else6.i
+ %17 = load i32, ptr addrspace(2) @f, align 4
+ %18 = load i32, ptr addrspace(2) @g, align 4
+ %19 = tail call noundef nonnull align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %2, i32 %18)
+ %20 = load i32, ptr addrspace(11) %19, align 4
+ %add.i = add i32 %20, %17
+ store i32 %add.i, ptr addrspace(11) %19, align 4
+ ret void
+}
More information about the cfe-commits
mailing list