https://github.com/bob80905 updated https://github.com/llvm/llvm-project/pull/222166
>From 5e9f823af5c9efb3c9ca09135dfcda4e22b515ec Mon Sep 17 00:00:00 2001 From: Joshua Batista <[email protected]> Date: Tue, 8 Sep 2026 13:36:59 -0700 Subject: [PATCH 1/4] First attempt implementing InterlockedCompareStoreFloatBitwise --- clang/include/clang/Basic/Builtins.td | 7 ++ clang/lib/CodeGen/CGHLSLBuiltins.cpp | 14 ++- clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 5 + clang/lib/Sema/HLSLExternalSemaSource.cpp | 15 ++- clang/lib/Sema/SemaHLSL.cpp | 22 ++-- .../InterlockedCompareStoreFloatBitwise.hlsl | 35 ++++++ .../builtins/RWBuffer-Interlocked.hlsl | 6 ++ ...r-InterlockedCompareStoreFloatBitwise.hlsl | 27 +++++ ...r-InterlockedCompareStoreFloatBitwise.hlsl | 21 ++++ ...erlockedCompareStoreFloatBitwise-sm60.hlsl | 39 +++++++ ...lockedCompareStoreFloatBitwise-errors.hlsl | 100 ++++++++++++++++++ .../ResourceAtomicCompareStoreFloat.ll | 30 ++++++ 12 files changed, 311 insertions(+), 10 deletions(-) create mode 100644 clang/test/CodeGenHLSL/builtins/InterlockedCompareStoreFloatBitwise.hlsl create mode 100644 clang/test/CodeGenHLSL/builtins/RWByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl create mode 100644 clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl create mode 100644 clang/test/SemaHLSL/BuiltIns/ByteAddressBuffer-InterlockedCompareStoreFloatBitwise-sm60.hlsl create mode 100644 clang/test/SemaHLSL/BuiltIns/InterlockedCompareStoreFloatBitwise-errors.hlsl create mode 100644 llvm/test/CodeGen/DirectX/ResourceAtomicCompareStoreFloat.ll diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index ca56c780a381f2..2993b0eb04a9d1 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -5588,6 +5588,13 @@ def HLSLInterlockedCompareStore : LangBuiltin<"HLSL_LANG"> { let Prototype = "void (...)"; } +def HLSLInterlockedCompareStoreFloatBitwise : LangBuiltin<"HLSL_LANG"> { + let Spellings = ["__builtin_hlsl_interlocked_compare_store_float_bitwise"]; + // Prevent inadvertent float -> double arg promotion. + let Attributes = [NoThrow, CustomTypeChecking]; + let Prototype = "void (...)"; +} + def HLSLInterlockedExchange : LangBuiltin<"HLSL_LANG"> { let Spellings = ["__builtin_hlsl_interlocked_exchange"]; let Attributes = [NoThrow, CustomTypeChecking]; diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp index 9eb6d80b5764f6..d0ab2a5bf8bdfc 100644 --- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp +++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp @@ -385,6 +385,17 @@ static Value *handleInterlockedCompareOp(CodeGenFunction &CGF, Value *Compare = CGF.EmitScalarExpr(E->getArg(1)); Value *Val = CGF.EmitScalarExpr(E->getArg(2)); + // `cmpxchg` takes an integer or a pointer, so the float-bitwise operations + // work on the bit pattern of the float. This is what those operations mean, + // and DXIL and SPIR-V both need the integer form. + if (Compare->getType()->isFloatingPointTy()) { + llvm::Type *IntTy = + CGF.Builder.getIntNTy(Compare->getType()->getPrimitiveSizeInBits()); + Compare = CGF.Builder.CreateBitCast(Compare, IntTy); + Val = CGF.Builder.CreateBitCast(Val, IntTy); + DestAddr = DestAddr.withElementType(IntTy); + } + Value *Pair = CGF.Builder.CreateAtomicCmpXchg( DestAddr, Compare, Val, llvm::AtomicOrdering::Monotonic, llvm::AtomicOrdering::Monotonic, getHLSLAtomicScope(CGF, DestLV)); @@ -1523,7 +1534,8 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID, return handleInterlockedOp(*this, E, llvm::AtomicRMWInst::And); } case Builtin::BI__builtin_hlsl_interlocked_compare_exchange: - case Builtin::BI__builtin_hlsl_interlocked_compare_store: { + case Builtin::BI__builtin_hlsl_interlocked_compare_store: + case Builtin::BI__builtin_hlsl_interlocked_compare_store_float_bitwise: { return handleInterlockedCompareOp(*this, E); } case Builtin::BI__builtin_hlsl_interlocked_exchange: { diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp index df1ad894e2e976..50b6cee74de3c3 100644 --- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp +++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp @@ -1769,6 +1769,11 @@ BuiltinTypeDeclBuilder::addByteAddressBufferInterlockedMethods() { addByteAddressBufferInterlockedCompareMethod( "InterlockedCompareStore", AST.UnsignedIntTy, "__builtin_hlsl_interlocked_compare_store"); + // The float-bitwise compare reuses the 32-bit integer DXIL operation, so it + // needs no capability bits and works from SM 6.0. + addByteAddressBufferInterlockedCompareMethod( + "InterlockedCompareStoreFloatBitwise", AST.FloatTy, + "__builtin_hlsl_interlocked_compare_store_float_bitwise"); addByteAddressBufferInterlockedMethod( "InterlockedExchange", AST.UnsignedIntTy, "__builtin_hlsl_interlocked_exchange", /*RequiresOriginalValue=*/true); diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp index 4918a3bab52b23..09369f251d97da 100644 --- a/clang/lib/Sema/HLSLExternalSemaSource.cpp +++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp @@ -944,13 +944,18 @@ static void defineHLSLInterlockedFunc(Sema &S, NamespaceDecl *NS, // Synthesize the compare-and-swap overload sets: {int, uint, int64_t, // uint64_t} x {groupshared, device}. Each function has one form only. +// The float-bitwise operations have their own names and take float only. static void defineHLSLInterlockedCompareFunc(Sema &S, NamespaceDecl *NS, StringRef FuncName, StringRef BuiltinName, - AtomicOverloadShape Shape) { + AtomicOverloadShape Shape, + bool FloatOnly = false) { ASTContext &AST = S.getASTContext(); - QualType Elems[] = {AST.IntTy, AST.UnsignedIntTy, AST.LongTy, - AST.UnsignedLongTy}; + QualType IntElems[] = {AST.IntTy, AST.UnsignedIntTy, AST.LongTy, + AST.UnsignedLongTy}; + QualType FloatElems[] = {AST.FloatTy}; + ArrayRef<QualType> Elems = + FloatOnly ? ArrayRef<QualType>(FloatElems) : ArrayRef<QualType>(IntElems); for (QualType ElemTy : Elems) for (LangAS AS : {LangAS::hlsl_groupshared, LangAS::hlsl_device}) @@ -970,6 +975,10 @@ void HLSLExternalSemaSource::defineHLSLAtomicIntrinsics() { "InterlockedCompareStore", "__builtin_hlsl_interlocked_compare_store", AtomicOverloadShape::CompareStore); + defineHLSLInterlockedCompareFunc( + *SemaPtr, HLSLNamespace, "InterlockedCompareStoreFloatBitwise", + "__builtin_hlsl_interlocked_compare_store_float_bitwise", + AtomicOverloadShape::CompareStore, /*FloatOnly=*/true); defineHLSLInterlockedFunc(*SemaPtr, HLSLNamespace, "InterlockedExchange", "__builtin_hlsl_interlocked_exchange", /*RequiresOriginalValue=*/true, diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 5882c5790956f4..7ee7ec5d7a39f0 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -4124,7 +4124,7 @@ static bool CheckSamplingBuiltin(Sema &S, CallExpr *TheCall, SampleKind Kind) { } /// The `dest` types an interlocked operation accepts. Float is 32-bit only. -enum class InterlockedDest { Int, IntOrFloat }; +enum class InterlockedDest { Int, IntOrFloat, Float }; /// Check a call to an HLSL interlocked builtin. The builtins are variadic, so /// this is the only check a direct call gets. Overload resolution checks the @@ -4147,13 +4147,17 @@ static bool CheckInterlockedBuiltin(Sema &S, CallExpr *TheCall, } QualType DestTy = TheCall->getArg(0)->getType().getUnqualifiedType(); - const bool AllowsFloat = Dest == InterlockedDest::IntOrFloat; - if (!DestTy->isIntegerType() && - !(AllowsFloat && DestTy->isSpecificBuiltinType(BuiltinType::Float))) { + const bool DestIsOK = + DestTy->isSpecificBuiltinType(BuiltinType::Float) + ? Dest != InterlockedDest::Int + : Dest != InterlockedDest::Float && DestTy->isIntegerType(); + if (!DestIsOK) { S.Diag(TheCall->getArg(0)->getBeginLoc(), diag::err_builtin_invalid_arg_type) - << /*ordinal=*/1 << /*scalar*/ 1 << /*integer*/ 1 - << /*32 bit floating-point*/ (AllowsFloat ? 3 : 0) << DestTy; + << /*ordinal=*/1 << /*scalar*/ 1 + << /*integer*/ (Dest == InterlockedDest::Float ? 0 : 1) + << /*32 bit floating-point*/ (Dest == InterlockedDest::Int ? 0 : 3) + << DestTy; return true; } @@ -4707,6 +4711,12 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) { /*ReportsOriginalValue=*/false)) return true; break; + case Builtin::BI__builtin_hlsl_interlocked_compare_store_float_bitwise: + if (CheckInterlockedBuiltin(SemaRef, TheCall, /*MinArgs=*/3, /*MaxArgs=*/3, + InterlockedDest::Float, + /*ReportsOriginalValue=*/false)) + return true; + break; case Builtin::BI__builtin_hlsl_interlocked_compare_exchange: if (CheckInterlockedBuiltin(SemaRef, TheCall, /*MinArgs=*/4, /*MaxArgs=*/4, InterlockedDest::Int, diff --git a/clang/test/CodeGenHLSL/builtins/InterlockedCompareStoreFloatBitwise.hlsl b/clang/test/CodeGenHLSL/builtins/InterlockedCompareStoreFloatBitwise.hlsl new file mode 100644 index 00000000000000..c5fbb4854e85c1 --- /dev/null +++ b/clang/test/CodeGenHLSL/builtins/InterlockedCompareStoreFloatBitwise.hlsl @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -triple \ +// RUN: dxil-pc-shadermodel6.6-library %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,DXCHECK + +// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -triple \ +// RUN: spirv-pc-vulkan-library %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,SPVCHECK + +// Test basic lowering of HLSL InterlockedCompareStoreFloatBitwise to `cmpxchg +// monotonic`. `cmpxchg` takes an integer, so the float arguments become their +// bit patterns first. The operation reports nothing, so the `cmpxchg` result +// stays unused. + +groupshared float gs_f32; + +// CHECK-LABEL: define {{.*}}void @{{.*}}test_float +// CHECK: [[CMP:%.*]] = bitcast float %{{.*}} to i32 +// CHECK-NEXT: [[VAL:%.*]] = bitcast float %{{.*}} to i32 +// DXCHECK-NEXT: cmpxchg ptr addrspace(3) {{.*}}@gs_f32{{.*}}, i32 [[CMP]], i32 [[VAL]] syncscope("workgroup") monotonic monotonic +// SPVCHECK-NEXT: cmpxchg ptr addrspace(3) {{.*}}@gs_f32{{.*}}, i32 [[CMP]], i32 [[VAL]] syncscope("workgroup") monotonic monotonic +export void test_float(float cmp, float v) { + InterlockedCompareStoreFloatBitwise(gs_f32, cmp, v); +} + +// A device-address-space destination uses the "device" scope instead. +RWBuffer<float> Buf : register(u0); + +// CHECK-LABEL: define {{.*}}void @{{.*}}test_device +// CHECK: [[CMP:%.*]] = bitcast float %{{.*}} to i32 +// CHECK-NEXT: [[VAL:%.*]] = bitcast float %{{.*}} to i32 +// DXCHECK-NEXT: cmpxchg ptr %{{.*}}, i32 [[CMP]], i32 [[VAL]] syncscope("device") monotonic monotonic +// SPVCHECK-NEXT: cmpxchg ptr addrspace(11) %{{.*}}, i32 [[CMP]], i32 [[VAL]] syncscope("device") monotonic monotonic +export void test_device(float cmp, float v) { + InterlockedCompareStoreFloatBitwise(Buf[0], cmp, v); +} diff --git a/clang/test/CodeGenHLSL/builtins/RWBuffer-Interlocked.hlsl b/clang/test/CodeGenHLSL/builtins/RWBuffer-Interlocked.hlsl index 47cff714a7df75..b85e40a9b10931 100644 --- a/clang/test/CodeGenHLSL/builtins/RWBuffer-Interlocked.hlsl +++ b/clang/test/CodeGenHLSL/builtins/RWBuffer-Interlocked.hlsl @@ -19,6 +19,7 @@ RWBuffer<int> Out : register(u0); RWBuffer<uint> UOut : register(u1); +RWBuffer<float> FOut : register(u2); // CHECK-LABEL: define void @main // DXCHECK: %[[PTR1:.*]] = call {{.*}} @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0_1t.i32(target("dx.TypedBuffer", i32, 1, 0, 1) %{{.*}}, i32 %{{.*}}) @@ -43,6 +44,8 @@ RWBuffer<uint> UOut : register(u1); // DXCHECK: cmpxchg ptr %[[PTR10]], i32 1, i32 2 syncscope("device") monotonic monotonic // DXCHECK: %[[PTR11:.*]] = call {{.*}} @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0_1t.i32(target("dx.TypedBuffer", i32, 1, 0, 1) %{{.*}}, i32 %{{.*}}) // DXCHECK: cmpxchg ptr %[[PTR11]], i32 1, i32 2 syncscope("device") monotonic monotonic +// DXCHECK: %[[PTR12:.*]] = call {{.*}} @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_f32_1_0_0t.i32(target("dx.TypedBuffer", float, 1, 0, 0) %{{.*}}, i32 %{{.*}}) +// DXCHECK: cmpxchg ptr %[[PTR12]], i32 1065353216, i32 1073741824 syncscope("device") monotonic monotonic // SPVCHECK: %[[PTR1:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, i32 %{{.*}}) // SPVCHECK: atomicrmw add ptr addrspace(11) %[[PTR1]], i32 1 syncscope("device") monotonic // SPVCHECK: %[[PTR2:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, i32 %{{.*}}) @@ -65,6 +68,8 @@ RWBuffer<uint> UOut : register(u1); // SPVCHECK: cmpxchg ptr addrspace(11) %[[PTR10]], i32 1, i32 2 syncscope("device") monotonic monotonic // SPVCHECK: %[[PTR11:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, i32 %{{.*}}) // SPVCHECK: cmpxchg ptr addrspace(11) %[[PTR11]], i32 1, i32 2 syncscope("device") monotonic monotonic +// SPVCHECK: %[[PTR12:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.Image", float, {{.*}}) %{{.*}}, i32 %{{.*}}) +// SPVCHECK: cmpxchg ptr addrspace(11) %[[PTR12]], i32 1065353216, i32 1073741824 syncscope("device") monotonic monotonic [shader("compute")] [numthreads(1,1,1)] void main(uint3 id : SV_DispatchThreadID) { @@ -80,4 +85,5 @@ void main(uint3 id : SV_DispatchThreadID) { InterlockedExchange(Out[id.x], 1, orig); InterlockedCompareStore(Out[id.x], 1, 2); InterlockedCompareExchange(Out[id.x], 1, 2, orig); + InterlockedCompareStoreFloatBitwise(FOut[id.x], 1.0f, 2.0f); } diff --git a/clang/test/CodeGenHLSL/builtins/RWByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl b/clang/test/CodeGenHLSL/builtins/RWByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl new file mode 100644 index 00000000000000..ca2f2b02cdaa47 --- /dev/null +++ b/clang/test/CodeGenHLSL/builtins/RWByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl @@ -0,0 +1,27 @@ +// RUN: %clang_cc1 -std=hlsl202x -finclude-default-header -triple \ +// RUN: dxil-pc-shadermodel6.6-library %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,DXCHECK + +// RUN: %clang_cc1 -std=hlsl202x -finclude-default-header -triple \ +// RUN: spirv-pc-vulkan1.3-library %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,SPVCHECK + +// Test that the RWByteAddressBuffer::InterlockedCompareStoreFloatBitwise +// member method lowers to `resource_getpointer -> cmpxchg`, for both DXIL and +// SPIR-V targets. The float arguments become their bit patterns first, because +// `cmpxchg` takes an integer. + +RWByteAddressBuffer BAB : register(u0); + +// CHECK-LABEL: define {{.*}}void @{{.*}}test_bab_float +// DXCHECK: %[[HANDLE:.*]] = load target("dx.RawBuffer", i8, 1, 0), ptr {{.*}} +// DXCHECK: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i8_1_0t.i32(target("dx.RawBuffer", i8, 1, 0) %[[HANDLE]], i32 %{{.*}}) +// SPVCHECK: %[[HANDLE:.*]] = load target("spirv.VulkanBuffer", [0 x i8], 12, 1), ptr {{.*}} +// SPVCHECK: %[[PTR:.*]] = call ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i8_12_1t.i32(target("spirv.VulkanBuffer", [0 x i8], 12, 1) %[[HANDLE]], i32 %{{.*}}) +// CHECK: %[[CMP:.*]] = bitcast float %{{.*}} to i32 +// CHECK-NEXT: %[[VAL:.*]] = bitcast float %{{.*}} to i32 +// DXCHECK-NEXT: cmpxchg ptr %[[PTR]], i32 %[[CMP]], i32 %[[VAL]] syncscope("device") monotonic monotonic +// SPVCHECK-NEXT: cmpxchg ptr addrspace(11) %[[PTR]], i32 %[[CMP]], i32 %[[VAL]] syncscope("device") monotonic monotonic +export void test_bab_float(uint off, float cmp, float v) { + BAB.InterlockedCompareStoreFloatBitwise(off, cmp, v); +} diff --git a/clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl b/clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl new file mode 100644 index 00000000000000..5ddc6a4956f15a --- /dev/null +++ b/clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -std=hlsl202x -finclude-default-header -triple \ +// RUN: dxil-pc-shadermodel6.6-library %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,DXCHECK + +// SPIR-V codegen for RasterizerOrderedByteAddressBuffer is not implemented +// yet (asserts in clang/lib/CodeGen/Targets/SPIR.cpp on +// `!ResAttrs.IsROV && "Rasterizer order views not implemented for SPIR-V yet"`). +// Add a `spirv-pc-vulkan1.3-library` RUN line here when SPIR-V ROV support +// lands. + +RasterizerOrderedByteAddressBuffer ROVB : register(u1); + +// CHECK-LABEL: define void @{{.*}}test_rovb_float +// DXCHECK: %[[HANDLE:.*]] = load target("dx.RawBuffer", i8, 1, 1), ptr {{.*}} +// DXCHECK: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i8_1_1t.i32(target("dx.RawBuffer", i8, 1, 1) %[[HANDLE]], i32 %{{.*}}) +// DXCHECK: %[[CMP:.*]] = bitcast float %{{.*}} to i32 +// DXCHECK-NEXT: %[[VAL:.*]] = bitcast float %{{.*}} to i32 +// DXCHECK-NEXT: cmpxchg ptr %[[PTR]], i32 %[[CMP]], i32 %[[VAL]] syncscope("device") monotonic monotonic +export void test_rovb_float(uint off, float cmp, float v) { + ROVB.InterlockedCompareStoreFloatBitwise(off, cmp, v); +} diff --git a/clang/test/SemaHLSL/BuiltIns/ByteAddressBuffer-InterlockedCompareStoreFloatBitwise-sm60.hlsl b/clang/test/SemaHLSL/BuiltIns/ByteAddressBuffer-InterlockedCompareStoreFloatBitwise-sm60.hlsl new file mode 100644 index 00000000000000..5954884163670f --- /dev/null +++ b/clang/test/SemaHLSL/BuiltIns/ByteAddressBuffer-InterlockedCompareStoreFloatBitwise-sm60.hlsl @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -std=hlsl202x -finclude-default-header \ +// RUN: -triple dxil-pc-shadermodel6.0-library %s -fsyntax-only -verify \ +// RUN: -verify-ignore-unexpected=warning + +// The float-bitwise compare reuses the 32-bit integer DXIL operation, so it +// needs no capability bits and works from SM 6.0. The 64-bit compare-store +// needs SM 6.6. This file checks both halves, so it proves the two are gated +// differently. + +RWByteAddressBuffer BAB : register(u0); +RasterizerOrderedByteAddressBuffer ROVB : register(u1); +groupshared float gs_f32; +groupshared uint64_t gs_u64; + +void sm60_bab_float_bitwise_ok(uint off, float cmp, float v) { + BAB.InterlockedCompareStoreFloatBitwise(off, cmp, v); +} + +void sm60_rovb_float_bitwise_ok(uint off, float cmp, float v) { + ROVB.InterlockedCompareStoreFloatBitwise(off, cmp, v); +} + +void sm60_free_function_ok(float cmp, float v) { + InterlockedCompareStoreFloatBitwise(gs_f32, cmp, v); +} + +void sm60_direct_builtin_ok(float cmp, float v) { + __builtin_hlsl_interlocked_compare_store_float_bitwise(gs_f32, cmp, v); +} + +void sm60_no_bab_compare_store64(uint off, uint64_t cmp, uint64_t v) { + BAB.InterlockedCompareStore64(off, cmp, v); + // expected-error@-1 {{no member named 'InterlockedCompareStore64' in 'hlsl::RWByteAddressBuffer'}} +} + +void sm60_no_direct_builtin_u64(uint64_t cmp, uint64_t v) { + __builtin_hlsl_interlocked_compare_store(gs_u64, cmp, v); + // expected-error@-1 {{'__builtin_hlsl_interlocked_compare_store' requires shader model 6.6 or newer}} +} diff --git a/clang/test/SemaHLSL/BuiltIns/InterlockedCompareStoreFloatBitwise-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/InterlockedCompareStoreFloatBitwise-errors.hlsl new file mode 100644 index 00000000000000..f291c6264f0999 --- /dev/null +++ b/clang/test/SemaHLSL/BuiltIns/InterlockedCompareStoreFloatBitwise-errors.hlsl @@ -0,0 +1,100 @@ +// RUN: %clang_cc1 -std=hlsl202x -finclude-default-header \ +// RUN: -triple dxil-pc-shadermodel6.6-library %s -emit-llvm-only \ +// RUN: -disable-llvm-passes -verify + +// InterlockedCompareStoreFloatBitwise compares the bit pattern of a 32-bit +// float, so it is provided as a float-only overload set (groupshared/device). +// It reports nothing, so it has a single 3-argument form and no out parameter. + +groupshared float gs_f32; +groupshared int gs_i32; +groupshared double gs_f64; +groupshared half gs_f16; +struct S { float x; }; +groupshared S gs_s; + +void too_few(float cmp) { + InterlockedCompareStoreFloatBitwise(gs_f32, cmp); // expected-error{{no matching function for call to 'InterlockedCompareStoreFloatBitwise'}} + // expected-note@*:* 2 {{candidate function}} +} + +void too_many(float cmp, float v, float extra) { + InterlockedCompareStoreFloatBitwise(gs_f32, cmp, v, extra); // expected-error{{no matching function for call to 'InterlockedCompareStoreFloatBitwise'}} + // expected-note@*:* 2 {{candidate function}} +} + +void local_dest(float cmp, float v) { + float dest; + InterlockedCompareStoreFloatBitwise(dest, cmp, v); // expected-error{{no matching function for call to 'InterlockedCompareStoreFloatBitwise'}} + // expected-note@*:* 2 {{candidate function}} +} + +// The bitwise compare is defined for 32-bit float alone, so there is no +// integer, half or double overload. +void int_dest(int cmp, int v) { + InterlockedCompareStoreFloatBitwise(gs_i32, cmp, v); // expected-error{{no matching function for call to 'InterlockedCompareStoreFloatBitwise'}} + // expected-note@*:* 2 {{candidate function}} +} + +void double_dest(double cmp, double v) { + InterlockedCompareStoreFloatBitwise(gs_f64, cmp, v); // expected-error{{no matching function for call to 'InterlockedCompareStoreFloatBitwise'}} + // expected-note@*:* 2 {{candidate function}} +} + +void half_dest(half cmp, half v) { + InterlockedCompareStoreFloatBitwise(gs_f16, cmp, v); // expected-error{{no matching function for call to 'InterlockedCompareStoreFloatBitwise'}} + // expected-note@*:* 2 {{candidate function}} +} + +void struct_dest(float cmp, float v) { + InterlockedCompareStoreFloatBitwise(gs_s, cmp, v); // expected-error{{no matching function for call to 'InterlockedCompareStoreFloatBitwise'}} + // expected-note@*:* 2 {{candidate function}} +} + +void direct_too_few(float cmp) { + __builtin_hlsl_interlocked_compare_store_float_bitwise(gs_f32, cmp); + // expected-error@-1 {{too few arguments to function call, expected 3, have 2}} +} + +void direct_too_many(float cmp, float v, float extra) { + __builtin_hlsl_interlocked_compare_store_float_bitwise(gs_f32, cmp, v, extra); + // expected-error@-1 {{too many arguments to function call, expected 3, have 4}} +} + +void direct_integer_dest(int cmp, int v) { + __builtin_hlsl_interlocked_compare_store_float_bitwise(gs_i32, cmp, v); + // expected-error@-1 {{1st argument must be a scalar 32 bit floating-point type (was 'int')}} +} + +void direct_double_dest(double cmp, double v) { + __builtin_hlsl_interlocked_compare_store_float_bitwise(gs_f64, cmp, v); + // expected-error@-1 {{1st argument must be a scalar 32 bit floating-point type (was 'double')}} +} + +void direct_half_dest(half cmp, half v) { + __builtin_hlsl_interlocked_compare_store_float_bitwise(gs_f16, cmp, v); + // expected-error@-1 {{1st argument must be a scalar 32 bit floating-point type (was 'half')}} +} + +void direct_non_scalar_dest() { + S local_s; + __builtin_hlsl_interlocked_compare_store_float_bitwise(local_s, 1.0f, 2.0f); + // expected-error@-1 {{1st argument must be a scalar 32 bit floating-point type (was 'S')}} +} + +void direct_nonlvalue_dest(float cmp, float v) { + __builtin_hlsl_interlocked_compare_store_float_bitwise(1.0f, cmp, v); + // expected-error@-1 {{cannot bind non-lvalue argument '1.F' to out parameter}} +} + +void direct_default_as_dest(float cmp, float v) { + float local; + __builtin_hlsl_interlocked_compare_store_float_bitwise(local, cmp, v); + // expected-error@-1 {{1st argument to atomic builtin must reference groupshared or device memory (was 'float')}} +} + +// The last argument is the new value rather than an out parameter, so an +// rvalue is accepted here. +void direct_rvalue_value_ok() { + __builtin_hlsl_interlocked_compare_store_float_bitwise(gs_f32, 1.0f, 2.0f); +} diff --git a/llvm/test/CodeGen/DirectX/ResourceAtomicCompareStoreFloat.ll b/llvm/test/CodeGen/DirectX/ResourceAtomicCompareStoreFloat.ll new file mode 100644 index 00000000000000..03eff2ade22ea9 --- /dev/null +++ b/llvm/test/CodeGen/DirectX/ResourceAtomicCompareStoreFloat.ll @@ -0,0 +1,30 @@ +; RUN: opt -S -dxil-resource-access -dxil-op-lower -mtriple=dxil-pc-shadermodel6.6-compute %s | FileCheck %s + +; InterlockedCompareStoreFloatBitwise compares the bit pattern of a float, so +; clang emits an i32 `cmpxchg` on a float resource. Lowering must key the DXIL +; op off the operand type rather than the resource element type. + +target triple = "dxil-pc-shadermodel6.6-compute" + +; CHECK-LABEL: define void @typed_buffer_float_compare_store +define void @typed_buffer_float_compare_store(i32 %index, i32 %cmp, i32 %val) { + %buffer = call target("dx.TypedBuffer", float, 1, 0, 0) + @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr null) + %ptr = call ptr @llvm.dx.resource.getpointer( + target("dx.TypedBuffer", float, 1, 0, 0) %buffer, i32 %index) + ; CHECK: call i32 @dx.op.atomicCompareExchange.i32(i32 79, %dx.types.Handle %{{.*}}, i32 %index, i32 poison, i32 poison, i32 %cmp, i32 %val) + %old = cmpxchg ptr %ptr, i32 %cmp, i32 %val monotonic monotonic + ret void +} + +; The raw buffer path carries no element type, so the same i32 op applies. +; CHECK-LABEL: define void @raw_buffer_float_compare_store +define void @raw_buffer_float_compare_store(i32 %offset, i32 %cmp, i32 %val) { + %buffer = call target("dx.RawBuffer", i8, 1, 0, 0) + @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr null) + %ptr = call ptr @llvm.dx.resource.getpointer( + target("dx.RawBuffer", i8, 1, 0, 0) %buffer, i32 %offset) + ; CHECK: call i32 @dx.op.atomicCompareExchange.i32(i32 79, %dx.types.Handle %{{.*}}, i32 %offset, i32 poison, i32 poison, i32 %cmp, i32 %val) + %old = cmpxchg ptr %ptr, i32 %cmp, i32 %val monotonic monotonic + ret void +} >From 1a222f7b979272f6e250968621a27cea316dfc12 Mon Sep 17 00:00:00 2001 From: Joshua Batista <[email protected]> Date: Thu, 17 Sep 2026 11:30:02 -0700 Subject: [PATCH 2/4] Add InterlockedCompareStoreFloatBitwise coverage to the texture test --- clang/test/CodeGenHLSL/builtins/RWTexture-Interlocked.hlsl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clang/test/CodeGenHLSL/builtins/RWTexture-Interlocked.hlsl b/clang/test/CodeGenHLSL/builtins/RWTexture-Interlocked.hlsl index 6fc44101d48793..69972462f4b9c6 100644 --- a/clang/test/CodeGenHLSL/builtins/RWTexture-Interlocked.hlsl +++ b/clang/test/CodeGenHLSL/builtins/RWTexture-Interlocked.hlsl @@ -44,6 +44,8 @@ RWTexture2D<float> FOut : register(u2); // DXCHECK: cmpxchg ptr %[[PTR10]], i32 1, i32 2 syncscope("device") monotonic monotonic // DXCHECK: %[[PTR11:.*]] = call {{.*}} @llvm.dx.resource.getpointer.{{.*}}(target("dx.Texture", i32, 1, 0, 1, 2) %{{.*}}, <2 x i32> %{{.*}}) // DXCHECK: cmpxchg ptr %[[PTR11]], i32 1, i32 2 syncscope("device") monotonic monotonic +// DXCHECK: %[[PTR12:.*]] = call {{.*}} @llvm.dx.resource.getpointer.{{.*}}(target("dx.Texture", float, 1, 0, 0, 2) %{{.*}}, <2 x i32> %{{.*}}) +// DXCHECK: cmpxchg ptr %[[PTR12]], i32 1065353216, i32 1073741824 syncscope("device") monotonic monotonic // SPVCHECK: %[[PTR1:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, <2 x i32> %{{.*}}) // SPVCHECK: atomicrmw add ptr addrspace(11) %[[PTR1]], i32 1 syncscope("device") monotonic // SPVCHECK: %[[PTR2:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, <2 x i32> %{{.*}}) @@ -66,6 +68,8 @@ RWTexture2D<float> FOut : register(u2); // SPVCHECK: cmpxchg ptr addrspace(11) %[[PTR10]], i32 1, i32 2 syncscope("device") monotonic monotonic // SPVCHECK: %[[PTR11:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, <2 x i32> %{{.*}}) // SPVCHECK: cmpxchg ptr addrspace(11) %[[PTR11]], i32 1, i32 2 syncscope("device") monotonic monotonic +// SPVCHECK: %[[PTR12:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.Image", float, {{.*}}) %{{.*}}, <2 x i32> %{{.*}}) +// SPVCHECK: cmpxchg ptr addrspace(11) %[[PTR12]], i32 1065353216, i32 1073741824 syncscope("device") monotonic monotonic [shader("compute")] [numthreads(1,1,1)] void main(uint3 id : SV_DispatchThreadID) { @@ -82,4 +86,7 @@ void main(uint3 id : SV_DispatchThreadID) { InterlockedExchange(FOut[id.xy], 1.0f, FOrig); InterlockedCompareStore(Out[id.xy], 1, 2); InterlockedCompareExchange(Out[id.xy], 1, 2, Orig); + // The float bitwise forms compare the bit pattern, so the operands show up + // as the i32 encodings of 1.0f and 2.0f. + InterlockedCompareStoreFloatBitwise(FOut[id.xy], 1.0f, 2.0f); } >From ea6d1ac9d76b9acce09710b9421ec914e7741928 Mon Sep 17 00:00:00 2001 From: Joshua Batista <[email protected]> Date: Thu, 17 Sep 2026 14:50:46 -0700 Subject: [PATCH 3/4] Remove the comment on the InterlockedCompareStoreFloatBitwise builtin --- clang/include/clang/Basic/Builtins.td | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 2993b0eb04a9d1..c9a4dd3c4d6533 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -5590,7 +5590,6 @@ def HLSLInterlockedCompareStore : LangBuiltin<"HLSL_LANG"> { def HLSLInterlockedCompareStoreFloatBitwise : LangBuiltin<"HLSL_LANG"> { let Spellings = ["__builtin_hlsl_interlocked_compare_store_float_bitwise"]; - // Prevent inadvertent float -> double arg promotion. let Attributes = [NoThrow, CustomTypeChecking]; let Prototype = "void (...)"; } >From 39f6e41292aea85cb92b867a60f2bd52525c1c77 Mon Sep 17 00:00:00 2001 From: Joshua Batista <[email protected]> Date: Fri, 25 Sep 2026 13:07:18 -0700 Subject: [PATCH 4/4] address hekota, add a float overload helper, merge check lines, link the issue --- clang/lib/Sema/HLSLExternalSemaSource.cpp | 28 ++++++++++++------- .../InterlockedCompareStoreFloatBitwise.hlsl | 3 +- ...r-InterlockedCompareStoreFloatBitwise.hlsl | 6 ++-- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp index 09369f251d97da..200c8001fbe69c 100644 --- a/clang/lib/Sema/HLSLExternalSemaSource.cpp +++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp @@ -944,24 +944,32 @@ static void defineHLSLInterlockedFunc(Sema &S, NamespaceDecl *NS, // Synthesize the compare-and-swap overload sets: {int, uint, int64_t, // uint64_t} x {groupshared, device}. Each function has one form only. -// The float-bitwise operations have their own names and take float only. static void defineHLSLInterlockedCompareFunc(Sema &S, NamespaceDecl *NS, StringRef FuncName, StringRef BuiltinName, - AtomicOverloadShape Shape, - bool FloatOnly = false) { + AtomicOverloadShape Shape) { ASTContext &AST = S.getASTContext(); - QualType IntElems[] = {AST.IntTy, AST.UnsignedIntTy, AST.LongTy, - AST.UnsignedLongTy}; - QualType FloatElems[] = {AST.FloatTy}; - ArrayRef<QualType> Elems = - FloatOnly ? ArrayRef<QualType>(FloatElems) : ArrayRef<QualType>(IntElems); + QualType Elems[] = {AST.IntTy, AST.UnsignedIntTy, AST.LongTy, + AST.UnsignedLongTy}; for (QualType ElemTy : Elems) for (LangAS AS : {LangAS::hlsl_groupshared, LangAS::hlsl_device}) buildAtomicOverload(S, NS, FuncName, BuiltinName, ElemTy, AS, Shape); } +// The float-bitwise compare-and-swap functions have their own names and take +// float only. +static void defineHLSLInterlockedCompareFuncFloat(Sema &S, NamespaceDecl *NS, + StringRef FuncName, + StringRef BuiltinName, + AtomicOverloadShape Shape) { + ASTContext &AST = S.getASTContext(); + buildAtomicOverload(S, NS, FuncName, BuiltinName, AST.FloatTy, + LangAS::hlsl_groupshared, Shape); + buildAtomicOverload(S, NS, FuncName, BuiltinName, AST.FloatTy, + LangAS::hlsl_device, Shape); +} + void HLSLExternalSemaSource::defineHLSLAtomicIntrinsics() { defineHLSLInterlockedFunc(*SemaPtr, HLSLNamespace, "InterlockedAdd", "__builtin_hlsl_interlocked_add"); @@ -975,10 +983,10 @@ void HLSLExternalSemaSource::defineHLSLAtomicIntrinsics() { "InterlockedCompareStore", "__builtin_hlsl_interlocked_compare_store", AtomicOverloadShape::CompareStore); - defineHLSLInterlockedCompareFunc( + defineHLSLInterlockedCompareFuncFloat( *SemaPtr, HLSLNamespace, "InterlockedCompareStoreFloatBitwise", "__builtin_hlsl_interlocked_compare_store_float_bitwise", - AtomicOverloadShape::CompareStore, /*FloatOnly=*/true); + AtomicOverloadShape::CompareStore); defineHLSLInterlockedFunc(*SemaPtr, HLSLNamespace, "InterlockedExchange", "__builtin_hlsl_interlocked_exchange", /*RequiresOriginalValue=*/true, diff --git a/clang/test/CodeGenHLSL/builtins/InterlockedCompareStoreFloatBitwise.hlsl b/clang/test/CodeGenHLSL/builtins/InterlockedCompareStoreFloatBitwise.hlsl index c5fbb4854e85c1..e73a72d8072bcf 100644 --- a/clang/test/CodeGenHLSL/builtins/InterlockedCompareStoreFloatBitwise.hlsl +++ b/clang/test/CodeGenHLSL/builtins/InterlockedCompareStoreFloatBitwise.hlsl @@ -16,8 +16,7 @@ groupshared float gs_f32; // CHECK-LABEL: define {{.*}}void @{{.*}}test_float // CHECK: [[CMP:%.*]] = bitcast float %{{.*}} to i32 // CHECK-NEXT: [[VAL:%.*]] = bitcast float %{{.*}} to i32 -// DXCHECK-NEXT: cmpxchg ptr addrspace(3) {{.*}}@gs_f32{{.*}}, i32 [[CMP]], i32 [[VAL]] syncscope("workgroup") monotonic monotonic -// SPVCHECK-NEXT: cmpxchg ptr addrspace(3) {{.*}}@gs_f32{{.*}}, i32 [[CMP]], i32 [[VAL]] syncscope("workgroup") monotonic monotonic +// CHECK-NEXT: cmpxchg ptr addrspace(3) {{.*}}@gs_f32{{.*}}, i32 [[CMP]], i32 [[VAL]] syncscope("workgroup") monotonic monotonic export void test_float(float cmp, float v) { InterlockedCompareStoreFloatBitwise(gs_f32, cmp, v); } diff --git a/clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl b/clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl index 5ddc6a4956f15a..89162c57f416f9 100644 --- a/clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl +++ b/clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareStoreFloatBitwise.hlsl @@ -3,10 +3,8 @@ // RUN: FileCheck %s --check-prefixes=CHECK,DXCHECK // SPIR-V codegen for RasterizerOrderedByteAddressBuffer is not implemented -// yet (asserts in clang/lib/CodeGen/Targets/SPIR.cpp on -// `!ResAttrs.IsROV && "Rasterizer order views not implemented for SPIR-V yet"`). -// Add a `spirv-pc-vulkan1.3-library` RUN line here when SPIR-V ROV support -// lands. +// yet. Add a `spirv-pc-vulkan1.3-library` RUN line here when SPIR-V ROV +// support lands. See https://github.com/llvm/llvm-project/issues/124585. RasterizerOrderedByteAddressBuffer ROVB : register(u1); _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
