#![cfg_attr(
any(
all(
target_arch = "riscv32",
not(any(miri, portable_atomic_sanitize_thread)),
any(not(portable_atomic_no_asm), portable_atomic_unstable_asm),
not(portable_atomic_no_outline_atomics),
any(target_os = "linux", target_os = "android"),
),
all(
target_arch = "arm",
not(any(miri, portable_atomic_sanitize_thread)),
any(not(portable_atomic_no_asm), portable_atomic_unstable_asm),
any(target_os = "linux", target_os = "android"),
any(test, not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))),
not(portable_atomic_no_outline_atomics),
),
),
allow(dead_code)
)]
#[cfg(target_pointer_width = "16")]
compile_error!(
"internal error: unreachable since atomics for 16-bit targets can always be provided by disable interrupts"
);
#[cfg(target_pointer_width = "128")]
compile_error!(
"internal error: unreachable since 128-bit target either has atomic CAS for the pointer width or does not have CAS"
);
mod utils;
cfg_has_fast_atomic_64! {
mod seq_lock;
type AtomicChunk = core::sync::atomic::AtomicU64;
type Chunk = u64;
}
cfg_no_fast_atomic_64! {
#[path = "seq_lock_wide.rs"]
mod seq_lock;
type AtomicChunk = core::sync::atomic::AtomicU32;
type Chunk = u32;
}
use core::{cell::UnsafeCell, mem, sync::atomic::Ordering};
use self::{
seq_lock::{SeqLock, SeqLockWriteGuard},
utils::CachePadded,
};
#[cfg(portable_atomic_no_strict_provenance)]
use crate::utils::ptr::PtrExt as _;
use crate::utils::unlikely;
#[inline]
#[must_use]
fn lock(addr: usize) -> &'static SeqLock {
const LEN: usize = 67;
const L: CachePadded<SeqLock> = CachePadded::new(SeqLock::new());
static LOCKS: [CachePadded<SeqLock>; LEN] = [
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,
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,
L, L, L, L, L, L, L,
];
&LOCKS[addr % LEN]
}
macro_rules! atomic {
($atomic_type:ident, $int_type:ident, $align:literal) => {
#[repr(C, align($align))]
pub(crate) struct $atomic_type {
v: UnsafeCell<$int_type>,
}
impl $atomic_type {
const LEN: usize = mem::size_of::<$int_type>() / mem::size_of::<Chunk>();
#[inline]
unsafe fn chunks(&self) -> &[AtomicChunk; Self::LEN] {
static_assert!($atomic_type::LEN > 1);
static_assert!(mem::size_of::<$int_type>() % mem::size_of::<Chunk>() == 0);
unsafe { &*(self.v.get() as *const $int_type as *const [AtomicChunk; Self::LEN]) }
}
#[inline]
fn optimistic_read(&self) -> $int_type {
let mut dst: [Chunk; Self::LEN] = [0; Self::LEN];
let chunks = unsafe { self.chunks() };
for i in 0..Self::LEN {
dst[i] = chunks[i].load(Ordering::Relaxed);
}
unsafe { mem::transmute::<[Chunk; Self::LEN], $int_type>(dst) }
}
#[inline]
fn read(&self, _guard: &SeqLockWriteGuard<'static>) -> $int_type {
self.optimistic_read()
}
#[inline]
fn write(&self, val: $int_type, _guard: &SeqLockWriteGuard<'static>) {
let val = unsafe { mem::transmute::<$int_type, [Chunk; Self::LEN]>(val) };
let chunks = unsafe { self.chunks() };
for i in 0..Self::LEN {
chunks[i].store(val[i], Ordering::Relaxed);
}
}
}
unsafe impl Sync for $atomic_type {}
#[cfg(any(
test,
not(any(
all(
target_arch = "x86_64",
not(all(
any(miri, portable_atomic_sanitize_thread),
portable_atomic_no_cmpxchg16b_intrinsic,
)),
any(not(portable_atomic_no_asm), portable_atomic_unstable_asm),
not(portable_atomic_no_outline_atomics),
not(any(target_env = "sgx", miri)),
),
all(
target_arch = "powerpc64",
not(portable_atomic_no_asm),
not(portable_atomic_no_outline_atomics),
any(
all(
target_os = "linux",
any(
all(
target_env = "gnu",
any(target_endian = "little", not(target_feature = "crt-static")),
),
all(
target_env = "musl",
any(not(target_feature = "crt-static"), feature = "std"),
),
target_env = "ohos",
all(target_env = "uclibc", not(target_feature = "crt-static")),
portable_atomic_outline_atomics,
),
),
target_os = "android",
all(
target_os = "freebsd",
any(
target_endian = "little",
not(target_feature = "crt-static"),
portable_atomic_outline_atomics,
),
),
target_os = "openbsd",
all(
target_os = "aix",
not(portable_atomic_pre_llvm_20),
portable_atomic_outline_atomics, // TODO(aix): currently disabled by default
),
),
not(any(miri, portable_atomic_sanitize_thread)),
),
all(
target_arch = "riscv64",
not(any(miri, portable_atomic_sanitize_thread)),
any(not(portable_atomic_no_asm), portable_atomic_unstable_asm),
not(portable_atomic_no_outline_atomics),
any(target_os = "linux", target_os = "android"),
),
)),
))]
items!({
impl_default_no_fetch_ops!($atomic_type, $int_type);
impl_default_bit_opts!($atomic_type, $int_type);
impl $atomic_type {
#[inline]
pub(crate) const fn new(v: $int_type) -> Self {
Self { v: UnsafeCell::new(v) }
}
#[inline]
pub(crate) fn is_lock_free() -> bool {
Self::IS_ALWAYS_LOCK_FREE
}
pub(crate) const IS_ALWAYS_LOCK_FREE: bool = false;
#[inline]
#[cfg_attr(
all(debug_assertions, not(portable_atomic_no_track_caller)),
track_caller
)]
pub(crate) fn compare_exchange_weak(
&self,
current: $int_type,
new: $int_type,
success: Ordering,
failure: Ordering,
) -> Result<$int_type, $int_type> {
self.compare_exchange(current, new, success, failure)
}
#[inline]
pub(crate) fn not(&self, order: Ordering) {
self.fetch_not(order);
}
#[inline]
pub(crate) fn neg(&self, order: Ordering) {
self.fetch_neg(order);
}
#[inline]
pub(crate) const fn as_ptr(&self) -> *mut $int_type {
self.v.get()
}
}
});
#[cfg_attr(
any(
all(
target_arch = "x86_64",
not(all(
any(miri, portable_atomic_sanitize_thread),
portable_atomic_no_cmpxchg16b_intrinsic,
)),
any(not(portable_atomic_no_asm), portable_atomic_unstable_asm),
not(portable_atomic_no_outline_atomics),
not(any(target_env = "sgx", miri)),
),
all(
target_arch = "powerpc64",
not(portable_atomic_no_asm),
not(portable_atomic_no_outline_atomics),
any(
all(
target_os = "linux",
any(
all(
target_env = "gnu",
any(target_endian = "little", not(target_feature = "crt-static")),
),
all(
target_env = "musl",
any(not(target_feature = "crt-static"), feature = "std"),
),
target_env = "ohos",
all(target_env = "uclibc", not(target_feature = "crt-static")),
portable_atomic_outline_atomics,
),
),
target_os = "android",
all(
target_os = "freebsd",
any(
target_endian = "little",
not(target_feature = "crt-static"),
portable_atomic_outline_atomics,
),
),
target_os = "openbsd",
all(
target_os = "aix",
not(portable_atomic_pre_llvm_20),
portable_atomic_outline_atomics, // TODO(aix): currently disabled by default
),
),
not(any(miri, portable_atomic_sanitize_thread)),
),
all(
target_arch = "riscv64",
not(any(miri, portable_atomic_sanitize_thread)),
any(not(portable_atomic_no_asm), portable_atomic_unstable_asm),
not(portable_atomic_no_outline_atomics),
any(target_os = "linux", target_os = "android"),
),
),
allow(dead_code)
)]
impl $atomic_type {
#[inline]
#[cfg_attr(all(debug_assertions, not(portable_atomic_no_track_caller)), track_caller)]
pub(crate) fn load(&self, order: Ordering) -> $int_type {
crate::utils::assert_load_ordering(order);
let lock = lock(self.v.get().addr());
if let Some(stamp) = lock.optimistic_read(order) {
let val = self.optimistic_read();
if lock.validate_read(stamp, order) {
return val;
}
}
let guard = lock.write(
Ordering::AcqRel, );
let val = self.read(&guard);
guard.abort();
val
}
#[inline]
#[cfg_attr(all(debug_assertions, not(portable_atomic_no_track_caller)), track_caller)]
pub(crate) fn store(&self, val: $int_type, order: Ordering) {
crate::utils::assert_store_ordering(order);
let guard = lock(self.v.get().addr()).write(order);
self.write(val, &guard)
}
#[inline]
pub(crate) fn swap(&self, val: $int_type, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(val, &guard);
prev
}
#[inline]
#[cfg_attr(all(debug_assertions, not(portable_atomic_no_track_caller)), track_caller)]
pub(crate) fn compare_exchange(
&self,
current: $int_type,
new: $int_type,
success: Ordering,
failure: Ordering,
) -> Result<$int_type, $int_type> {
crate::utils::assert_compare_exchange_ordering(success, failure);
let order = if unlikely(success == Ordering::SeqCst || failure == Ordering::SeqCst)
{
Ordering::SeqCst
} else {
Ordering::AcqRel
};
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
if prev == current {
self.write(new, &guard);
Ok(prev)
} else {
guard.abort();
Err(prev)
}
}
#[inline]
pub(crate) fn fetch_add(&self, val: $int_type, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(prev.wrapping_add(val), &guard);
prev
}
#[inline]
pub(crate) fn fetch_sub(&self, val: $int_type, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(prev.wrapping_sub(val), &guard);
prev
}
#[inline]
pub(crate) fn fetch_and(&self, val: $int_type, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(prev & val, &guard);
prev
}
#[inline]
pub(crate) fn fetch_nand(&self, val: $int_type, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(!(prev & val), &guard);
prev
}
#[inline]
pub(crate) fn fetch_or(&self, val: $int_type, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(prev | val, &guard);
prev
}
#[inline]
pub(crate) fn fetch_xor(&self, val: $int_type, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(prev ^ val, &guard);
prev
}
#[inline]
pub(crate) fn fetch_not(&self, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(!prev, &guard);
prev
}
#[inline]
pub(crate) fn fetch_neg(&self, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(prev.wrapping_neg(), &guard);
prev
}
}
impl $atomic_type {
#[inline]
pub(crate) fn fetch_max(&self, val: $int_type, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(core::cmp::max(prev, val), &guard);
prev
}
#[inline]
pub(crate) fn fetch_min(&self, val: $int_type, order: Ordering) -> $int_type {
let guard = lock(self.v.get().addr()).write(order);
let prev = self.read(&guard);
self.write(core::cmp::min(prev, val), &guard);
prev
}
}
};
}
#[cfg_attr(
portable_atomic_no_cfg_target_has_atomic,
cfg(any(
test,
not(any(
not(portable_atomic_no_atomic_64),
all(
target_arch = "riscv32",
not(any(miri, portable_atomic_sanitize_thread)),
any(not(portable_atomic_no_asm), portable_atomic_unstable_asm),
any(target_feature = "zacas", portable_atomic_target_feature = "zacas"),
),
))
))
)]
#[cfg_attr(
not(portable_atomic_no_cfg_target_has_atomic),
cfg(any(
test,
not(any(
target_has_atomic = "64",
all(
target_arch = "riscv32",
not(any(miri, portable_atomic_sanitize_thread)),
any(not(portable_atomic_no_asm), portable_atomic_unstable_asm),
any(target_feature = "zacas", portable_atomic_target_feature = "zacas"),
),
))
))
)]
cfg_no_fast_atomic_64! {
atomic!(AtomicI64, i64, 8);
atomic!(AtomicU64, u64, 8);
}
atomic!(AtomicI128, i128, 16);
atomic!(AtomicU128, u128, 16);
#[cfg(test)]
mod tests {
use super::*;
cfg_no_fast_atomic_64! {
test_atomic_int!(i64);
test_atomic_int!(u64);
}
test_atomic_int!(i128);
test_atomic_int!(u128);
cfg_no_fast_atomic_64! {
stress_test!(u64);
}
stress_test!(u128);
}