include!("macros.rs");
#[cfg(not(portable_atomic_no_asm))]
use core::arch::asm;
use core::sync::atomic::Ordering;
#[derive(Clone, Copy)]
#[repr(C)]
union U128 {
whole: u128,
pair: [u64; 2],
}
#[inline(always)]
unsafe fn ldxp(src: *mut u128, order: Ordering) -> u128 {
debug_assert!(src as usize % 16 == 0);
unsafe {
let (prev_lo, prev_hi);
match order {
Ordering::Relaxed => {
asm!(
"ldxp {prev_lo}, {prev_hi}, [{src}]",
src = in(reg) src,
prev_lo = out(reg) prev_lo,
prev_hi = out(reg) prev_hi,
options(nostack),
);
}
Ordering::Acquire | Ordering::SeqCst => {
asm!(
"ldaxp {prev_lo}, {prev_hi}, [{src}]",
src = in(reg) src,
prev_lo = out(reg) prev_lo,
prev_hi = out(reg) prev_hi,
options(nostack),
);
}
_ => unreachable!("{:?}", order),
}
U128 { pair: [prev_lo, prev_hi] }.whole
}
}
#[inline(always)]
unsafe fn stxp(dst: *mut u128, val: u128, order: Ordering) -> bool {
debug_assert!(dst as usize % 16 == 0);
unsafe {
let r: i32;
let val = U128 { whole: val };
match order {
Ordering::Relaxed => {
asm!(
"stxp {r:w}, {val_lo}, {val_hi}, [{dst}]",
dst = in(reg) dst,
r = out(reg) r,
val_lo = in(reg) val.pair[0],
val_hi = in(reg) val.pair[1],
options(nostack),
);
}
Ordering::Release | Ordering::SeqCst => {
asm!(
"stlxp {r:w}, {val_lo}, {val_hi}, [{dst}]",
dst = in(reg) dst,
r = out(reg) r,
val_lo = in(reg) val.pair[0],
val_hi = in(reg) val.pair[1],
options(nostack),
);
}
_ => unreachable!("{:?}", order),
}
debug_assert!(r == 0 || r == 1, "r={}", r);
r == 0
}
}
#[cfg(any(
target_feature = "lse",
portable_atomic_target_feature = "lse",
not(portable_atomic_no_aarch64_target_feature),
))]
#[cfg_attr(not(portable_atomic_no_aarch64_target_feature), target_feature(enable = "lse"))]
#[inline]
unsafe fn _casp(dst: *mut u128, old: u128, new: u128, order: Ordering) -> u128 {
debug_assert!(dst as usize % 16 == 0);
unsafe {
let old = U128 { whole: old };
let new = U128 { whole: new };
let (prev_lo, prev_hi);
match order {
Ordering::Relaxed => {
asm!(
"casp x6, x7, x4, x5, [{dst}]",
dst = in(reg) dst,
inout("x6") old.pair[0] => prev_lo,
inout("x7") old.pair[1] => prev_hi,
in("x4") new.pair[0],
in("x5") new.pair[1],
options(nostack),
);
}
Ordering::Acquire => {
asm!(
"caspa x6, x7, x4, x5, [{dst}]",
dst = in(reg) dst,
inout("x6") old.pair[0] => prev_lo,
inout("x7") old.pair[1] => prev_hi,
in("x4") new.pair[0],
in("x5") new.pair[1],
options(nostack),
);
}
Ordering::Release => {
asm!(
"caspl x6, x7, x4, x5, [{dst}]",
dst = in(reg) dst,
inout("x6") old.pair[0] => prev_lo,
inout("x7") old.pair[1] => prev_hi,
in("x4") new.pair[0],
in("x5") new.pair[1],
options(nostack),
);
}
Ordering::AcqRel | Ordering::SeqCst => {
asm!(
"caspal x6, x7, x4, x5, [{dst}]",
dst = in(reg) dst,
inout("x6") old.pair[0] => prev_lo,
inout("x7") old.pair[1] => prev_hi,
in("x4") new.pair[0],
in("x5") new.pair[1],
options(nostack),
);
}
_ => unreachable!("{:?}", order),
}
U128 { pair: [prev_lo, prev_hi] }.whole
}
}
#[cfg(any(target_feature = "lse2", portable_atomic_target_feature = "lse2", test))]
#[inline]
unsafe fn _ldp(src: *mut u128, order: Ordering) -> u128 {
debug_assert!(src as usize % 16 == 0);
unsafe {
let (prev_lo, prev_hi);
match order {
Ordering::Relaxed => {
asm!(
"ldp {prev_lo}, {prev_hi}, [{src}]",
src = in(reg) src,
prev_lo = lateout(reg) prev_lo,
prev_hi = lateout(reg) prev_hi,
options(nostack),
);
}
Ordering::Acquire => {
asm!(
"ldp {prev_lo}, {prev_hi}, [{src}]",
"dmb ishld",
src = in(reg) src,
prev_lo = lateout(reg) prev_lo,
prev_hi = lateout(reg) prev_hi,
options(nostack),
);
}
Ordering::SeqCst => {
asm!(
"ldp {prev_lo}, {prev_hi}, [{src}]",
"dmb ish",
src = in(reg) src,
prev_lo = lateout(reg) prev_lo,
prev_hi = lateout(reg) prev_hi,
options(nostack),
);
}
_ => unreachable!("{:?}", order),
}
U128 { pair: [prev_lo, prev_hi] }.whole
}
}
#[cfg(any(target_feature = "lse2", portable_atomic_target_feature = "lse2", test))]
#[inline]
unsafe fn _stp(dst: *mut u128, val: u128, order: Ordering) {
debug_assert!(dst as usize % 16 == 0);
unsafe {
let val = U128 { whole: val };
match order {
Ordering::Relaxed => {
asm!(
"stp {val_lo}, {val_hi}, [{dst}]",
dst = in(reg) dst,
val_lo = in(reg) val.pair[0],
val_hi = in(reg) val.pair[1],
options(nostack),
);
}
Ordering::Release => {
asm!(
"dmb ish",
"stp {val_lo}, {val_hi}, [{dst}]",
dst = in(reg) dst,
val_lo = in(reg) val.pair[0],
val_hi = in(reg) val.pair[1],
options(nostack),
);
}
Ordering::SeqCst => {
asm!(
"dmb ish",
"stp {val_lo}, {val_hi}, [{dst}]",
"dmb ish",
dst = in(reg) dst,
val_lo = in(reg) val.pair[0],
val_hi = in(reg) val.pair[1],
options(nostack),
);
}
_ => unreachable!("{:?}", order),
}
}
}
#[inline]
fn ldx_ordering(order: Ordering) -> Ordering {
match order {
Ordering::Release | Ordering::Relaxed => Ordering::Relaxed,
Ordering::SeqCst => Ordering::SeqCst,
Ordering::AcqRel | Ordering::Acquire => Ordering::Acquire,
_ => unreachable!("{:?}", order),
}
}
#[inline]
fn stx_ordering(order: Ordering) -> Ordering {
match order {
Ordering::Acquire | Ordering::Relaxed => Ordering::Relaxed,
Ordering::SeqCst => Ordering::SeqCst,
Ordering::AcqRel | Ordering::Release => Ordering::Release,
_ => unreachable!("{:?}", order),
}
}
#[inline]
unsafe fn atomic_compare_exchange(
dst: *mut u128,
old: u128,
new: u128,
success: Ordering,
failure: Ordering,
) -> Result<u128, u128> {
let success = crate::utils::upgrade_success_ordering(success, failure);
#[cfg(any(target_feature = "lse", portable_atomic_target_feature = "lse"))]
let res = unsafe { _casp(dst, old, new, success) };
#[cfg(not(all(
not(portable_atomic_no_aarch64_target_feature),
feature = "outline-atomics",
// https://github.com/rust-lang/stdarch/blob/bcbe010614f398ec86f3a9274d22e33e5f2ee60b/crates/std_detect/src/detect/mod.rs
// Note: aarch64 freebsd is tier 3, so std may not be available.
any(feature = "std", target_os = "linux", target_os = "windows", /* target_os = "freebsd" */)
)))]
#[cfg(not(any(target_feature = "lse", portable_atomic_target_feature = "lse")))]
let res = unsafe { _compare_exchange_ldxp_stxp(dst, old, new, success) };
#[cfg(all(
not(portable_atomic_no_aarch64_target_feature),
feature = "outline-atomics",
// https://github.com/rust-lang/stdarch/blob/bcbe010614f398ec86f3a9274d22e33e5f2ee60b/crates/std_detect/src/detect/mod.rs
// Note: aarch64 freebsd is tier 3, so std may not be available.
any(feature = "std", target_os = "linux", target_os = "windows", /* target_os = "freebsd" */)
))]
#[cfg(not(any(target_feature = "lse", portable_atomic_target_feature = "lse")))]
let res = {
extern crate std;
unsafe {
ifunc!(unsafe fn(dst: *mut u128, old: u128, new: u128, success: Ordering) -> u128;
if std::arch::is_aarch64_feature_detected!("lse") {
_casp
} else {
_compare_exchange_ldxp_stxp
})
}
};
if res == old {
Ok(res)
} else {
Err(res)
}
}
use self::atomic_compare_exchange as atomic_compare_exchange_weak;
#[inline]
unsafe fn _compare_exchange_ldxp_stxp(
dst: *mut u128,
old: u128,
new: u128,
success: Ordering,
) -> u128 {
unsafe { atomic_update(dst, success, |x| if x == old { new } else { x }) }
}
#[inline]
unsafe fn atomic_update<F>(dst: *mut u128, order: Ordering, mut f: F) -> u128
where
F: FnMut(u128) -> u128,
{
debug_assert!(dst as usize % 16 == 0);
unsafe {
let ldx_order = ldx_ordering(order);
let stx_order = stx_ordering(order);
let mut prev;
loop {
prev = ldxp(dst, ldx_order);
let next = f(prev);
if stxp(dst, next, stx_order) {
break;
}
}
prev
}
}
#[inline]
unsafe fn atomic_load(src: *mut u128, order: Ordering) -> u128 {
#[cfg(any(target_feature = "lse2", portable_atomic_target_feature = "lse2"))]
unsafe {
_ldp(src, order)
}
#[cfg(not(any(target_feature = "lse2", portable_atomic_target_feature = "lse2")))]
unsafe {
_compare_exchange_ldxp_stxp(src, 0, 0, order)
}
}
#[inline]
unsafe fn atomic_store(dst: *mut u128, val: u128, order: Ordering) {
#[cfg(any(target_feature = "lse2", portable_atomic_target_feature = "lse2"))]
unsafe {
_stp(dst, val, order);
}
#[cfg(not(any(target_feature = "lse2", portable_atomic_target_feature = "lse2")))]
unsafe {
atomic_swap(dst, val, order);
}
}
#[inline]
unsafe fn atomic_swap(dst: *mut u128, val: u128, order: Ordering) -> u128 {
unsafe { atomic_update(dst, order, |_| val) }
}
atomic128!(AtomicI128, i128);
atomic128!(AtomicU128, u128);
#[cfg(not(any(miri, sanitize_thread)))]
#[cfg(test)]
mod tests {
use super::*;
test_atomic_int!(i128);
test_atomic_int!(u128);
}
#[cfg(not(any(miri, sanitize_thread)))]
#[cfg(test)]
#[allow(dead_code, clippy::undocumented_unsafe_blocks, clippy::wildcard_imports)]
mod no_outline_atomics {
use super::*;
#[inline]
unsafe fn atomic_compare_exchange(
dst: *mut u128,
old: u128,
new: u128,
success: Ordering,
_failure: Ordering,
) -> Result<u128, u128> {
let res = unsafe { _compare_exchange_ldxp_stxp(dst, old, new, success) };
if res == old {
Ok(res)
} else {
Err(res)
}
}
use self::atomic_compare_exchange as atomic_compare_exchange_weak;
atomic128!(AtomicI128, i128);
atomic128!(AtomicU128, u128);
mod tests {
use super::*;
test_atomic_int!(i128);
test_atomic_int!(u128);
}
}