Skip to main content

khal_std/sync/
atomics.rs

1// =============================================================================
2// Atomics — QueueFamily (storage buffer) scope
3// =============================================================================
4
5/// Atomically adds `value` to `*ptr` and returns the old value.
6#[inline(always)]
7pub fn atomic_add_i32(ptr: &mut i32, value: i32) -> i32 {
8    #[cfg(target_arch = "spirv")]
9    unsafe {
10        spirv_std::arch::atomic_i_add::<
11            i32,
12            { spirv_std::memory::Scope::QueueFamily as u32 },
13            { spirv_std::memory::Semantics::NONE.bits() },
14        >(ptr, value)
15    }
16    #[cfg(not(target_arch = "spirv"))]
17    {
18        use core::sync::atomic::{AtomicI32, Ordering};
19        let atomic = unsafe { &*(ptr as *mut i32 as *const AtomicI32) };
20        atomic.fetch_add(value, Ordering::Relaxed)
21    }
22}
23
24/// Atomically adds `value` to `*ptr` (u32) and returns the old value.
25#[inline(always)]
26pub fn atomic_add_u32(ptr: &mut u32, value: u32) -> u32 {
27    #[cfg(target_arch = "spirv")]
28    unsafe {
29        spirv_std::arch::atomic_i_add::<
30            u32,
31            { spirv_std::memory::Scope::QueueFamily as u32 },
32            { spirv_std::memory::Semantics::NONE.bits() },
33        >(ptr, value)
34    }
35    #[cfg(not(target_arch = "spirv"))]
36    {
37        use core::sync::atomic::{AtomicU32, Ordering};
38        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
39        atomic.fetch_add(value, Ordering::Relaxed)
40    }
41}
42
43/// Atomically computes max(`*ptr`, `value`) and returns the old value.
44#[inline(always)]
45pub fn atomic_max_u32(ptr: &mut u32, value: u32) -> u32 {
46    #[cfg(target_arch = "spirv")]
47    unsafe {
48        spirv_std::arch::atomic_u_max::<
49            u32,
50            { spirv_std::memory::Scope::QueueFamily as u32 },
51            { spirv_std::memory::Semantics::NONE.bits() },
52        >(ptr, value)
53    }
54    #[cfg(not(target_arch = "spirv"))]
55    {
56        use core::sync::atomic::{AtomicU32, Ordering};
57        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
58        atomic.fetch_max(value, Ordering::Relaxed)
59    }
60}
61
62/// Atomically computes min(`*ptr`, `value`) and returns the old value.
63#[inline(always)]
64pub fn atomic_min_u32(ptr: &mut u32, value: u32) -> u32 {
65    #[cfg(target_arch = "spirv")]
66    unsafe {
67        spirv_std::arch::atomic_u_min::<
68            u32,
69            { spirv_std::memory::Scope::QueueFamily as u32 },
70            { spirv_std::memory::Semantics::NONE.bits() },
71        >(ptr, value)
72    }
73    #[cfg(not(target_arch = "spirv"))]
74    {
75        use core::sync::atomic::{AtomicU32, Ordering};
76        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
77        atomic.fetch_min(value, Ordering::Relaxed)
78    }
79}
80
81/// Atomically loads the value at `*ptr`.
82///
83/// Accepts `&mut u32` for compatibility with SPIR-V's pointer model where
84/// all atomic operations require mutable pointers. Use `atomic_load_u32_shared`
85/// when only a shared reference is available.
86#[inline(always)]
87pub fn atomic_load_u32(ptr: &mut u32) -> u32 {
88    #[cfg(target_arch = "spirv")]
89    unsafe {
90        spirv_std::arch::atomic_load::<
91            u32,
92            { spirv_std::memory::Scope::QueueFamily as u32 },
93            { spirv_std::memory::Semantics::NONE.bits() },
94        >(ptr)
95    }
96    #[cfg(not(target_arch = "spirv"))]
97    {
98        use core::sync::atomic::{AtomicU32, Ordering};
99        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
100        atomic.load(Ordering::Relaxed)
101    }
102}
103
104/// Atomically loads the value at `*ptr` from a shared reference.
105#[inline(always)]
106pub fn atomic_load_u32_shared(ptr: &u32) -> u32 {
107    #[cfg(target_arch = "spirv")]
108    unsafe {
109        spirv_std::arch::atomic_load::<
110            u32,
111            { spirv_std::memory::Scope::QueueFamily as u32 },
112            { spirv_std::memory::Semantics::NONE.bits() },
113        >(ptr)
114    }
115    #[cfg(not(target_arch = "spirv"))]
116    {
117        use core::sync::atomic::{AtomicU32, Ordering};
118        let atomic = unsafe { &*(ptr as *const u32 as *const AtomicU32) };
119        atomic.load(Ordering::Relaxed)
120    }
121}
122
123/// Atomically exchanges `*ptr` with `value` and returns the old value.
124#[inline(always)]
125pub fn atomic_exchange_u32(ptr: &mut u32, value: u32) -> u32 {
126    #[cfg(target_arch = "spirv")]
127    unsafe {
128        spirv_std::arch::atomic_exchange::<
129            u32,
130            { spirv_std::memory::Scope::QueueFamily as u32 },
131            { spirv_std::memory::Semantics::NONE.bits() },
132        >(ptr, value)
133    }
134    #[cfg(not(target_arch = "spirv"))]
135    {
136        use core::sync::atomic::{AtomicU32, Ordering};
137        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
138        atomic.swap(value, Ordering::Relaxed)
139    }
140}
141
142/// Atomically compares `*ptr` with `comparator` and, if equal, replaces with `value`.
143/// Returns the old value at `*ptr`.
144#[inline(always)]
145pub fn atomic_compare_exchange_u32(ptr: &mut u32, value: u32, comparator: u32) -> u32 {
146    #[cfg(target_arch = "spirv")]
147    unsafe {
148        spirv_std::arch::atomic_compare_exchange::<
149            u32,
150            { spirv_std::memory::Scope::QueueFamily as u32 },
151            { spirv_std::memory::Semantics::NONE.bits() },
152            { spirv_std::memory::Semantics::NONE.bits() },
153        >(ptr, value, comparator)
154    }
155    #[cfg(not(target_arch = "spirv"))]
156    {
157        use core::sync::atomic::{AtomicU32, Ordering};
158        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
159        match atomic.compare_exchange(comparator, value, Ordering::Relaxed, Ordering::Relaxed) {
160            Ok(old) => old,
161            Err(old) => old,
162        }
163    }
164}
165
166/// Atomically ORs `*ptr` with `value` and returns the old value.
167#[inline(always)]
168pub fn atomic_or_u32(ptr: &mut u32, value: u32) -> u32 {
169    #[cfg(target_arch = "spirv")]
170    unsafe {
171        spirv_std::arch::atomic_or::<
172            u32,
173            { spirv_std::memory::Scope::QueueFamily as u32 },
174            { spirv_std::memory::Semantics::NONE.bits() },
175        >(ptr, value)
176    }
177    #[cfg(not(target_arch = "spirv"))]
178    {
179        use core::sync::atomic::{AtomicU32, Ordering};
180        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
181        atomic.fetch_or(value, Ordering::Relaxed)
182    }
183}
184
185/// Atomically ANDs `*ptr` with `value` and returns the old value.
186#[inline(always)]
187pub fn atomic_and_u32(ptr: &mut u32, value: u32) -> u32 {
188    #[cfg(target_arch = "spirv")]
189    unsafe {
190        spirv_std::arch::atomic_and::<
191            u32,
192            { spirv_std::memory::Scope::QueueFamily as u32 },
193            { spirv_std::memory::Semantics::NONE.bits() },
194        >(ptr, value)
195    }
196    #[cfg(not(target_arch = "spirv"))]
197    {
198        use core::sync::atomic::{AtomicU32, Ordering};
199        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
200        atomic.fetch_and(value, Ordering::Relaxed)
201    }
202}
203
204/// Atomically adds a float `value` to the f32 stored (as u32 bits) at `ptr`.
205///
206/// Uses a compare-and-swap loop to emulate float atomic add:
207/// repeatedly loads the current bits, reinterprets as f32, adds `value`,
208/// and attempts to CAS the result back. The pointer must hold the bit
209/// pattern of a valid f32 (initialize with `0u32` for 0.0).
210#[inline]
211pub fn atomic_add_f32(ptr: &mut u32, value: f32) {
212    loop {
213        let old_bits = atomic_load_u32(ptr);
214        let new_val = f32::from_bits(old_bits) + value;
215        let new_bits = new_val.to_bits();
216        let prev = atomic_compare_exchange_u32(ptr, new_bits, old_bits);
217        if prev == old_bits {
218            break;
219        }
220    }
221}
222
223// =============================================================================
224// Atomics — Workgroup scope (shared memory)
225// =============================================================================
226
227/// Atomically adds `value` to `*ptr` (workgroup scope) and returns the old value.
228#[inline(always)]
229pub fn atomic_add_u32_workgroup(ptr: &mut u32, value: u32) -> u32 {
230    #[cfg(target_arch = "spirv")]
231    unsafe {
232        spirv_std::arch::atomic_i_add::<
233            u32,
234            { spirv_std::memory::Scope::Workgroup as u32 },
235            { spirv_std::memory::Semantics::NONE.bits() },
236        >(ptr, value)
237    }
238    #[cfg(not(target_arch = "spirv"))]
239    {
240        use core::sync::atomic::{AtomicU32, Ordering};
241        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
242        atomic.fetch_add(value, Ordering::Relaxed)
243    }
244}
245
246/// Atomically computes max(`*ptr`, `value`) (workgroup scope) and returns the old value.
247#[inline(always)]
248pub fn atomic_max_u32_workgroup(ptr: &mut u32, value: u32) -> u32 {
249    #[cfg(target_arch = "spirv")]
250    unsafe {
251        spirv_std::arch::atomic_u_max::<
252            u32,
253            { spirv_std::memory::Scope::Workgroup as u32 },
254            { spirv_std::memory::Semantics::NONE.bits() },
255        >(ptr, value)
256    }
257    #[cfg(not(target_arch = "spirv"))]
258    {
259        use core::sync::atomic::{AtomicU32, Ordering};
260        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
261        atomic.fetch_max(value, Ordering::Relaxed)
262    }
263}
264
265/// Atomically stores `value` to `*ptr` (workgroup scope).
266#[inline(always)]
267pub fn atomic_store_u32_workgroup(ptr: &mut u32, value: u32) {
268    #[cfg(target_arch = "spirv")]
269    unsafe {
270        spirv_std::arch::atomic_exchange::<
271            u32,
272            { spirv_std::memory::Scope::Workgroup as u32 },
273            { spirv_std::memory::Semantics::NONE.bits() },
274        >(ptr, value);
275    }
276    #[cfg(not(target_arch = "spirv"))]
277    {
278        use core::sync::atomic::{AtomicU32, Ordering};
279        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
280        atomic.store(value, Ordering::Relaxed);
281    }
282}
283
284/// Atomically loads `*ptr` (workgroup scope).
285#[inline(always)]
286pub fn atomic_load_u32_workgroup(ptr: &mut u32) -> u32 {
287    #[cfg(target_arch = "spirv")]
288    unsafe {
289        spirv_std::arch::atomic_load::<
290            u32,
291            { spirv_std::memory::Scope::Workgroup as u32 },
292            { spirv_std::memory::Semantics::NONE.bits() },
293        >(ptr)
294    }
295    #[cfg(not(target_arch = "spirv"))]
296    {
297        use core::sync::atomic::{AtomicU32, Ordering};
298        let atomic = unsafe { &*(ptr as *mut u32 as *const AtomicU32) };
299        atomic.load(Ordering::Relaxed)
300    }
301}