#[path = "fallback/outline_atomics.rs"]
mod fallback;
use core::{arch::asm, cell::UnsafeCell, mem, sync::atomic::Ordering};
use crate::utils::{Pair, U64};
const KUSER_HELPER_VERSION: usize = 0xFFFF0FFC;
const KUSER_CMPXCHG64: usize = 0xFFFF0F60;
#[inline]
fn __kuser_helper_version() -> i32 {
use core::sync::atomic::AtomicI32;
static CACHE: AtomicI32 = AtomicI32::new(0);
let mut v = CACHE.load(Ordering::Relaxed);
if v != 0 {
return v;
}
v = unsafe { (KUSER_HELPER_VERSION as *const i32).read() };
CACHE.store(v, Ordering::Relaxed);
v
}
#[inline]
fn has_kuser_cmpxchg64() -> bool {
if cfg!(portable_atomic_test_outline_atomics_detect_false) {
return false;
}
__kuser_helper_version() >= 5
}
#[inline]
unsafe fn __kuser_cmpxchg64(old_val: *const u64, new_val: *const u64, ptr: *mut u64) -> bool {
unsafe {
let f: extern "C" fn(*const u64, *const u64, *mut u64) -> u32 =
mem::transmute(KUSER_CMPXCHG64 as *const ());
f(old_val, new_val, ptr) == 0
}
}
#[inline]
unsafe fn byte_wise_atomic_load(src: *const u64) -> u64 {
unsafe {
let (out_lo, out_hi);
asm!(
"ldr {out_lo}, [{src}]",
"ldr {out_hi}, [{src}, #4]",
src = in(reg) src,
out_lo = out(reg) out_lo,
out_hi = out(reg) out_hi,
options(pure, nostack, preserves_flags, readonly),
);
U64 { pair: Pair { lo: out_lo, hi: out_hi } }.whole
}
}
#[inline(always)]
unsafe fn atomic_update_kuser_cmpxchg64<F>(dst: *mut u64, mut f: F) -> u64
where
F: FnMut(u64) -> u64,
{
debug_assert!(dst as usize % 8 == 0);
debug_assert!(has_kuser_cmpxchg64());
unsafe {
loop {
let prev = byte_wise_atomic_load(dst);
let next = f(prev);
if __kuser_cmpxchg64(&prev, &next, dst) {
return prev;
}
}
}
}
macro_rules! atomic_with_ifunc {
(
unsafe fn $name:ident($($arg:tt)*) $(-> $ret_ty:ty)? { $($kuser_cmpxchg64_fn_body:tt)* }
fallback = $seqcst_fallback_fn:ident
) => {
#[inline]
unsafe fn $name($($arg)*) $(-> $ret_ty)? {
unsafe fn kuser_cmpxchg64_fn($($arg)*) $(-> $ret_ty)? {
$($kuser_cmpxchg64_fn_body)*
}
unsafe {
ifunc!(unsafe fn($($arg)*) $(-> $ret_ty)? {
if has_kuser_cmpxchg64() {
kuser_cmpxchg64_fn
} else {
fallback::$seqcst_fallback_fn
}
})
}
}
};
}
atomic_with_ifunc! {
unsafe fn atomic_load(src: *mut u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(src, |old| old) }
}
fallback = atomic_load_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_store(dst: *mut u64, val: u64) {
unsafe { atomic_update_kuser_cmpxchg64(dst, |_| val); }
}
fallback = atomic_store_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_swap(dst: *mut u64, val: u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, |_| val) }
}
fallback = atomic_swap_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_compare_exchange(dst: *mut u64, old: u64, new: u64) -> (u64, bool) {
let prev = unsafe {
atomic_update_kuser_cmpxchg64(dst, |v| if v == old { new } else { v })
};
(prev, prev == old)
}
fallback = atomic_compare_exchange_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_add(dst: *mut u64, val: u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, |x| x.wrapping_add(val)) }
}
fallback = atomic_add_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_sub(dst: *mut u64, val: u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, |x| x.wrapping_sub(val)) }
}
fallback = atomic_sub_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_and(dst: *mut u64, val: u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, |x| x & val) }
}
fallback = atomic_and_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_nand(dst: *mut u64, val: u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, |x| !(x & val)) }
}
fallback = atomic_nand_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_or(dst: *mut u64, val: u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, |x| x | val) }
}
fallback = atomic_or_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_xor(dst: *mut u64, val: u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, |x| x ^ val) }
}
fallback = atomic_xor_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_max(dst: *mut u64, val: u64) -> u64 {
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
unsafe {
atomic_update_kuser_cmpxchg64(dst, |x| core::cmp::max(x as i64, val as i64) as u64)
}
}
fallback = atomic_max_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_umax(dst: *mut u64, val: u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, |x| core::cmp::max(x, val)) }
}
fallback = atomic_umax_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_min(dst: *mut u64, val: u64) -> u64 {
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
unsafe {
atomic_update_kuser_cmpxchg64(dst, |x| core::cmp::min(x as i64, val as i64) as u64)
}
}
fallback = atomic_min_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_umin(dst: *mut u64, val: u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, |x| core::cmp::min(x, val)) }
}
fallback = atomic_umin_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_not(dst: *mut u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, |x| !x) }
}
fallback = atomic_not_seqcst
}
atomic_with_ifunc! {
unsafe fn atomic_neg(dst: *mut u64) -> u64 {
unsafe { atomic_update_kuser_cmpxchg64(dst, u64::wrapping_neg) }
}
fallback = atomic_neg_seqcst
}
macro_rules! atomic64 {
($atomic_type:ident, $int_type:ident, $atomic_max:ident, $atomic_min:ident) => {
#[repr(C, align(8))]
pub(crate) struct $atomic_type {
v: UnsafeCell<$int_type>,
}
unsafe impl Sync for $atomic_type {}
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 {
has_kuser_cmpxchg64()
}
#[inline]
pub(crate) const fn is_always_lock_free() -> bool {
false
}
#[inline]
pub(crate) fn get_mut(&mut self) -> &mut $int_type {
unsafe { &mut *self.v.get() }
}
#[inline]
pub(crate) fn into_inner(self) -> $int_type {
self.v.into_inner()
}
#[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);
unsafe { atomic_load(self.v.get().cast::<u64>()) as $int_type }
}
#[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);
unsafe { atomic_store(self.v.get().cast::<u64>(), val as u64) }
}
#[inline]
pub(crate) fn swap(&self, val: $int_type, _order: Ordering) -> $int_type {
unsafe { atomic_swap(self.v.get().cast::<u64>(), val as u64) as $int_type }
}
#[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);
unsafe {
let (prev, ok) = atomic_compare_exchange(
self.v.get().cast::<u64>(),
current as u64,
new as u64,
);
if ok {
Ok(prev as $int_type)
} else {
Err(prev as $int_type)
}
}
}
#[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 fetch_add(&self, val: $int_type, _order: Ordering) -> $int_type {
unsafe { atomic_add(self.v.get().cast::<u64>(), val as u64) as $int_type }
}
#[inline]
pub(crate) fn fetch_sub(&self, val: $int_type, _order: Ordering) -> $int_type {
unsafe { atomic_sub(self.v.get().cast::<u64>(), val as u64) as $int_type }
}
#[inline]
pub(crate) fn fetch_and(&self, val: $int_type, _order: Ordering) -> $int_type {
unsafe { atomic_and(self.v.get().cast::<u64>(), val as u64) as $int_type }
}
#[inline]
pub(crate) fn fetch_nand(&self, val: $int_type, _order: Ordering) -> $int_type {
unsafe { atomic_nand(self.v.get().cast::<u64>(), val as u64) as $int_type }
}
#[inline]
pub(crate) fn fetch_or(&self, val: $int_type, _order: Ordering) -> $int_type {
unsafe { atomic_or(self.v.get().cast::<u64>(), val as u64) as $int_type }
}
#[inline]
pub(crate) fn fetch_xor(&self, val: $int_type, _order: Ordering) -> $int_type {
unsafe { atomic_xor(self.v.get().cast::<u64>(), val as u64) as $int_type }
}
#[inline]
pub(crate) fn fetch_max(&self, val: $int_type, _order: Ordering) -> $int_type {
unsafe { $atomic_max(self.v.get().cast::<u64>(), val as u64) as $int_type }
}
#[inline]
pub(crate) fn fetch_min(&self, val: $int_type, _order: Ordering) -> $int_type {
unsafe { $atomic_min(self.v.get().cast::<u64>(), val as u64) as $int_type }
}
#[inline]
pub(crate) fn fetch_not(&self, _order: Ordering) -> $int_type {
unsafe { atomic_not(self.v.get().cast::<u64>()) as $int_type }
}
#[inline]
pub(crate) fn not(&self, order: Ordering) {
self.fetch_not(order);
}
#[inline]
pub(crate) fn fetch_neg(&self, _order: Ordering) -> $int_type {
unsafe { atomic_neg(self.v.get().cast::<u64>()) as $int_type }
}
#[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()
}
}
};
}
atomic64!(AtomicI64, i64, atomic_max, atomic_min);
atomic64!(AtomicU64, u64, atomic_umax, atomic_umin);
#[allow(
clippy::alloc_instead_of_core,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core,
clippy::undocumented_unsafe_blocks,
clippy::wildcard_imports
)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kuser_helper_version() {
let version = __kuser_helper_version();
assert!(version >= 5, "{:?}", version);
assert_eq!(version, unsafe { (KUSER_HELPER_VERSION as *const i32).read() });
}
test_atomic_int!(i64);
test_atomic_int!(u64);
stress_test!(u64);
}