https://github.com/inbelic updated https://github.com/llvm/llvm-project/pull/205433
>From af6351c4ab34c7f7399a1843916b1ea94cf6a683 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Tue, 23 Jun 2026 20:44:24 +0000 Subject: [PATCH 1/5] Revert "[DirectX] Denote `dx.resource.getpointer` with `IntrInaccessibleMemOnly` and `IntrReadMem` (#193593)" This reverts commit 63e36755decfc92ee5e457c292aeeb0a69cfa484. --- llvm/include/llvm/IR/IntrinsicsDirectX.td | 2 +- .../DirectX/getpointer-sink-behavior.ll | 31 ------------------- .../Transforms/GVN/no-sink-dxgetpointer.ll | 5 ++- .../DirectX/no-sink-dxgetpointer.ll | 6 ++-- 4 files changed, 6 insertions(+), 38 deletions(-) delete mode 100644 llvm/test/Transforms/DirectX/getpointer-sink-behavior.ll diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td index af360dfc78965..fed15deb46b77 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_any_ty], - [IntrReadMem, IntrInaccessibleMemOnly]>; + [IntrConvergent, IntrNoMem]>; def int_dx_resource_getbasepointer : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty], 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-dxgetpointer.ll b/llvm/test/Transforms/GVN/no-sink-dxgetpointer.ll index eeaead53f893a..2d5a07562f4a4 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 -; 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/SimplifyCFG/DirectX/no-sink-dxgetpointer.ll b/llvm/test/Transforms/SimplifyCFG/DirectX/no-sink-dxgetpointer.ll index 576acce2b8dd2..038b25d765d6f 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 -; 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. >From a0cadee9c7cb7b32646602bdc26478f380d51704 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Tue, 23 Jun 2026 20:50:56 +0000 Subject: [PATCH 2/5] [SPIR-V] Cosolidate resource get pointer and counter handle with directx --- clang/lib/CodeGen/CGHLSLBuiltins.cpp | 3 +- llvm/include/llvm/IR/IntrinsicsSPIRV.td | 8 +- .../Transforms/GVN/no-sink-spvgetpointer.ll | 98 ++++++++++++++ .../DirectX/no-phi-dxgetpointer.ll | 88 ++++++++++++ .../SPIRV/no-sink-spvgetpointer.ll | 128 ++++++++++++++++++ 5 files changed, 320 insertions(+), 5 deletions(-) create mode 100644 llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll create mode 100644 llvm/test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll create mode 100644 llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.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/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/GVN/no-sink-spvgetpointer.ll b/llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll new file mode 100644 index 0000000000000..f21dda66986fd --- /dev/null +++ b/llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll @@ -0,0 +1,98 @@ +; 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 + +; 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. + +%"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 }> + +@In = internal global %"class.hlsl::RWBuffer" poison, align 4 [email protected] = private unnamed_addr constant [3 x i8] c"In\00", align 1 +@Out0 = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4 [email protected] = private unnamed_addr constant [5 x i8] c"Out0\00", align 1 +@Out1 = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4 [email protected] = private unnamed_addr constant [5 x i8] c"Out1\00", align 1 [email protected] = local_unnamed_addr global target("spirv.VulkanBuffer", %__cblayout_c, 2, 0) poison +@cond = external hidden local_unnamed_addr addrspace(2) global i32, align 4 [email protected] = 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..24dcf473513ce --- /dev/null +++ b/llvm/test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll @@ -0,0 +1,88 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6 +; RUN: opt -passes='simplifycfg<sink-common-insts>' -passes=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. +; +; 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]; +; } +; } + +@condition = external hidden addrspace(2) global i32, align 4 [email protected] = private unnamed_addr constant [5 x i8] c"bufA\00", align 1 [email protected] = private unnamed_addr constant [5 x i8] c"bufB\00", align 1 [email protected] = 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/SPIRV/no-sink-spvgetpointer.ll b/llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll new file mode 100644 index 0000000000000..4071cf518eb7a --- /dev/null +++ b/llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll @@ -0,0 +1,128 @@ +; 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 + +; 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. + +%"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 }> + +@a = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4 [email protected] = private unnamed_addr constant [2 x i8] c"a\00", align 1 +@b = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4 [email protected] = private unnamed_addr constant [2 x i8] c"b\00", align 1 +@c = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4 [email protected] = private unnamed_addr constant [2 x i8] c"c\00", align 1 [email protected] = local_unnamed_addr global target("spirv.VulkanBuffer", %__cblayout_d, 2, 0) poison +@e = external hidden local_unnamed_addr addrspace(2) global i32, align 4 +@f = external hidden local_unnamed_addr addrspace(2) global i32, align 4 +@g = external hidden local_unnamed_addr addrspace(2) global i32, align 4 +@h = external hidden local_unnamed_addr addrspace(2) global i32, align 4 [email protected] = 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 +} >From 07d557a65b8a8ae64c91df2b9000b11febb0a09b Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Tue, 23 Jun 2026 21:17:41 +0000 Subject: [PATCH 3/5] [SPIR-V] Add test case for counter --- .../GVN/no-sink-spvcounterhandle.ll | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll 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..137f63f08f0a8 --- /dev/null +++ b/llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll @@ -0,0 +1,70 @@ +; 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 + +; 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. +; +; 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" + [email protected] = private unnamed_addr constant [4 x i8] c"Out\00", align 1 +@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 +} >From 7423d1300e32ce305f0ead0bd69d83f5923cff45 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Thu, 25 Jun 2026 16:31:43 +0000 Subject: [PATCH 4/5] review: add tests of convergent tokens being emitted --- .../resources/StructuredBuffers-constructors.hlsl | 2 ++ .../resources/StructuredBuffers-subscripts.hlsl | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) 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]; >From 02d763b19502a04e74fdc7b7903189ac43c2b584 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Thu, 25 Jun 2026 16:36:22 +0000 Subject: [PATCH 5/5] review: update test descriptions --- llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll | 3 +++ llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll | 3 +++ .../test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll | 3 +++ .../test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll | 3 +++ 4 files changed, 12 insertions(+) diff --git a/llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll b/llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll index 137f63f08f0a8..9065fbbed0da7 100644 --- a/llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll +++ b/llvm/test/Transforms/GVN/no-sink-spvcounterhandle.ll @@ -5,6 +5,9 @@ ; 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); diff --git a/llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll b/llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll index f21dda66986fd..5e6c2a83118bd 100644 --- a/llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll +++ b/llvm/test/Transforms/GVN/no-sink-spvgetpointer.ll @@ -4,6 +4,9 @@ ; 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) } diff --git a/llvm/test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll b/llvm/test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll index 24dcf473513ce..395fa7e073f3f 100644 --- a/llvm/test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll +++ b/llvm/test/Transforms/SimplifyCFG/DirectX/no-phi-dxgetpointer.ll @@ -11,6 +11,9 @@ ; %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); diff --git a/llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll b/llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll index 4071cf518eb7a..ebd1c0cc84131 100644 --- a/llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll +++ b/llvm/test/Transforms/SimplifyCFG/SPIRV/no-sink-spvgetpointer.ll @@ -4,6 +4,9 @@ ; 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 }> _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
