portable_atomic/imp/fallback/
mod.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3/*
4Fallback implementation using global locks.
5
6This implementation uses seqlock for global locks.
7
8This is basically based on global locks in crossbeam-utils's `AtomicCell`,
9but seqlock is implemented in a way that does not depend on UB
10(see comments in optimistic_read method in atomic! macro for details).
11
12Note that we cannot use a lock per atomic type, since the in-memory representation of the atomic
13type and the value type must be the same.
14*/
15
16#![cfg_attr(
17    any(
18        all(
19            target_arch = "x86_64",
20            not(portable_atomic_no_outline_atomics),
21            not(any(target_env = "sgx", miri)),
22        ),
23        all(
24            target_arch = "powerpc64",
25            feature = "fallback",
26            not(portable_atomic_no_outline_atomics),
27            portable_atomic_outline_atomics, // TODO(powerpc64): currently disabled by default
28            any(
29                all(
30                    target_os = "linux",
31                    any(
32                        target_env = "gnu",
33                        all(
34                            any(target_env = "musl", target_env = "ohos"),
35                            not(target_feature = "crt-static"),
36                        ),
37                        portable_atomic_outline_atomics,
38                    ),
39                ),
40                target_os = "android",
41                target_os = "freebsd",
42                all(target_os = "openbsd", portable_atomic_outline_atomics),
43            ),
44            not(any(miri, portable_atomic_sanitize_thread)),
45        ),
46        all(
47            target_arch = "riscv32",
48            not(any(miri, portable_atomic_sanitize_thread)),
49            not(portable_atomic_no_asm),
50            not(portable_atomic_pre_llvm_19),
51            any(
52                target_feature = "experimental-zacas",
53                portable_atomic_target_feature = "experimental-zacas",
54                all(
55                    feature = "fallback",
56                    not(portable_atomic_no_outline_atomics),
57                    any(test, portable_atomic_outline_atomics), // TODO(riscv): currently disabled by default
58                    any(target_os = "linux", target_os = "android"),
59                ),
60            ),
61        ),
62        all(
63            target_arch = "riscv64",
64            not(portable_atomic_no_asm),
65            not(portable_atomic_pre_llvm_19),
66            any(
67                target_feature = "experimental-zacas",
68                portable_atomic_target_feature = "experimental-zacas",
69                all(
70                    feature = "fallback",
71                    not(portable_atomic_no_outline_atomics),
72                    any(test, portable_atomic_outline_atomics), // TODO(riscv): currently disabled by default
73                    any(target_os = "linux", target_os = "android"),
74                    not(any(miri, portable_atomic_sanitize_thread)),
75                ),
76            ),
77        ),
78        all(
79            target_arch = "arm",
80            any(not(portable_atomic_no_asm), portable_atomic_unstable_asm),
81            any(target_os = "linux", target_os = "android"),
82            not(portable_atomic_no_outline_atomics),
83        ),
84    ),
85    allow(dead_code)
86)]
87
88#[macro_use]
89pub(crate) mod utils;
90
91// Use "wide" sequence lock if the pointer width <= 32 for preventing its counter against wrap
92// around.
93//
94// In narrow architectures (pointer width <= 16), the counter is still <= 32-bit and may be
95// vulnerable to wrap around. But it's mostly okay, since in such a primitive hardware, the
96// counter will not be increased that fast.
97//
98// Some 64-bit architectures have ABI with 32-bit pointer width (e.g., x86_64 X32 ABI,
99// AArch64 ILP32 ABI, mips64 N32 ABI). On those targets, AtomicU64 is available and fast,
100// so use it to implement normal sequence lock.
101cfg_has_fast_atomic_64! {
102    mod seq_lock;
103}
104cfg_no_fast_atomic_64! {
105    #[path = "seq_lock_wide.rs"]
106    mod seq_lock;
107}
108
109use core::{cell::UnsafeCell, mem, sync::atomic::Ordering};
110
111use seq_lock::{SeqLock, SeqLockWriteGuard};
112use utils::CachePadded;
113
114// Some 64-bit architectures have ABI with 32-bit pointer width (e.g., x86_64 X32 ABI,
115// AArch64 ILP32 ABI, mips64 N32 ABI). On those targets, AtomicU64 is fast,
116// so use it to reduce chunks of byte-wise atomic memcpy.
117use seq_lock::{AtomicChunk, Chunk};
118
119// Adapted from https://github.com/crossbeam-rs/crossbeam/blob/crossbeam-utils-0.8.7/crossbeam-utils/src/atomic/atomic_cell.rs#L969-L1016.
120#[inline]
121#[must_use]
122fn lock(addr: usize) -> &'static SeqLock {
123    // The number of locks is a prime number because we want to make sure `addr % LEN` gets
124    // dispersed across all locks.
125    //
126    // crossbeam-utils 0.8.7 uses 97 here but does not use CachePadded,
127    // so the actual concurrency level will be smaller.
128    const LEN: usize = 67;
129    const L: CachePadded<SeqLock> = CachePadded::new(SeqLock::new());
130    static LOCKS: [CachePadded<SeqLock>; LEN] = [
131        L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L,
132        L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L,
133        L, L, L, L, L, L, L,
134    ];
135
136    // If the modulus is a constant number, the compiler will use crazy math to transform this into
137    // a sequence of cheap arithmetic operations rather than using the slow modulo instruction.
138    &LOCKS[addr % LEN]
139}
140
141macro_rules! atomic {
142    ($atomic_type:ident, $int_type:ident, $align:literal) => {
143        #[repr(C, align($align))]
144        pub(crate) struct $atomic_type {
145            v: UnsafeCell<$int_type>,
146        }
147
148        impl $atomic_type {
149            const LEN: usize = mem::size_of::<$int_type>() / mem::size_of::<Chunk>();
150
151            #[inline]
152            unsafe fn chunks(&self) -> &[AtomicChunk; Self::LEN] {
153                static_assert!($atomic_type::LEN > 1);
154                static_assert!(mem::size_of::<$int_type>() % mem::size_of::<Chunk>() == 0);
155
156                // SAFETY: the caller must uphold the safety contract for `chunks`.
157                unsafe { &*(self.v.get() as *const $int_type as *const [AtomicChunk; Self::LEN]) }
158            }
159
160            #[inline]
161            fn optimistic_read(&self) -> $int_type {
162                // Using `MaybeUninit<[usize; Self::LEN]>` here doesn't change codegen: https://godbolt.org/z/86f8s733M
163                let mut dst: [Chunk; Self::LEN] = [0; Self::LEN];
164                // SAFETY:
165                // - There are no threads that perform non-atomic concurrent write operations.
166                // - There is no writer that updates the value using atomic operations of different granularity.
167                //
168                // If the atomic operation is not used here, it will cause a data race
169                // when `write` performs concurrent write operation.
170                // Such a data race is sometimes considered virtually unproblematic
171                // in SeqLock implementations:
172                //
173                // - https://github.com/Amanieu/seqlock/issues/2
174                // - https://github.com/crossbeam-rs/crossbeam/blob/crossbeam-utils-0.8.7/crossbeam-utils/src/atomic/atomic_cell.rs#L1111-L1116
175                // - https://rust-lang.zulipchat.com/#narrow/stream/136281-t-lang.2Fwg-unsafe-code-guidelines/topic/avoiding.20UB.20due.20to.20races.20by.20discarding.20result.3F
176                //
177                // However, in our use case, the implementation that loads/stores value as
178                // chunks of usize is enough fast and sound, so we use that implementation.
179                //
180                // See also atomic-memcpy crate, a generic implementation of this pattern:
181                // https://github.com/taiki-e/atomic-memcpy
182                let chunks = unsafe { self.chunks() };
183                for i in 0..Self::LEN {
184                    dst[i] = chunks[i].load(Ordering::Relaxed);
185                }
186                // SAFETY: integers are plain old data types so we can always transmute to them.
187                unsafe { mem::transmute::<[Chunk; Self::LEN], $int_type>(dst) }
188            }
189
190            #[inline]
191            fn read(&self, _guard: &SeqLockWriteGuard<'static>) -> $int_type {
192                // This calls optimistic_read that can return teared value, but the resulting value
193                // is guaranteed not to be teared because we hold the lock to write.
194                self.optimistic_read()
195            }
196
197            #[inline]
198            fn write(&self, val: $int_type, _guard: &SeqLockWriteGuard<'static>) {
199                // SAFETY: integers are plain old data types so we can always transmute them to arrays of integers.
200                let val = unsafe { mem::transmute::<$int_type, [Chunk; Self::LEN]>(val) };
201                // SAFETY:
202                // - The guard guarantees that we hold the lock to write.
203                // - There are no threads that perform non-atomic concurrent read or write operations.
204                //
205                // See optimistic_read for the reason that atomic operations are used here.
206                let chunks = unsafe { self.chunks() };
207                for i in 0..Self::LEN {
208                    chunks[i].store(val[i], Ordering::Relaxed);
209                }
210            }
211        }
212
213        // Send is implicitly implemented.
214        // SAFETY: any data races are prevented by the lock and atomic operation.
215        unsafe impl Sync for $atomic_type {}
216
217        impl_default_no_fetch_ops!($atomic_type, $int_type);
218        impl_default_bit_opts!($atomic_type, $int_type);
219        impl $atomic_type {
220            #[inline]
221            pub(crate) const fn new(v: $int_type) -> Self {
222                Self { v: UnsafeCell::new(v) }
223            }
224
225            #[inline]
226            pub(crate) fn is_lock_free() -> bool {
227                Self::IS_ALWAYS_LOCK_FREE
228            }
229            pub(crate) const IS_ALWAYS_LOCK_FREE: bool = false;
230
231            #[inline]
232            pub(crate) fn get_mut(&mut self) -> &mut $int_type {
233                // SAFETY: the mutable reference guarantees unique ownership.
234                // (UnsafeCell::get_mut requires Rust 1.50)
235                unsafe { &mut *self.v.get() }
236            }
237
238            #[inline]
239            #[cfg_attr(all(debug_assertions, not(portable_atomic_no_track_caller)), track_caller)]
240            pub(crate) fn load(&self, order: Ordering) -> $int_type {
241                crate::utils::assert_load_ordering(order);
242                let lock = lock(self.v.get() as usize);
243
244                // Try doing an optimistic read first.
245                if let Some(stamp) = lock.optimistic_read() {
246                    let val = self.optimistic_read();
247
248                    if lock.validate_read(stamp) {
249                        return val;
250                    }
251                }
252
253                // Grab a regular write lock so that writers don't starve this load.
254                let guard = lock.write();
255                let val = self.read(&guard);
256                // The value hasn't been changed. Drop the guard without incrementing the stamp.
257                guard.abort();
258                val
259            }
260
261            #[inline]
262            #[cfg_attr(all(debug_assertions, not(portable_atomic_no_track_caller)), track_caller)]
263            pub(crate) fn store(&self, val: $int_type, order: Ordering) {
264                crate::utils::assert_store_ordering(order);
265                let guard = lock(self.v.get() as usize).write();
266                self.write(val, &guard)
267            }
268
269            #[inline]
270            pub(crate) fn swap(&self, val: $int_type, _order: Ordering) -> $int_type {
271                let guard = lock(self.v.get() as usize).write();
272                let prev = self.read(&guard);
273                self.write(val, &guard);
274                prev
275            }
276
277            #[inline]
278            #[cfg_attr(all(debug_assertions, not(portable_atomic_no_track_caller)), track_caller)]
279            pub(crate) fn compare_exchange(
280                &self,
281                current: $int_type,
282                new: $int_type,
283                success: Ordering,
284                failure: Ordering,
285            ) -> Result<$int_type, $int_type> {
286                crate::utils::assert_compare_exchange_ordering(success, failure);
287                let guard = lock(self.v.get() as usize).write();
288                let prev = self.read(&guard);
289                if prev == current {
290                    self.write(new, &guard);
291                    Ok(prev)
292                } else {
293                    // The value hasn't been changed. Drop the guard without incrementing the stamp.
294                    guard.abort();
295                    Err(prev)
296                }
297            }
298
299            #[inline]
300            #[cfg_attr(all(debug_assertions, not(portable_atomic_no_track_caller)), track_caller)]
301            pub(crate) fn compare_exchange_weak(
302                &self,
303                current: $int_type,
304                new: $int_type,
305                success: Ordering,
306                failure: Ordering,
307            ) -> Result<$int_type, $int_type> {
308                self.compare_exchange(current, new, success, failure)
309            }
310
311            #[inline]
312            pub(crate) fn fetch_add(&self, val: $int_type, _order: Ordering) -> $int_type {
313                let guard = lock(self.v.get() as usize).write();
314                let prev = self.read(&guard);
315                self.write(prev.wrapping_add(val), &guard);
316                prev
317            }
318
319            #[inline]
320            pub(crate) fn fetch_sub(&self, val: $int_type, _order: Ordering) -> $int_type {
321                let guard = lock(self.v.get() as usize).write();
322                let prev = self.read(&guard);
323                self.write(prev.wrapping_sub(val), &guard);
324                prev
325            }
326
327            #[inline]
328            pub(crate) fn fetch_and(&self, val: $int_type, _order: Ordering) -> $int_type {
329                let guard = lock(self.v.get() as usize).write();
330                let prev = self.read(&guard);
331                self.write(prev & val, &guard);
332                prev
333            }
334
335            #[inline]
336            pub(crate) fn fetch_nand(&self, val: $int_type, _order: Ordering) -> $int_type {
337                let guard = lock(self.v.get() as usize).write();
338                let prev = self.read(&guard);
339                self.write(!(prev & val), &guard);
340                prev
341            }
342
343            #[inline]
344            pub(crate) fn fetch_or(&self, val: $int_type, _order: Ordering) -> $int_type {
345                let guard = lock(self.v.get() as usize).write();
346                let prev = self.read(&guard);
347                self.write(prev | val, &guard);
348                prev
349            }
350
351            #[inline]
352            pub(crate) fn fetch_xor(&self, val: $int_type, _order: Ordering) -> $int_type {
353                let guard = lock(self.v.get() as usize).write();
354                let prev = self.read(&guard);
355                self.write(prev ^ val, &guard);
356                prev
357            }
358
359            #[inline]
360            pub(crate) fn fetch_max(&self, val: $int_type, _order: Ordering) -> $int_type {
361                let guard = lock(self.v.get() as usize).write();
362                let prev = self.read(&guard);
363                self.write(core::cmp::max(prev, val), &guard);
364                prev
365            }
366
367            #[inline]
368            pub(crate) fn fetch_min(&self, val: $int_type, _order: Ordering) -> $int_type {
369                let guard = lock(self.v.get() as usize).write();
370                let prev = self.read(&guard);
371                self.write(core::cmp::min(prev, val), &guard);
372                prev
373            }
374
375            #[inline]
376            pub(crate) fn fetch_not(&self, _order: Ordering) -> $int_type {
377                let guard = lock(self.v.get() as usize).write();
378                let prev = self.read(&guard);
379                self.write(!prev, &guard);
380                prev
381            }
382            #[inline]
383            pub(crate) fn not(&self, order: Ordering) {
384                self.fetch_not(order);
385            }
386
387            #[inline]
388            pub(crate) fn fetch_neg(&self, _order: Ordering) -> $int_type {
389                let guard = lock(self.v.get() as usize).write();
390                let prev = self.read(&guard);
391                self.write(prev.wrapping_neg(), &guard);
392                prev
393            }
394            #[inline]
395            pub(crate) fn neg(&self, order: Ordering) {
396                self.fetch_neg(order);
397            }
398
399            #[inline]
400            pub(crate) const fn as_ptr(&self) -> *mut $int_type {
401                self.v.get()
402            }
403        }
404    };
405}
406
407#[cfg_attr(portable_atomic_no_cfg_target_has_atomic, cfg(any(test, portable_atomic_no_atomic_64)))]
408#[cfg_attr(
409    not(portable_atomic_no_cfg_target_has_atomic),
410    cfg(any(
411        test,
412        not(any(
413            target_has_atomic = "64",
414            all(
415                target_arch = "riscv32",
416                not(any(miri, portable_atomic_sanitize_thread)),
417                not(portable_atomic_no_asm),
418                any(
419                    target_feature = "experimental-zacas",
420                    portable_atomic_target_feature = "experimental-zacas",
421                ),
422            ),
423        ))
424    ))
425)]
426cfg_no_fast_atomic_64! {
427    atomic!(AtomicI64, i64, 8);
428    atomic!(AtomicU64, u64, 8);
429}
430
431atomic!(AtomicI128, i128, 16);
432atomic!(AtomicU128, u128, 16);
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    cfg_no_fast_atomic_64! {
439        test_atomic_int!(i64);
440        test_atomic_int!(u64);
441    }
442    test_atomic_int!(i128);
443    test_atomic_int!(u128);
444
445    // load/store/swap implementation is not affected by signedness, so it is
446    // enough to test only unsigned types.
447    cfg_no_fast_atomic_64! {
448        stress_test!(u64);
449    }
450    stress_test!(u128);
451}