https://github.com/tcorringham updated https://github.com/llvm/llvm-project/pull/211256
>From 9df964e1090328a8585ca87a24c41bf90f640ee5 Mon Sep 17 00:00:00 2001 From: Tim Corringham <[email protected]> Date: Wed, 22 Jul 2026 11:08:50 +0100 Subject: [PATCH 1/2] [HLSL][SPIRV] Add IntrConvergent to convergent intrinsics A number of spirv intrinsics need to run in convergent code flow but LLVM -O3 could sometimes lower these into divergent control flow as they were missing the IntrConvergent attribute. This change adds the IntrConvergent attribute to these intrinsics, and switches the builtin emission from `Builder.CreateIntrinsic` to `EmitRuntimeCall` (via a helper function) so that the attribute is preserved through codegen. Tests check that the Convergent attribute is added where expected, and that it survives a llvm-as/llvm-dis round-trip. Assisted by Cursor Fixes #211253 --- clang/lib/CodeGen/CGHLSLBuiltins.cpp | 40 +++++++++++++------ llvm/include/llvm/IR/IntrinsicsSPIRV.td | 12 +++--- .../spirv-resource-intrinsic-attributes.ll | 29 ++++++++++++++ llvm/unittests/IR/IntrinsicsTest.cpp | 26 +++++++++++- 4 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 llvm/test/Assembler/spirv-resource-intrinsic-attributes.ll diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp index 1bda113143e07..9cdf650f1d560 100644 --- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp +++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp @@ -533,6 +533,19 @@ static Value *emitHlslClamp(CodeGenFunction &CGF, const CallExpr *E, return Clamp; } +static CallInst *emitConvergentResourceIntrinsicCall(CodeGenFunction &CGF, + llvm::Type *RetTy, + Intrinsic::ID ID, + ArrayRef<Value *> Args) { + SmallVector<llvm::Type *> ArgTys; + ArgTys.reserve(Args.size()); + for (Value *Arg : Args) + ArgTys.push_back(Arg->getType()); + Function *IntrFn = Intrinsic::getOrInsertDeclaration( + &CGF.CGM.getModule(), ID, RetTy, ArgTys); + return CGF.EmitRuntimeCall(IntrFn, Args); +} + static Value *emitGetDimensions(CodeGenFunction &CGF, const CallExpr *E, unsigned IntrinsicID, unsigned NumRetComps, bool HasLod) { @@ -667,13 +680,13 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID, llvm::Type *RetTy = ConvertType(E->getType()); if (E->getNumArgs() <= 4) { - return Builder.CreateIntrinsic( - RetTy, CGM.getHLSLRuntime().getSampleIntrinsic(), Args); + return emitConvergentResourceIntrinsicCall( + *this, RetTy, CGM.getHLSLRuntime().getSampleIntrinsic(), Args); } Args.push_back(emitHlslClamp(*this, E, 4)); - return Builder.CreateIntrinsic( - RetTy, CGM.getHLSLRuntime().getSampleClampIntrinsic(), Args); + return emitConvergentResourceIntrinsicCall( + *this, RetTy, CGM.getHLSLRuntime().getSampleClampIntrinsic(), Args); } case Builtin::BI__builtin_hlsl_resource_sample_bias: { Value *HandleOp = EmitScalarExpr(E->getArg(0)); @@ -692,13 +705,14 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID, Args.push_back(emitHlslOffset(*this, E, 4, getOffsetType(CGM, RT))); llvm::Type *RetTy = ConvertType(E->getType()); - if (E->getNumArgs() <= 5) - return Builder.CreateIntrinsic( - RetTy, CGM.getHLSLRuntime().getSampleBiasIntrinsic(), Args); + if (E->getNumArgs() <= 5) { + return emitConvergentResourceIntrinsicCall( + *this, RetTy, CGM.getHLSLRuntime().getSampleBiasIntrinsic(), Args); + } Args.push_back(emitHlslClamp(*this, E, 5)); - return Builder.CreateIntrinsic( - RetTy, CGM.getHLSLRuntime().getSampleBiasClampIntrinsic(), Args); + return emitConvergentResourceIntrinsicCall( + *this, RetTy, CGM.getHLSLRuntime().getSampleBiasClampIntrinsic(), Args); } case Builtin::BI__builtin_hlsl_resource_sample_grad: { Value *HandleOp = EmitScalarExpr(E->getArg(0)); @@ -827,8 +841,8 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID, Value *SamplerOp = EmitScalarExpr(E->getArg(1)); Value *CoordOp = EmitScalarExpr(E->getArg(2)); - return Builder.CreateIntrinsic( - ConvertType(E->getType()), + return emitConvergentResourceIntrinsicCall( + *this, ConvertType(E->getType()), CGM.getHLSLRuntime().getCalculateLodIntrinsic(), {HandleOp, SamplerOp, CoordOp}); } @@ -837,8 +851,8 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID, Value *SamplerOp = EmitScalarExpr(E->getArg(1)); Value *CoordOp = EmitScalarExpr(E->getArg(2)); - return Builder.CreateIntrinsic( - ConvertType(E->getType()), + return emitConvergentResourceIntrinsicCall( + *this, ConvertType(E->getType()), CGM.getHLSLRuntime().getCalculateLodUnclampedIntrinsic(), {HandleOp, SamplerOp, CoordOp}); } diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td index d948ef78b9584..b6debb2a0b74a 100644 --- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td +++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td @@ -245,25 +245,25 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty] def int_spv_resource_sample : DefaultAttrsIntrinsic< [llvm_any_ty], [llvm_any_ty, llvm_any_ty, llvm_any_ty, llvm_any_ty], - [IntrReadMem]>; + [IntrReadMem, IntrConvergent]>; def int_spv_resource_sample_clamp : DefaultAttrsIntrinsic<[llvm_any_ty], [llvm_any_ty, llvm_any_ty, llvm_any_ty, llvm_any_ty, llvm_float_ty], - [IntrReadMem]>; + [IntrReadMem, IntrConvergent]>; def int_spv_resource_samplebias : DefaultAttrsIntrinsic<[llvm_any_ty], [llvm_any_ty, llvm_any_ty, llvm_any_ty, llvm_float_ty, llvm_any_ty], - [IntrReadMem]>; + [IntrReadMem, IntrConvergent]>; def int_spv_resource_samplebias_clamp : DefaultAttrsIntrinsic<[llvm_any_ty], [llvm_any_ty, llvm_any_ty, llvm_any_ty, llvm_float_ty, llvm_any_ty, llvm_float_ty], - [IntrReadMem]>; + [IntrReadMem, IntrConvergent]>; def int_spv_resource_samplegrad : DefaultAttrsIntrinsic<[llvm_any_ty], @@ -311,12 +311,12 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty] def int_spv_resource_calculate_lod : DefaultAttrsIntrinsic<[llvm_any_ty], [llvm_any_ty, llvm_any_ty, llvm_any_ty], - [IntrReadMem]>; + [IntrReadMem, IntrConvergent]>; def int_spv_resource_calculate_lod_unclamped : DefaultAttrsIntrinsic<[llvm_any_ty], [llvm_any_ty, llvm_any_ty, llvm_any_ty], - [IntrReadMem]>; + [IntrReadMem, IntrConvergent]>; def int_spv_resource_gather : DefaultAttrsIntrinsic<[llvm_any_ty], diff --git a/llvm/test/Assembler/spirv-resource-intrinsic-attributes.ll b/llvm/test/Assembler/spirv-resource-intrinsic-attributes.ll new file mode 100644 index 0000000000000..6cb690411d8b0 --- /dev/null +++ b/llvm/test/Assembler/spirv-resource-intrinsic-attributes.ll @@ -0,0 +1,29 @@ +; RUN: llvm-as < %s | llvm-dis | FileCheck %s + +; Verify SPIR-V resource intrinsics that use implicit derivatives are marked +; convergent so LLVM does not sink them into divergent control flow. + +; CHECK: declare float @llvm.spv.resource.calculate.lod.f32.{{.*}} [[CONVERGENT:#[0-9]+]] +declare float @llvm.spv.resource.calculate.lod.f32.tspirv.Image_f32_1_2_0_0_1_0t.tspirv.Samplert.v2f32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0), target("spirv.Sampler"), <2 x float>) + +; CHECK: declare float @llvm.spv.resource.calculate.lod.unclamped.f32.{{.*}} [[CONVERGENT]] +declare float @llvm.spv.resource.calculate.lod.unclamped.f32.tspirv.Image_f32_1_2_0_0_1_0t.tspirv.Samplert.v2f32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0), target("spirv.Sampler"), <2 x float>) + +; CHECK: declare <4 x float> @llvm.spv.resource.sample.v4f32.{{.*}} [[CONVERGENT]] +declare <4 x float> @llvm.spv.resource.sample.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.tspirv.Samplert.v2f32.v2i32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0), target("spirv.Sampler"), <2 x float>, <2 x i32>) + +; CHECK: declare <4 x float> @llvm.spv.resource.sample.clamp.v4f32.{{.*}} [[CONVERGENT]] +declare <4 x float> @llvm.spv.resource.sample.clamp.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.tspirv.Samplert.v2f32.v2i32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0), target("spirv.Sampler"), <2 x float>, <2 x i32>, float) + +; CHECK: declare <4 x float> @llvm.spv.resource.samplebias.v4f32.{{.*}} [[CONVERGENT]] +declare <4 x float> @llvm.spv.resource.samplebias.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.tspirv.Samplert.v2f32.v2i32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0), target("spirv.Sampler"), <2 x float>, float, <2 x i32>) + +; CHECK: declare <4 x float> @llvm.spv.resource.samplebias.clamp.v4f32.{{.*}} [[CONVERGENT]] +declare <4 x float> @llvm.spv.resource.samplebias.clamp.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.tspirv.Samplert.v2f32.v2i32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0), target("spirv.Sampler"), <2 x float>, float, <2 x i32>, float) + +; Explicit-derivative sampling should remain non-convergent. +; CHECK: declare <4 x float> @llvm.spv.resource.samplegrad.v4f32.{{.*}} [[NON_CONVERGENT:#[0-9]+]] +declare <4 x float> @llvm.spv.resource.samplegrad.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.tspirv.Samplert.v3f32.v2f32.v2f32.v2i32(target("spirv.Image", float, 1, 2, 0, 0, 1, 0), target("spirv.Sampler"), <3 x float>, <2 x float>, <2 x float>, <2 x i32>) + +; CHECK: attributes [[CONVERGENT]] = { convergent nocallback nofree nosync nounwind willreturn memory(read) } +; CHECK: attributes [[NON_CONVERGENT]] = { nocallback nofree nosync nounwind willreturn memory(read) } diff --git a/llvm/unittests/IR/IntrinsicsTest.cpp b/llvm/unittests/IR/IntrinsicsTest.cpp index d502bf591b7fa..c2a9fc5316fff 100644 --- a/llvm/unittests/IR/IntrinsicsTest.cpp +++ b/llvm/unittests/IR/IntrinsicsTest.cpp @@ -23,6 +23,7 @@ #include "llvm/IR/IntrinsicsPowerPC.h" #include "llvm/IR/IntrinsicsRISCV.h" #include "llvm/IR/IntrinsicsS390.h" +#include "llvm/IR/IntrinsicsSPIRV.h" #include "llvm/IR/IntrinsicsX86.h" #include "llvm/IR/Module.h" #include "gtest/gtest.h" @@ -189,8 +190,31 @@ TEST_F(IntrinsicsTest, InstrProfInheritance) { } } +TEST(IntrinsicAttributes, SPIRVResourceImplicitDerivativeIntrinsicsAreConvergent) { + using namespace Intrinsic; + LLVMContext Context; + static constexpr ID ConvergentResourceIntrinsics[] = { + spv_resource_sample, + spv_resource_sample_clamp, + spv_resource_samplebias, + spv_resource_samplebias_clamp, + spv_resource_calculate_lod, + spv_resource_calculate_lod_unclamped, + }; + for (ID IntrID : ConvergentResourceIntrinsics) { + AttributeSet AS = getFnAttributes(Context, IntrID); + EXPECT_TRUE(AS.hasAttribute(Attribute::Convergent)) + << "Intrinsic " << getName(IntrID) << " should be convergent"; + } + + AttributeSet SampleGradAttrs = getFnAttributes(Context, spv_resource_samplegrad); + EXPECT_FALSE(SampleGradAttrs.hasAttribute(Attribute::Convergent)) + << "Intrinsic " << getName(spv_resource_samplegrad) + << " should not be convergent"; +} + // Check that getFnAttributes for intrinsics that do not have any function -// attributes correcty returns an empty set. +// attributes correctly returns an empty set. TEST(IntrinsicAttributes, TestGetFnAttributesBug) { using namespace Intrinsic; LLVMContext Context; >From 19e3b539dde0ff0c49b1bf5a3c0a51de1f35d464 Mon Sep 17 00:00:00 2001 From: Tim Corringham <[email protected]> Date: Wed, 22 Jul 2026 14:54:59 +0100 Subject: [PATCH 2/2] Fix clang-format errors Tidy up formatting errors - no functional change. --- clang/lib/CodeGen/CGHLSLBuiltins.cpp | 4 ++-- llvm/unittests/IR/IntrinsicsTest.cpp | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp index 9cdf650f1d560..2187f9de315f2 100644 --- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp +++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp @@ -541,8 +541,8 @@ static CallInst *emitConvergentResourceIntrinsicCall(CodeGenFunction &CGF, ArgTys.reserve(Args.size()); for (Value *Arg : Args) ArgTys.push_back(Arg->getType()); - Function *IntrFn = Intrinsic::getOrInsertDeclaration( - &CGF.CGM.getModule(), ID, RetTy, ArgTys); + Function *IntrFn = Intrinsic::getOrInsertDeclaration(&CGF.CGM.getModule(), ID, + RetTy, ArgTys); return CGF.EmitRuntimeCall(IntrFn, Args); } diff --git a/llvm/unittests/IR/IntrinsicsTest.cpp b/llvm/unittests/IR/IntrinsicsTest.cpp index c2a9fc5316fff..003b9fd61ceaf 100644 --- a/llvm/unittests/IR/IntrinsicsTest.cpp +++ b/llvm/unittests/IR/IntrinsicsTest.cpp @@ -190,16 +190,14 @@ TEST_F(IntrinsicsTest, InstrProfInheritance) { } } -TEST(IntrinsicAttributes, SPIRVResourceImplicitDerivativeIntrinsicsAreConvergent) { +TEST(IntrinsicAttributes, + SPIRVResourceImplicitDerivativeIntrinsicsAreConvergent) { using namespace Intrinsic; LLVMContext Context; static constexpr ID ConvergentResourceIntrinsics[] = { - spv_resource_sample, - spv_resource_sample_clamp, - spv_resource_samplebias, - spv_resource_samplebias_clamp, - spv_resource_calculate_lod, - spv_resource_calculate_lod_unclamped, + spv_resource_sample, spv_resource_sample_clamp, + spv_resource_samplebias, spv_resource_samplebias_clamp, + spv_resource_calculate_lod, spv_resource_calculate_lod_unclamped, }; for (ID IntrID : ConvergentResourceIntrinsics) { AttributeSet AS = getFnAttributes(Context, IntrID); @@ -207,7 +205,8 @@ TEST(IntrinsicAttributes, SPIRVResourceImplicitDerivativeIntrinsicsAreConvergent << "Intrinsic " << getName(IntrID) << " should be convergent"; } - AttributeSet SampleGradAttrs = getFnAttributes(Context, spv_resource_samplegrad); + AttributeSet SampleGradAttrs = + getFnAttributes(Context, spv_resource_samplegrad); EXPECT_FALSE(SampleGradAttrs.hasAttribute(Attribute::Convergent)) << "Intrinsic " << getName(spv_resource_samplegrad) << " should not be convergent"; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
