[llvm] [SPIRV] Support `Volatile` memory semantics operand in atomic load/store (PR #195978)

Victor Mustya via llvm-commits llvm-commits at lists.llvm.org
Tue May 5 19:47:18 PDT 2026


https://github.com/vmustya updated https://github.com/llvm/llvm-project/pull/195978

>From 41f0b3a1416eed32eae9b4a358434ec01b167fe1 Mon Sep 17 00:00:00 2001
From: Victor Mustya <victor.mustya at intel.com>
Date: Tue, 5 May 2026 19:15:24 -0700
Subject: [PATCH] [SPIRV] Support `Volatile` memory semantics operand in atomic
 load/store

The Vulkan memory model supports the `Volatile` memory semantics being
used with atomic operations. This patch adds the support to the SPIR-V
backend.

When the memory model is OpenCL, the `Volatile` memory semantics is not
supported. In this case, we ignore it and emit a regular `OpAtomicLoad`
or `OpAtomicStore` instruction. It should be safe, because the atomic
operations aren't eliminated anyway.

Assisted-by: Claude Opus 4.6 <noreply at anthropic.com>
---
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 21 ++++--------
 .../lib/Target/SPIRV/SPIRVSymbolicOperands.td |  1 +
 .../atomic-load-store-unsupported.ll          | 21 +-----------
 .../transcoding/atomic-load-store-volatile.ll | 32 +++++++++++++++++++
 4 files changed, 41 insertions(+), 34 deletions(-)
 create mode 100644 llvm/test/CodeGen/SPIRV/transcoding/atomic-load-store-volatile.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index aee3a29c6e42b..cb0f4d3bd1246 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -1739,9 +1739,9 @@ bool SPIRVInstructionSelector::selectPopCount(Register ResVReg,
                                               SPIRVTypeInst ResType,
                                               MachineInstr &I,
                                               unsigned Opcode) const {
-  // Vulkan restricts OpBitCount to 32-bit integers or vectors of 32-bit 
-  // integers unless VK_KHR_maintenance9 is enabled. Until VK_KHR_maintaince9 
-  // is core we will not generate OpBitCount with any other types when 
+  // Vulkan restricts OpBitCount to 32-bit integers or vectors of 32-bit
+  // integers unless VK_KHR_maintenance9 is enabled. Until VK_KHR_maintaince9
+  // is core we will not generate OpBitCount with any other types when
   // targeting Vulkan.
   if (!STI.getTargetTriple().isVulkanOS())
     return selectUnOp(ResVReg, ResType, I, Opcode);
@@ -1944,11 +1944,6 @@ bool SPIRVInstructionSelector::selectAtomicLoad(Register ResVReg,
   assert(I.getNumMemOperands());
   const MachineMemOperand &MemOp = **I.memoperands_begin();
   assert(MemOp.isAtomic());
-  // TODO: This must be relaxed since the volatile attribute on atomic load is
-  // supported with the VulkanMemoryModelKHR capability.
-  if (MemOp.isVolatile())
-    return diagnoseUnsupported(I, "Lowering to SPIR-V of atomic load of "
-                                  "volatile memory is not supported");
 
   uint32_t Scope =
       static_cast<uint32_t>(getMemScope(Context, MemOp.getSyncScopeID()));
@@ -1958,6 +1953,8 @@ bool SPIRVInstructionSelector::selectAtomicLoad(Register ResVReg,
   uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(
       addressSpaceToStorageClass(MemOp.getAddrSpace(), STI)));
   uint32_t MemSem = static_cast<uint32_t>(getMemSemantics(AO));
+  if (MemOp.isVolatile() && STI.getTargetTriple().isVulkanOS())
+    MemSem |= static_cast<uint32_t>(SPIRV::MemorySemantics::Volatile);
   Register MemSemReg = buildI32Constant(MemSem | StorageClass, I);
 
   MachineIRBuilder MIRBuilder(I);
@@ -2044,12 +2041,6 @@ bool SPIRVInstructionSelector::selectAtomicStore(MachineInstr &I) const {
   const MachineMemOperand &MemOp = **I.memoperands_begin();
   assert(MemOp.isAtomic());
 
-  // TODO: This must be relaxed since the volatile attribute on atomic store is
-  // supported with the VulkanMemoryModelKHR capability.
-  if (MemOp.isVolatile())
-    return diagnoseUnsupported(I, "Lowering to SPIR-V of atomic store of "
-                                  "volatile memory is not supported");
-
   uint32_t Scope =
       static_cast<uint32_t>(getMemScope(Context, MemOp.getSyncScopeID()));
   Register ScopeReg = buildI32Constant(Scope, I);
@@ -2058,6 +2049,8 @@ bool SPIRVInstructionSelector::selectAtomicStore(MachineInstr &I) const {
   uint32_t StorageClass = static_cast<uint32_t>(getMemSemanticsForStorageClass(
       addressSpaceToStorageClass(MemOp.getAddrSpace(), STI)));
   uint32_t MemSem = static_cast<uint32_t>(getMemSemantics(AO));
+  if (MemOp.isVolatile() && STI.getTargetTriple().isVulkanOS())
+    MemSem |= static_cast<uint32_t>(SPIRV::MemorySemantics::Volatile);
   Register MemSemReg = buildI32Constant(MemSem | StorageClass, I);
 
   MachineIRBuilder MIRBuilder(I);
diff --git a/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td b/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td
index cd52181bfc436..acb104ddfbea1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td
+++ b/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td
@@ -1672,6 +1672,7 @@ defm ImageMemory : MemorySemanticsOperand<0x800, 0, 0, [], []>;
 defm OutputMemoryKHR : MemorySemanticsOperand<0x1000, 0, 0, [], [VulkanMemoryModelKHR]>;
 defm MakeAvailableKHR : MemorySemanticsOperand<0x2000, 0, 0, [], [VulkanMemoryModelKHR]>;
 defm MakeVisibleKHR : MemorySemanticsOperand<0x4000, 0, 0, [], [VulkanMemoryModelKHR]>;
+defm Volatile : MemorySemanticsOperand<0x8000, 0, 0, [], [VulkanMemoryModelKHR]>;
 
 //===----------------------------------------------------------------------===//
 // Multiclass used to define MemoryOperand enum values and at the same time
diff --git a/llvm/test/CodeGen/SPIRV/transcoding/atomic-load-store-unsupported.ll b/llvm/test/CodeGen/SPIRV/transcoding/atomic-load-store-unsupported.ll
index 4382c3cf87097..b6c055ff3b7de 100644
--- a/llvm/test/CodeGen/SPIRV/transcoding/atomic-load-store-unsupported.ll
+++ b/llvm/test/CodeGen/SPIRV/transcoding/atomic-load-store-unsupported.ll
@@ -1,20 +1,13 @@
-; Verify that atomic load/store of vectors and volatile atomic load/store
-; correctly fail to select.
+; Verify that atomic load/store of vectors correctly fail to select.
 
 ; RUN: split-file %s %t
 
 ; RUN: not llc -O0 -mtriple=spirv64-- %t/load-vector.ll -o /dev/null 2>&1 | FileCheck --check-prefix=FAIL-LOAD-VEC %s
 
-; RUN: not llc -O0 -mtriple=spirv64-- %t/load-volatile.ll -o /dev/null 2>&1 | FileCheck --check-prefix=FAIL-LOAD-VOL %s
-
 ; RUN: not llc -O0 -mtriple=spirv64-- %t/store-vector.ll -o /dev/null 2>&1 | FileCheck --check-prefix=FAIL-STORE-VEC %s
 
-; RUN: not llc -O0 -mtriple=spirv64-- %t/store-volatile.ll -o /dev/null 2>&1 | FileCheck --check-prefix=FAIL-STORE-VOL %s
-
 ; FAIL-LOAD-VEC: error:{{.*}}atomic load is only allowed for integer or floating point types
-; FAIL-LOAD-VOL: error:{{.*}}atomic load of volatile memory is not supported
 ; FAIL-STORE-VEC: error:{{.*}}atomic store is only allowed for integer or floating point types
-; FAIL-STORE-VOL: error:{{.*}}atomic store of volatile memory is not supported
 
 ;--- load-vector.ll
 define <2 x i32> @load_vector_acquire(ptr addrspace(1) %ptr) {
@@ -22,20 +15,8 @@ define <2 x i32> @load_vector_acquire(ptr addrspace(1) %ptr) {
   ret <2 x i32> %val
 }
 
-;--- load-volatile.ll
-define i32 @load_i32_acquire_device_volatile(ptr addrspace(1) %ptr) {
-  %val = load atomic volatile i32, ptr addrspace(1) %ptr syncscope("device") acquire, align 4
-  ret i32 %val
-}
-
 ;--- store-vector.ll
 define void @store_vector_release(ptr addrspace(1) %ptr, <2 x i32> %val) {
   store atomic <2 x i32> %val, ptr addrspace(1) %ptr release, align 8
   ret void
 }
-
-;--- store-volatile.ll
-define void @store_i32_release_device_volatile(ptr addrspace(1) %ptr, i32 %val) {
-  store atomic volatile i32 %val, ptr addrspace(1) %ptr syncscope("device") release, align 4
-  ret void
-}
diff --git a/llvm/test/CodeGen/SPIRV/transcoding/atomic-load-store-volatile.ll b/llvm/test/CodeGen/SPIRV/transcoding/atomic-load-store-volatile.ll
new file mode 100644
index 0000000000000..a988cd9311c57
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/transcoding/atomic-load-store-volatile.ll
@@ -0,0 +1,32 @@
+; Verify that volatile atomic load/store are accepted and produce
+; OpAtomicLoad/OpAtomicStore. On non-Vulkan targets the volatile qualifier
+; is silently dropped; on Vulkan the Volatile memory semantics bit is set.
+
+; Non-Vulkan: no Volatile memory semantics.
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=NONVK
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Vulkan: Volatile memory semantics (0x8000) ORed into the semantics operand.
+; RUN: llc -O0 -mtriple=spirv-unknown-vulkan1.3-compute %s -o - | FileCheck %s --check-prefix=VULKAN
+
+; NONVK-DAG: %[[#C514:]] = OpConstant %[[#]] 514
+; NONVK-DAG: %[[#C516:]] = OpConstant %[[#]] 516
+; NONVK: OpAtomicLoad %[[#]] %[[#]] %[[#]] %[[#C514]]
+; NONVK: OpAtomicStore %[[#]] %[[#]] %[[#C516]] %[[#]]
+
+; 0x8000 | 0x2 (Acquire) | 0x200 (CrossWorkgroupMemory) = 33282
+; 0x8000 | 0x4 (Release) | 0x200 (CrossWorkgroupMemory) = 33284
+; VULKAN-DAG: %[[#VA:]] = OpConstant %[[#]] 33282
+; VULKAN-DAG: %[[#VR:]] = OpConstant %[[#]] 33284
+; VULKAN: OpAtomicLoad %[[#]] %[[#]] %[[#]] %[[#VA]]
+; VULKAN: OpAtomicStore %[[#]] %[[#]] %[[#VR]] %[[#]]
+
+define i32 @load_volatile(ptr addrspace(1) %ptr) {
+  %val = load atomic volatile i32, ptr addrspace(1) %ptr syncscope("device") acquire, align 4
+  ret i32 %val
+}
+
+define void @store_volatile(ptr addrspace(1) %ptr, i32 %val) {
+  store atomic volatile i32 %val, ptr addrspace(1) %ptr syncscope("device") release, align 4
+  ret void
+}



More information about the llvm-commits mailing list