[clang] [llvm] [HLSL][SPIRV] Add IntrConvergent to convergent intrinsics (PR #211256)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 22 07:03:02 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-ir
Author: Tim Corringham (tcorringham)
<details>
<summary>Changes</summary>
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
---
Full diff: https://github.com/llvm/llvm-project/pull/211256.diff
4 Files Affected:
- (modified) clang/lib/CodeGen/CGHLSLBuiltins.cpp (+27-13)
- (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+6-6)
- (added) llvm/test/Assembler/spirv-resource-intrinsic-attributes.ll (+29)
- (modified) llvm/unittests/IR/IntrinsicsTest.cpp (+24-1)
``````````diff
diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
index 1bda113143e07..2187f9de315f2 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..003b9fd61ceaf 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,30 @@ 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;
``````````
</details>
https://github.com/llvm/llvm-project/pull/211256
More information about the llvm-commits
mailing list