Skip to content

[HLSL] Implement HLSL InterlockedXor#209254

Open
Alexander-Johnston wants to merge 2 commits into
llvm:mainfrom
Alexander-Johnston:interlocked_xor
Open

[HLSL] Implement HLSL InterlockedXor#209254
Alexander-Johnston wants to merge 2 commits into
llvm:mainfrom
Alexander-Johnston:interlocked_xor

Conversation

@Alexander-Johnston

@Alexander-Johnston Alexander-Johnston commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Follows existing HLSL InterlockedOp implementations to provide InterlockedXor in HLSL, with DirectX and SPIRV support. Does not include RWByteAddressBuffer interlocked methods.

#99127

Follows existing HLSL InterlockedOp implementations to provide
InterlockedXor in HLSL, with DirectX and SPIRV support.
Does not include RWByteAddressBuffer interlocked methods.
@llvmorg-github-actions llvmorg-github-actions Bot added clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. backend:DirectX HLSL HLSL Language Support backend:SPIR-V llvm:ir labels Jul 13, 2026
@llvmorg-github-actions

llvmorg-github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-hlsl

Author: Alexander Johnston (Alexander-Johnston)

Changes

Follows existing HLSL InterlockedOp implementations to provide InterlockedXor in HLSL, with DirectX and SPIRV support. Does not include RWByteAddressBuffer interlocked methods.


Patch is 20.81 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/209254.diff

14 Files Affected:

  • (modified) clang/include/clang/Basic/Builtins.td (+6)
  • (modified) clang/lib/CodeGen/CGHLSLBuiltins.cpp (+5)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.h (+1)
  • (modified) clang/lib/Sema/HLSLExternalSemaSource.cpp (+2)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+4-3)
  • (added) clang/test/CodeGenHLSL/builtins/InterlockedXor.hlsl (+59)
  • (added) clang/test/SemaHLSL/BuiltIns/InterlockedXor-errors.hlsl (+100)
  • (modified) llvm/include/llvm/IR/IntrinsicsDirectX.td (+4)
  • (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+4)
  • (modified) llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp (+4)
  • (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+2)
  • (added) llvm/test/CodeGen/DirectX/InterlockedXor.ll (+52)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/InterlockedXor.ll (+36)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/InterlockedXor_spv_i64.ll (+37)
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 = exter...
[truncated]

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@damyanp damyanp requested a review from bob80905 July 14, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:DirectX backend:SPIR-V clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" HLSL HLSL Language Support llvm:ir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants