https://github.com/Alexander-Johnston updated https://github.com/llvm/llvm-project/pull/209254
>From e0c11f9ef1e2db3a48d3154afaf4aafb80c177f0 Mon Sep 17 00:00:00 2001 From: Alexander Johnston <[email protected]> Date: Mon, 13 Jul 2026 18:36:28 +0100 Subject: [PATCH 1/2] [HLSL] Implement HLSL InterlockedXor Follows existing HLSL InterlockedOp implementations to provide InterlockedXor in HLSL, with DirectX and SPIRV support. Does not include RWByteAddressBuffer interlocked methods. --- clang/include/clang/Basic/Builtins.td | 6 ++ clang/lib/CodeGen/CGHLSLBuiltins.cpp | 5 + clang/lib/CodeGen/CGHLSLRuntime.h | 1 + clang/lib/Sema/HLSLExternalSemaSource.cpp | 2 + clang/lib/Sema/SemaHLSL.cpp | 7 +- .../CodeGenHLSL/builtins/InterlockedXor.hlsl | 59 +++++++++++ .../BuiltIns/InterlockedXor-errors.hlsl | 100 ++++++++++++++++++ llvm/include/llvm/IR/IntrinsicsDirectX.td | 4 + llvm/include/llvm/IR/IntrinsicsSPIRV.td | 4 + .../Target/DirectX/DXILIntrinsicExpansion.cpp | 4 + .../Target/SPIRV/SPIRVInstructionSelector.cpp | 2 + llvm/test/CodeGen/DirectX/InterlockedXor.ll | 52 +++++++++ .../SPIRV/hlsl-intrinsics/InterlockedXor.ll | 36 +++++++ .../hlsl-intrinsics/InterlockedXor_spv_i64.ll | 37 +++++++ 14 files changed, 316 insertions(+), 3 deletions(-) create mode 100644 clang/test/CodeGenHLSL/builtins/InterlockedXor.hlsl create mode 100644 clang/test/SemaHLSL/BuiltIns/InterlockedXor-errors.hlsl create mode 100644 llvm/test/CodeGen/DirectX/InterlockedXor.ll create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/InterlockedXor.ll create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/InterlockedXor_spv_i64.ll diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 344a712ddc585..06d559a5c3ce2 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -5545,6 +5545,12 @@ def HLSLInterlockedOr : LangBuiltin<"HLSL_LANG"> { let Prototype = "void (...)"; } +def HLSLInterlockedXor : LangBuiltin<"HLSL_LANG"> { + let Spellings = ["__builtin_hlsl_interlocked_xor"]; + let Attributes = [NoThrow]; + let Prototype = "void (...)"; +} + def HLSLWaveActiveBallot : LangBuiltin<"HLSL_LANG"> { let Spellings = ["__builtin_hlsl_wave_active_ballot"]; let Attributes = [NoThrow, Const]; diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp index 1bda113143e07..d5d0d9b09bd46 100644 --- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp +++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp @@ -1466,6 +1466,11 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID, CGM.getHLSLRuntime().getInterlockedOrIntrinsic(), "hlsl.interlocked.or"); } + case Builtin::BI__builtin_hlsl_interlocked_xor: { + return handleInterlockedOp(*this, E, + CGM.getHLSLRuntime().getInterlockedXorIntrinsic(), + "hlsl.interlocked.xor"); + } case Builtin::BI__builtin_hlsl_wave_active_ballot: { [[maybe_unused]] Value *Op = EmitScalarExpr(E->getArg(0)); assert(Op->getType()->isIntegerTy(1) && diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h index cf47b1633fd3c..0fe6a59cc5927 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -153,6 +153,7 @@ class CGHLSLRuntime { GENERATE_HLSL_INTRINSIC_FUNCTION(WaveActiveBitAnd, wave_reduce_and) GENERATE_HLSL_INTRINSIC_FUNCTION(InterlockedAdd, interlocked_add) GENERATE_HLSL_INTRINSIC_FUNCTION(InterlockedOr, interlocked_or) + GENERATE_HLSL_INTRINSIC_FUNCTION(InterlockedXor, interlocked_xor) GENERATE_HLSL_INTRINSIC_FUNCTION(WaveActiveMax, wave_reduce_max) GENERATE_HLSL_INTRINSIC_FUNCTION(WaveActiveUMax, wave_reduce_umax) GENERATE_HLSL_INTRINSIC_FUNCTION(WaveActiveMin, wave_reduce_min) diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp index 115795ca74bae..77b8550ba4c6c 100644 --- a/clang/lib/Sema/HLSLExternalSemaSource.cpp +++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp @@ -821,6 +821,8 @@ void HLSLExternalSemaSource::defineHLSLAtomicIntrinsics() { "__builtin_hlsl_interlocked_add"); defineHLSLInterlockedFunc(*SemaPtr, HLSLNamespace, "InterlockedOr", "__builtin_hlsl_interlocked_or"); + defineHLSLInterlockedFunc(*SemaPtr, HLSLNamespace, "InterlockedXor", + "__builtin_hlsl_interlocked_xor"); } void HLSLExternalSemaSource::onCompletion(CXXRecordDecl *Record, diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index ae6cbce2f890c..fa8921b4b451e 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -4541,10 +4541,11 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) { break; } case Builtin::BI__builtin_hlsl_interlocked_add: - case Builtin::BI__builtin_hlsl_interlocked_or: { + case Builtin::BI__builtin_hlsl_interlocked_or: + case Builtin::BI__builtin_hlsl_interlocked_xor: { // The builtin's prototype in Builtins.td is `void (...)`, so direct calls - // to `__builtin_hlsl_interlocked_add` bypass argument checking entirely. - // When reached via the synthesized `InterlockedAdd` overload set in + // to `__builtin_hlsl_interlocked_op` bypass argument checking entirely. + // When reached via the synthesized `InterlockedOp` overload set in // HLSLExternalSemaSource, overload resolution has already enforced the // argument count, integer-type matching, and the address-space requirement // on `dest`. The checks below are a safety net for callers that invoke the diff --git a/clang/test/CodeGenHLSL/builtins/InterlockedXor.hlsl b/clang/test/CodeGenHLSL/builtins/InterlockedXor.hlsl new file mode 100644 index 0000000000000..3935ddff52702 --- /dev/null +++ b/clang/test/CodeGenHLSL/builtins/InterlockedXor.hlsl @@ -0,0 +1,59 @@ +// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -triple \ +// RUN: dxil-pc-shadermodel6.6-compute %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-compute %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,SPVCHECK + +// Test basic lowering of HLSL InterlockedXor to the target intrinsic. + +groupshared int gs_i32; +groupshared uint gs_u32; +groupshared int64_t gs_i64; +groupshared uint64_t gs_u64; + +// CHECK-LABEL: define {{(dso_local |hidden |internal |protected |spir_func )*}}void @{{.*}}test_int_2arg +// DXCHECK: call i32 @llvm.dx.interlocked.xor.i32.p3(ptr addrspace(3) {{.*}}@gs_i32{{.*}}, i32 %{{.*}}) +// SPVCHECK: call spir_func i32 @llvm.spv.interlocked.xor.i32.p3(ptr addrspace(3) {{.*}}@gs_i32{{.*}}, i32 %{{.*}}) +export void test_int_2arg(int v) { + InterlockedXor(gs_i32, v); +} + +// CHECK-LABEL: define {{(dso_local |hidden |internal |protected |spir_func )*}}void @{{.*}}test_uint_2arg +// DXCHECK: call i32 @llvm.dx.interlocked.xor.i32.p3(ptr addrspace(3) {{.*}}@gs_u32{{.*}}, i32 %{{.*}}) +// SPVCHECK: call spir_func i32 @llvm.spv.interlocked.xor.i32.p3(ptr addrspace(3) {{.*}}@gs_u32{{.*}}, i32 %{{.*}}) +export void test_uint_2arg(uint v) { + InterlockedXor(gs_u32, v); +} + +// CHECK-LABEL: define {{(dso_local |hidden |internal |protected |spir_func )*}}void @{{.*}}test_int_3arg +// DXCHECK: %[[R:.*]] = call i32 @llvm.dx.interlocked.xor.i32.p3(ptr addrspace(3) {{.*}}@gs_i32{{.*}}, i32 %{{.*}}) +// SPVCHECK: %[[R:.*]] = call spir_func i32 @llvm.spv.interlocked.xor.i32.p3(ptr addrspace(3) {{.*}}@gs_i32{{.*}}, i32 %{{.*}}) +// CHECK: store i32 %[[R]], ptr {{.*}} +export void test_int_3arg(int v, out int orig) { + InterlockedXor(gs_i32, v, orig); +} + +// CHECK-LABEL: define {{(dso_local |hidden |internal |protected |spir_func )*}}void @{{.*}}test_uint_3arg +// DXCHECK: %[[R:.*]] = call i32 @llvm.dx.interlocked.xor.i32.p3(ptr addrspace(3) {{.*}}@gs_u32{{.*}}, i32 %{{.*}}) +// SPVCHECK: %[[R:.*]] = call spir_func i32 @llvm.spv.interlocked.xor.i32.p3(ptr addrspace(3) {{.*}}@gs_u32{{.*}}, i32 %{{.*}}) +// CHECK: store i32 %[[R]], ptr {{.*}} +export void test_uint_3arg(uint v, out uint orig) { + InterlockedXor(gs_u32, v, orig); +} + +// CHECK-LABEL: define {{(dso_local |hidden |internal |protected |spir_func )*}}void @{{.*}}test_int64_2arg +// DXCHECK: call i64 @llvm.dx.interlocked.xor.i64.p3(ptr addrspace(3) {{.*}}@gs_i64{{.*}}, i64 %{{.*}}) +// SPVCHECK: call spir_func i64 @llvm.spv.interlocked.xor.i64.p3(ptr addrspace(3) {{.*}}@gs_i64{{.*}}, i64 %{{.*}}) +export void test_int64_2arg(int64_t v) { + InterlockedXor(gs_i64, v); +} + +// CHECK-LABEL: define {{(dso_local |hidden |internal |protected |spir_func )*}}void @{{.*}}test_uint64_3arg +// DXCHECK: %[[R:.*]] = call i64 @llvm.dx.interlocked.xor.i64.p3(ptr addrspace(3) {{.*}}@gs_u64{{.*}}, i64 %{{.*}}) +// SPVCHECK: %[[R:.*]] = call spir_func i64 @llvm.spv.interlocked.xor.i64.p3(ptr addrspace(3) {{.*}}@gs_u64{{.*}}, i64 %{{.*}}) +// CHECK: store i64 %[[R]], ptr {{.*}} +export void test_uint64_3arg(uint64_t v, out uint64_t orig) { + InterlockedXor(gs_u64, v, orig); +} diff --git a/clang/test/SemaHLSL/BuiltIns/InterlockedXor-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/InterlockedXor-errors.hlsl new file mode 100644 index 0000000000000..ce9fb07bdf569 --- /dev/null +++ b/clang/test/SemaHLSL/BuiltIns/InterlockedXor-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 + +// InterlockedXor is provided as a set of address-space-qualified overloads +// (groupshared/device, {int,uint,int64_t,uint64_t}, 2-arg/3-arg). All arg +// mismatches surface as "no matching function" with 16 candidates. The +// candidate notes come from synthesized FunctionDecls with no source +// location, so they are matched with `@*:*`. + +groupshared int gs_i32; +groupshared float gs_f32; +struct S { int x; }; +groupshared S gs_s; + +void too_few(int v) { + InterlockedXor(gs_i32); // expected-error{{no matching function for call to 'InterlockedXor'}} + // expected-note@*:* 16 {{candidate function}} +} + +void too_many(int v, int extra) { + int o; + InterlockedXor(gs_i32, v, o, extra); // expected-error{{no matching function for call to 'InterlockedXor'}} + // expected-note@*:* 16 {{candidate function}} +} + +// Atomics must operate on actual addresses in groupshared or device memory; +// passing a plain local (no address space) must not bind to any overload. +void local_dest(int v) { + int dest; + InterlockedXor(dest, v); // expected-error{{no matching function for call to 'InterlockedXor'}} + // expected-note@*:* 16 {{candidate function}} +} + +void float_dest(float v) { + InterlockedXor(gs_f32, v); // expected-error{{no matching function for call to 'InterlockedXor'}} + // expected-note@*:* 16 {{candidate function}} +} + +void struct_dest(int v) { + InterlockedXor(gs_s, v); // expected-error{{no matching function for call to 'InterlockedXor'}} + // expected-note@*:* 16 {{candidate function}} +} + +void mismatched_orig_type(int v) { + uint orig; + InterlockedXor(gs_i32, v, orig); // expected-error{{no matching function for call to 'InterlockedXor'}} + // expected-note@*:* 16 {{candidate function}} +} + +// The tests below exercise direct invocations of the underlying clang builtin +// `__builtin_hlsl_interlocked_xor`. These bypass overload resolution against +// the synthesized `InterlockedXor` overload set (the builtin's prototype in +// Builtins.td is `void (...)`), so each error is produced by the explicit +// checks in SemaHLSL.cpp rather than by candidate-set rejection. + +void direct_too_few() { + __builtin_hlsl_interlocked_xor(gs_i32); + // expected-error@-1 {{too few arguments to function call, expected at least 2, have 1}} +} + +void direct_too_many(int v, int extra) { + int o; + __builtin_hlsl_interlocked_xor(gs_i32, v, o, extra); + // expected-error@-1 {{too many arguments to function call, expected at most 3, have 4}} +} + +void direct_non_integer_dest() { + S local_s; + __builtin_hlsl_interlocked_xor(local_s, 1); + // expected-error@-1 {{1st argument must be a scalar integer type (was 'S')}} +} + +void direct_nonlvalue_dest(int v) { + __builtin_hlsl_interlocked_xor(1, v); + // expected-error@-1 {{cannot bind non-lvalue argument '1' to out parameter}} +} + +void direct_mismatched_value() { + uint uv = 1u; + __builtin_hlsl_interlocked_xor(gs_i32, uv); + // expected-error@-1 {{passing 'uint' (aka 'unsigned int') to parameter of incompatible type 'int'}} +} + +void direct_mismatched_orig(int v) { + uint orig; + __builtin_hlsl_interlocked_xor(gs_i32, v, orig); + // expected-error@-1 {{passing 'uint' (aka 'unsigned int') to parameter of incompatible type 'int'}} +} + +void direct_nonlvalue_orig(int v) { + __builtin_hlsl_interlocked_xor(gs_i32, v, 1); + // expected-error@-1 {{cannot bind non-lvalue argument '1' to out parameter}} +} + +void direct_default_as_dest(int v) { + int local; + __builtin_hlsl_interlocked_xor(local, v); + // expected-error@-1 {{1st argument to atomic builtin must reference groupshared or device memory (was 'int')}} +} diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td index 4dd86270f0d01..c2f960104a263 100644 --- a/llvm/include/llvm/IR/IntrinsicsDirectX.td +++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td @@ -265,6 +265,10 @@ def int_dx_interlocked_or : DefaultAttrsIntrinsic<[llvm_anyint_ty], [llvm_anyptr_ty, LLVMMatchType<0>], [IntrArgMemOnly]>; +def int_dx_interlocked_xor : + DefaultAttrsIntrinsic<[llvm_anyint_ty], + [llvm_anyptr_ty, LLVMMatchType<0>], + [IntrArgMemOnly]>; def int_dx_wave_reduce_max : DefaultAttrsIntrinsic<[llvm_any_ty], [LLVMMatchType<0>], [IntrConvergent, IntrNoMem, IntrTriviallyScalarizable]>; def int_dx_wave_reduce_umax : DefaultAttrsIntrinsic<[llvm_anyint_ty], [LLVMMatchType<0>], [IntrConvergent, IntrNoMem, IntrTriviallyScalarizable]>; def int_dx_wave_reduce_min : DefaultAttrsIntrinsic<[llvm_any_ty], [LLVMMatchType<0>], [IntrConvergent, IntrNoMem, IntrTriviallyScalarizable]>; diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td index d948ef78b9584..b58474bc73beb 100644 --- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td +++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td @@ -156,6 +156,10 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty] DefaultAttrsIntrinsic<[llvm_anyint_ty], [llvm_anyptr_ty, LLVMMatchType<0>], [IntrArgMemOnly]>; + def int_spv_interlocked_xor : + DefaultAttrsIntrinsic<[llvm_anyint_ty], + [llvm_anyptr_ty, LLVMMatchType<0>], + [IntrArgMemOnly]>; def int_spv_subgroup_ballot : ClangBuiltin<"__builtin_spirv_subgroup_ballot">, DefaultAttrsIntrinsic<[llvm_v4i32_ty], [llvm_i1_ty], [IntrConvergent, IntrNoMem]>; def int_spv_wave_reduce_umax : DefaultAttrsIntrinsic<[llvm_any_ty], [LLVMMatchType<0>], [IntrConvergent, IntrNoMem]>; diff --git a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp index 1025015c09a1f..adcadf9cb1e3a 100644 --- a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp +++ b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp @@ -230,6 +230,7 @@ static bool isIntrinsicExpansion(Function &F) { case Intrinsic::dx_radians: case Intrinsic::dx_interlocked_add: case Intrinsic::dx_interlocked_or: + case Intrinsic::dx_interlocked_xor: case Intrinsic::usub_sat: case Intrinsic::vector_reduce_add: case Intrinsic::vector_reduce_fadd: @@ -1347,6 +1348,9 @@ static bool expandIntrinsic(Function &F, CallInst *Orig) { case Intrinsic::dx_interlocked_or: Result = expandInterlockedIntrinsic(Orig, AtomicRMWInst::Or); break; + case Intrinsic::dx_interlocked_xor: + Result = expandInterlockedIntrinsic(Orig, AtomicRMWInst::Xor); + break; case Intrinsic::dx_resource_load_rawbuffer: if (expandBufferLoadIntrinsic(Orig, /*IsRaw*/ true)) return true; diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp index 11128eadef95f..aa1dac0873291 100644 --- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp @@ -5444,6 +5444,8 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg, return selectInterlockedOp(ResVReg, ResType, I, SPIRV::OpAtomicIAdd); case Intrinsic::spv_interlocked_or: return selectInterlockedOp(ResVReg, ResType, I, SPIRV::OpAtomicOr); + case Intrinsic::spv_interlocked_xor: + return selectInterlockedOp(ResVReg, ResType, I, SPIRV::OpAtomicXor); case Intrinsic::spv_wave_reduce_umax: return selectWaveReduceMax(ResVReg, ResType, I, /*IsUnsigned*/ true); case Intrinsic::spv_wave_reduce_max: diff --git a/llvm/test/CodeGen/DirectX/InterlockedXor.ll b/llvm/test/CodeGen/DirectX/InterlockedXor.ll new file mode 100644 index 0000000000000..d724ec6b79ba0 --- /dev/null +++ b/llvm/test/CodeGen/DirectX/InterlockedXor.ll @@ -0,0 +1,52 @@ +; RUN: opt -S -dxil-intrinsic-expansion -mtriple=dxil-pc-shadermodel6.6-compute %s | FileCheck %s + +; Verify llvm.dx.interlocked.xor expands to atomicrmw xor monotonic. + +; Groupshared (addrspace 3) memory tests. +@gs_i32 = internal addrspace(3) global i32 zeroinitializer +@gs_i64 = internal addrspace(3) global i64 zeroinitializer + +define i32 @test_i32(i32 %v) { +entry: +; CHECK-LABEL: @test_i32 +; CHECK: %[[R:.*]] = atomicrmw xor ptr addrspace(3) @gs_i32, i32 %v monotonic +; CHECK: ret i32 %[[R]] + %r = call i32 @llvm.dx.interlocked.xor.i32.p3(ptr addrspace(3) @gs_i32, i32 %v) + ret i32 %r +} + +define i64 @test_i64(i64 %v) { +entry: +; CHECK-LABEL: @test_i64 +; CHECK: %[[R:.*]] = atomicrmw xor ptr addrspace(3) @gs_i64, i64 %v monotonic +; CHECK: ret i64 %[[R]] + %r = call i64 @llvm.dx.interlocked.xor.i64.p3(ptr addrspace(3) @gs_i64, i64 %v) + ret i64 %r +} + +; Device (addrspace 1) memory tests. +@dev_i32 = external addrspace(1) global i32 +@dev_i64 = external addrspace(1) global i64 + +define i32 @test_device_i32(i32 %v) { +entry: +; CHECK-LABEL: @test_device_i32 +; CHECK: %[[R:.*]] = atomicrmw xor ptr addrspace(1) @dev_i32, i32 %v monotonic +; CHECK: ret i32 %[[R]] + %r = call i32 @llvm.dx.interlocked.xor.i32.p1(ptr addrspace(1) @dev_i32, i32 %v) + ret i32 %r +} + +define i64 @test_device_i64(i64 %v) { +entry: +; CHECK-LABEL: @test_device_i64 +; CHECK: %[[R:.*]] = atomicrmw xor ptr addrspace(1) @dev_i64, i64 %v monotonic +; CHECK: ret i64 %[[R]] + %r = call i64 @llvm.dx.interlocked.xor.i64.p1(ptr addrspace(1) @dev_i64, i64 %v) + ret i64 %r +} + +declare i32 @llvm.dx.interlocked.xor.i32.p3(ptr addrspace(3), i32) +declare i64 @llvm.dx.interlocked.xor.i64.p3(ptr addrspace(3), i64) +declare i32 @llvm.dx.interlocked.xor.i32.p1(ptr addrspace(1), i32) +declare i64 @llvm.dx.interlocked.xor.i64.p1(ptr addrspace(1), i64) diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/InterlockedXor.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/InterlockedXor.ll new file mode 100644 index 0000000000000..a91a92a559f73 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/InterlockedXor.ll @@ -0,0 +1,36 @@ +; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv1.6-vulkan1.3-compute %s -o - | FileCheck %s +; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %} + +; Test lowering of llvm.spv.interlocked.xor to OpAtomicIOr. + +; CHECK-DAG: %[[#uint:]] = OpTypeInt 32 0 +; CHECK-DAG: %[[#scope_wg:]] = OpConstant %[[#uint]] 2 +; CHECK-DAG: %[[#scope_dev:]] = OpConstant %[[#uint]] 1 +; CHECK-DAG: %[[#mem_wg:]] = OpConstant %[[#uint]] 256 +; CHECK-DAG: %[[#mem_uniform:]] = OpConstant %[[#uint]] 64 + +@gs_i32 = internal addrspace(3) global i32 zeroinitializer +@dev_i32 = external addrspace(11) global i32 + +; Workgroup (addrspace 3) memory tests. + +; CHECK-LABEL: Begin function test_i32 +define i32 @test_i32(i32 %v) { +entry: +; CHECK: %[[#R:]] = OpAtomicXor %[[#uint]] %[[#]] %[[#scope_wg]] %[[#mem_wg]] %[[#]] + %r = call i32 @llvm.spv.interlocked.xor.i32.p3(ptr addrspace(3) @gs_i32, i32 %v) + ret i32 %r +} + +; Device / StorageBuffer (addrspace 11) memory tests. + +; CHECK-LABEL: Begin function test_device_i32 +define i32 @test_device_i32(i32 %v) { +entry: +; CHECK: %[[#R:]] = OpAtomicXor %[[#uint]] %[[#]] %[[#scope_dev]] %[[#mem_uniform]] %[[#]] + %r = call i32 @llvm.spv.interlocked.xor.i32.p11(ptr addrspace(11) @dev_i32, i32 %v) + ret i32 %r +} + +declare i32 @llvm.spv.interlocked.xor.i32.p3(ptr addrspace(3), i32) +declare i32 @llvm.spv.interlocked.xor.i32.p11(ptr addrspace(11), i32) diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/InterlockedXor_spv_i64.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/InterlockedXor_spv_i64.ll new file mode 100644 index 0000000000000..482a1a641b53d --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/InterlockedXor_spv_i64.ll @@ -0,0 +1,37 @@ +; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv1.6-vulkan1.3-compute %s -o - | FileCheck %s +; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %} + +; Test lowering of llvm.spv.interlocked.xor with i64 to OpAtomicIOr. + +; CHECK-DAG: %[[#ulong:]] = OpTypeInt 64 0 +; CHECK-DAG: %[[#uint:]] = OpTypeInt 32 0 +; CHECK-DAG: %[[#scope_wg:]] = OpConstant %[[#uint]] 2 +; CHECK-DAG: %[[#scope_dev:]] = OpConstant %[[#uint]] 1 +; CHECK-DAG: %[[#mem_wg:]] = OpConstant %[[#uint]] 256 +; CHECK-DAG: %[[#mem_uniform:]] = OpConstant %[[#uint]] 64 + +@gs_i64 = internal addrspace(3) global i64 zeroinitializer +@dev_i64 = external addrspace(11) global i64 + +; Workgroup (addrspace 3) memory test. + +; CHECK-LABEL: Begin function test_i64 +define i64 @test_i64(i64 %v) { +entry: +; CHECK: %[[#R:]] = OpAtomicXor %[[#ulong]] %[[#]] %[[#scope_wg]] %[[#mem_wg]] %[[#]] + %r = call i64 @llvm.spv.interlocked.xor.i64.p3(ptr addrspace(3) @gs_i64, i64 %v) + ret i64 %r +} + +; Device / StorageBuffer (addrspace 11) memory test. + +; CHECK-LABEL: Begin function test_device_i64 +define i64 @test_device_i64(i64 %v) { +entry: +; CHECK: %[[#R:]] = OpAtomicXor %[[#ulong]] %[[#]] %[[#scope_dev]] %[[#mem_uniform]] %[[#]] + %r = call i64 @llvm.spv.interlocked.xor.i64.p11(ptr addrspace(11) @dev_i64, i64 %v) + ret i64 %r +} + +declare i64 @llvm.spv.interlocked.xor.i64.p3(ptr addrspace(3), i64) +declare i64 @llvm.spv.interlocked.xor.i64.p11(ptr addrspace(11), i64) >From c9629d029f3cd3269fe1dd85825d82ca2b90604b Mon Sep 17 00:00:00 2001 From: Alexander Johnston <[email protected]> Date: Mon, 13 Jul 2026 18:52:02 +0100 Subject: [PATCH 2/2] Fix format error --- clang/lib/CodeGen/CGHLSLBuiltins.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp index d5d0d9b09bd46..9d33e7c5c868a 100644 --- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp +++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp @@ -1467,9 +1467,9 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID, "hlsl.interlocked.or"); } case Builtin::BI__builtin_hlsl_interlocked_xor: { - return handleInterlockedOp(*this, E, - CGM.getHLSLRuntime().getInterlockedXorIntrinsic(), - "hlsl.interlocked.xor"); + return handleInterlockedOp( + *this, E, CGM.getHLSLRuntime().getInterlockedXorIntrinsic(), + "hlsl.interlocked.xor"); } case Builtin::BI__builtin_hlsl_wave_active_ballot: { [[maybe_unused]] Value *Op = EmitScalarExpr(E->getArg(0)); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
