https://github.com/tcorringham created 
https://github.com/llvm/llvm-project/pull/211256

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

>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] [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;

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to