include!("macros.rs");
#[cfg(not(any(target_feature = "zacas", portable_atomic_target_feature = "zacas")))]
#[path = "../fallback/outline_atomics.rs"]
mod fallback;
#[cfg(not(portable_atomic_no_outline_atomics))]
#[cfg(any(test, not(any(target_feature = "zacas", portable_atomic_target_feature = "zacas"))))]
#[cfg(any(target_os = "linux", target_os = "android"))]
#[path = "../detect/riscv_linux.rs"]
mod detect;
#[cfg(not(portable_atomic_no_asm))]
use core::arch::asm;
use core::sync::atomic::Ordering;
use crate::utils::{Pair, U128};
macro_rules! debug_assert_zacas {
() => {
#[cfg(not(any(target_feature = "zacas", portable_atomic_target_feature = "zacas")))]
{
debug_assert!(detect::detect().zacas());
}
};
}
#[cfg(not(portable_atomic_pre_llvm_20))]
macro_rules! start_zacas {
() => {
".option push\n.option arch, +zacas"
};
}
#[cfg(not(portable_atomic_pre_llvm_20))]
macro_rules! end_zacas {
() => {
".option pop"
};
}
#[cfg(not(portable_atomic_pre_llvm_20))]
macro_rules! atomic_rmw_amocas_order {
($op:ident, $order:ident) => {
atomic_rmw_amocas_order!($op, $order, failure = $order)
};
($op:ident, $order:ident, failure = $failure:ident) => {
match $order {
Ordering::Relaxed => $op!("", ""),
Ordering::Acquire => $op!("", ".aq"),
Ordering::Release => $op!("", ".rl"),
Ordering::AcqRel => $op!("", ".aqrl"),
Ordering::SeqCst if $failure == Ordering::SeqCst => $op!("fence rw,rw", ".aqrl"),
Ordering::SeqCst => $op!("", ".aqrl"),
_ => unreachable!(),
}
};
}
#[cfg(portable_atomic_pre_llvm_20)]
macro_rules! atomic_rmw_amocas_order_insn {
($op:ident, $order:ident) => {
atomic_rmw_amocas_order_insn!($op, $order, failure = $order)
};
($op:ident, $order:ident, failure = $failure:ident) => {
match $order {
Ordering::Relaxed => $op!("", "8"),
Ordering::Acquire => $op!("", "c"),
Ordering::Release => $op!("", "a"),
Ordering::AcqRel => $op!("", "e"),
Ordering::SeqCst if $failure == Ordering::SeqCst => $op!("fence rw,rw", "e"),
Ordering::SeqCst => $op!("", "e"),
_ => unreachable!(),
}
};
}
#[cfg(any(target_feature = "zacas", portable_atomic_target_feature = "zacas"))]
use self::atomic_load_zacas as atomic_load;
#[cfg(not(any(target_feature = "zacas", portable_atomic_target_feature = "zacas")))]
#[inline]
unsafe fn atomic_load(src: *mut u128, order: Ordering) -> u128 {
fn_alias! {
#[inline(never)]
unsafe fn(src: *mut u128) -> u128;
atomic_load_zacas_relaxed = atomic_load_zacas(Ordering::Relaxed);
atomic_load_zacas_acquire = atomic_load_zacas(Ordering::Acquire);
atomic_load_zacas_seqcst = atomic_load_zacas(Ordering::SeqCst);
}
unsafe {
match order {
Ordering::Relaxed => {
ifunc!(unsafe fn(src: *mut u128) -> u128 {
if detect::detect().zacas() {
atomic_load_zacas_relaxed
} else {
fallback::atomic_load_non_seqcst
}
})
}
Ordering::Acquire => {
ifunc!(unsafe fn(src: *mut u128) -> u128 {
if detect::detect().zacas() {
atomic_load_zacas_acquire
} else {
fallback::atomic_load_non_seqcst
}
})
}
Ordering::SeqCst => {
ifunc!(unsafe fn(src: *mut u128) -> u128 {
if detect::detect().zacas() {
atomic_load_zacas_seqcst
} else {
fallback::atomic_load_seqcst
}
})
}
_ => unreachable!(),
}
}
}
#[inline]
unsafe fn atomic_load_zacas(src: *mut u128, order: Ordering) -> u128 {
debug_assert!(src as usize % 16 == 0);
debug_assert_zacas!();
let (out_lo, out_hi);
unsafe {
#[cfg(not(portable_atomic_pre_llvm_20))]
macro_rules! load {
($fence:tt, $asm_order:tt) => {
asm!(
start_zacas!(),
$fence, concat!("amocas.q", $asm_order, " a2, a2, 0({src})"), end_zacas!(),
src = in(reg) ptr_reg!(src),
inout("a2") 0_u64 => out_lo,
inout("a3") 0_u64 => out_hi,
options(nostack, preserves_flags),
)
};
}
#[cfg(not(portable_atomic_pre_llvm_20))]
atomic_rmw_amocas_order!(load, order);
#[cfg(portable_atomic_pre_llvm_20)]
macro_rules! load {
($fence:tt, $insn_order:tt) => {
asm!(
$fence, concat!(".4byte 0x2", $insn_order, "c5462f"),
in("a0") ptr_reg!(src),
inout("a2") 0_u64 => out_lo,
inout("a3") 0_u64 => out_hi,
options(nostack, preserves_flags),
)
};
}
#[cfg(portable_atomic_pre_llvm_20)]
atomic_rmw_amocas_order_insn!(load, order);
U128 { pair: Pair { lo: out_lo, hi: out_hi } }.whole
}
}
#[inline]
unsafe fn atomic_store(dst: *mut u128, val: u128, order: Ordering) {
unsafe {
atomic_swap(dst, val, order);
}
}
#[inline]
unsafe fn atomic_compare_exchange(
dst: *mut u128,
old: u128,
new: u128,
success: Ordering,
failure: Ordering,
) -> Result<u128, u128> {
#[cfg(any(target_feature = "zacas", portable_atomic_target_feature = "zacas"))]
let (prev, ok) = unsafe { atomic_compare_exchange_zacas(dst, old, new, success, failure) };
#[cfg(not(any(target_feature = "zacas", portable_atomic_target_feature = "zacas")))]
let (prev, ok) = {
fn_alias! {
#[inline(never)]
unsafe fn(dst: *mut u128, old: u128, new: u128) -> (u128, bool);
zacas_relaxed_fn = atomic_compare_exchange_zacas(Ordering::Relaxed, Ordering::Relaxed);
zacas_acquire_fn = atomic_compare_exchange_zacas(Ordering::Acquire, Ordering::Acquire);
zacas_release_fn = atomic_compare_exchange_zacas(Ordering::Release, Ordering::Relaxed);
zacas_acqrel_fn = atomic_compare_exchange_zacas(Ordering::AcqRel, Ordering::Acquire);
zacas_seqcst_fn = atomic_compare_exchange_zacas(Ordering::SeqCst, Ordering::SeqCst);
}
let order = crate::utils::upgrade_success_ordering(success, failure);
unsafe {
match order {
Ordering::Relaxed => {
ifunc!(unsafe fn(dst: *mut u128, old: u128, new: u128) -> (u128, bool) {
if detect::detect().zacas() {
zacas_relaxed_fn
} else {
fallback::atomic_compare_exchange_non_seqcst
}
})
}
Ordering::Acquire => {
ifunc!(unsafe fn(dst: *mut u128, old: u128, new: u128) -> (u128, bool) {
if detect::detect().zacas() {
zacas_acquire_fn
} else {
fallback::atomic_compare_exchange_non_seqcst
}
})
}
Ordering::Release => {
ifunc!(unsafe fn(dst: *mut u128, old: u128, new: u128) -> (u128, bool) {
if detect::detect().zacas() {
zacas_release_fn
} else {
fallback::atomic_compare_exchange_non_seqcst
}
})
}
Ordering::AcqRel => {
ifunc!(unsafe fn(dst: *mut u128, old: u128, new: u128) -> (u128, bool) {
if detect::detect().zacas() {
zacas_acqrel_fn
} else {
fallback::atomic_compare_exchange_non_seqcst
}
})
}
Ordering::SeqCst => {
ifunc!(unsafe fn(dst: *mut u128, old: u128, new: u128) -> (u128, bool) {
if detect::detect().zacas() {
zacas_seqcst_fn
} else {
fallback::atomic_compare_exchange_seqcst
}
})
}
_ => unreachable!(),
}
}
};
if ok { Ok(prev) } else { Err(prev) }
}
#[inline]
unsafe fn atomic_compare_exchange_zacas(
dst: *mut u128,
old: u128,
new: u128,
success: Ordering,
failure: Ordering,
) -> (u128, bool) {
debug_assert!(dst as usize % 16 == 0);
debug_assert_zacas!();
let order = crate::utils::upgrade_success_ordering(success, failure);
let old = U128 { whole: old };
let new = U128 { whole: new };
let (prev_lo, prev_hi);
unsafe {
#[cfg(not(portable_atomic_pre_llvm_20))]
macro_rules! cmpxchg {
($fence:tt, $asm_order:tt) => {
asm!(
start_zacas!(),
$fence, concat!("amocas.q", $asm_order, " a4, a2, 0({dst})"), end_zacas!(),
dst = in(reg) ptr_reg!(dst),
inout("a4") old.pair.lo => prev_lo,
inout("a5") old.pair.hi => prev_hi,
in("a2") new.pair.lo,
in("a3") new.pair.hi,
options(nostack, preserves_flags),
)
};
}
#[cfg(not(portable_atomic_pre_llvm_20))]
atomic_rmw_amocas_order!(cmpxchg, order, failure = failure);
#[cfg(portable_atomic_pre_llvm_20)]
macro_rules! cmpxchg {
($fence:tt, $insn_order:tt) => {
asm!(
$fence, concat!(".4byte 0x2", $insn_order, "c5472f"),
in("a0") ptr_reg!(dst),
inout("a4") old.pair.lo => prev_lo,
inout("a5") old.pair.hi => prev_hi,
in("a2") new.pair.lo,
in("a3") new.pair.hi,
options(nostack, preserves_flags),
)
};
}
#[cfg(portable_atomic_pre_llvm_20)]
atomic_rmw_amocas_order_insn!(cmpxchg, order, failure = failure);
let prev = U128 { pair: Pair { lo: prev_lo, hi: prev_hi } }.whole;
(prev, prev == old.whole)
}
}
use self::atomic_compare_exchange as atomic_compare_exchange_weak;
#[inline]
unsafe fn byte_wise_atomic_load(src: *const u128) -> u128 {
let (out_lo, out_hi);
unsafe {
asm!(
"ld {out_lo}, ({src})", "ld {out_hi}, 8({src})", src = in(reg) ptr_reg!(src),
out_lo = out(reg) out_lo,
out_hi = out(reg) out_hi,
options(pure, nostack, preserves_flags, readonly),
);
U128 { pair: Pair { lo: out_lo, hi: out_hi } }.whole
}
}
macro_rules! select_atomic_rmw {
(
unsafe fn $name:ident(dst: *mut u128 $(, $($arg:tt)*)?) $(-> $ret_ty:ty)? {
|$zacas_fn_binding:ident| $($zacas_fn_body:tt)*
}
zacas = $zacas_fn:ident;
non_seqcst_fallback = $non_seqcst_fallback_fn:ident;
seqcst_fallback = $seqcst_fallback_fn:ident;
) => {
#[inline]
unsafe fn $zacas_fn(dst: *mut u128 $(, $($arg)*)?, order: Ordering) $(-> $ret_ty)? {
unsafe {
let mut prev = byte_wise_atomic_load(dst);
loop {
let next = {
let $zacas_fn_binding = prev;
$($zacas_fn_body)*
};
match atomic_compare_exchange_weak(dst, prev, next, order, Ordering::Relaxed) {
Ok(x) => return x,
Err(x) => prev = x,
}
}
}
}
#[cfg(any(target_feature = "zacas", portable_atomic_target_feature = "zacas"))]
use self::$zacas_fn as $name;
#[cfg(not(any(target_feature = "zacas", portable_atomic_target_feature = "zacas")))]
#[inline]
unsafe fn $name(dst: *mut u128 $(, $($arg)*)?, order: Ordering) $(-> $ret_ty)? {
fn_alias! {
#[inline(never)]
unsafe fn(dst: *mut u128 $(, $($arg)*)?) $(-> $ret_ty)?;
zacas_relaxed_fn = $zacas_fn(Ordering::Relaxed);
zacas_acquire_fn = $zacas_fn(Ordering::Acquire);
zacas_release_fn = $zacas_fn(Ordering::Release);
zacas_acqrel_fn = $zacas_fn(Ordering::AcqRel);
zacas_seqcst_fn = $zacas_fn(Ordering::SeqCst);
}
unsafe {
match order {
Ordering::Relaxed => {
ifunc!(unsafe fn(dst: *mut u128 $(, $($arg)*)?) $(-> $ret_ty)? {
if detect::detect().zacas() {
zacas_relaxed_fn
} else {
fallback::$non_seqcst_fallback_fn
}
})
}
Ordering::Acquire => {
ifunc!(unsafe fn(dst: *mut u128 $(, $($arg)*)?) $(-> $ret_ty)? {
if detect::detect().zacas() {
zacas_acquire_fn
} else {
fallback::$non_seqcst_fallback_fn
}
})
}
Ordering::Release => {
ifunc!(unsafe fn(dst: *mut u128 $(, $($arg)*)?) $(-> $ret_ty)? {
if detect::detect().zacas() {
zacas_release_fn
} else {
fallback::$non_seqcst_fallback_fn
}
})
}
Ordering::AcqRel => {
ifunc!(unsafe fn(dst: *mut u128 $(, $($arg)*)?) $(-> $ret_ty)? {
if detect::detect().zacas() {
zacas_acqrel_fn
} else {
fallback::$non_seqcst_fallback_fn
}
})
}
Ordering::SeqCst => {
ifunc!(unsafe fn(dst: *mut u128 $(, $($arg)*)?) $(-> $ret_ty)? {
if detect::detect().zacas() {
zacas_seqcst_fn
} else {
fallback::$seqcst_fallback_fn
}
})
}
_ => unreachable!(),
}
}
}
};
}
select_atomic_rmw! {
unsafe fn atomic_swap(dst: *mut u128, val: u128) -> u128 {
|_x| val
}
zacas = atomic_swap_zacas;
non_seqcst_fallback = atomic_swap_non_seqcst;
seqcst_fallback = atomic_swap_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_add(dst: *mut u128, val: u128) -> u128 {
|x| x.wrapping_add(val)
}
zacas = atomic_add_zacas;
non_seqcst_fallback = atomic_add_non_seqcst;
seqcst_fallback = atomic_add_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_sub(dst: *mut u128, val: u128) -> u128 {
|x| x.wrapping_sub(val)
}
zacas = atomic_sub_zacas;
non_seqcst_fallback = atomic_sub_non_seqcst;
seqcst_fallback = atomic_sub_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_and(dst: *mut u128, val: u128) -> u128 {
|x| x & val
}
zacas = atomic_and_zacas;
non_seqcst_fallback = atomic_and_non_seqcst;
seqcst_fallback = atomic_and_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_nand(dst: *mut u128, val: u128) -> u128 {
|x| !(x & val)
}
zacas = atomic_nand_zacas;
non_seqcst_fallback = atomic_nand_non_seqcst;
seqcst_fallback = atomic_nand_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_or(dst: *mut u128, val: u128) -> u128 {
|x| x | val
}
zacas = atomic_or_zacas;
non_seqcst_fallback = atomic_or_non_seqcst;
seqcst_fallback = atomic_or_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_xor(dst: *mut u128, val: u128) -> u128 {
|x| x ^ val
}
zacas = atomic_xor_zacas;
non_seqcst_fallback = atomic_xor_non_seqcst;
seqcst_fallback = atomic_xor_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_max(dst: *mut u128, val: u128) -> u128 {
|x| {
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
{ core::cmp::max(x as i128, val as i128) as u128 }
}
}
zacas = atomic_max_zacas;
non_seqcst_fallback = atomic_max_non_seqcst;
seqcst_fallback = atomic_max_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_umax(dst: *mut u128, val: u128) -> u128 {
|x| core::cmp::max(x, val)
}
zacas = atomic_umax_zacas;
non_seqcst_fallback = atomic_umax_non_seqcst;
seqcst_fallback = atomic_umax_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_min(dst: *mut u128, val: u128) -> u128 {
|x| {
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
{ core::cmp::min(x as i128, val as i128) as u128 }
}
}
zacas = atomic_min_zacas;
non_seqcst_fallback = atomic_min_non_seqcst;
seqcst_fallback = atomic_min_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_umin(dst: *mut u128, val: u128) -> u128 {
|x| core::cmp::min(x, val)
}
zacas = atomic_umin_zacas;
non_seqcst_fallback = atomic_umin_non_seqcst;
seqcst_fallback = atomic_umin_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_not(dst: *mut u128) -> u128 {
|x| !x
}
zacas = atomic_not_zacas;
non_seqcst_fallback = atomic_not_non_seqcst;
seqcst_fallback = atomic_not_seqcst;
}
select_atomic_rmw! {
unsafe fn atomic_neg(dst: *mut u128) -> u128 {
|x| x.wrapping_neg()
}
zacas = atomic_neg_zacas;
non_seqcst_fallback = atomic_neg_non_seqcst;
seqcst_fallback = atomic_neg_seqcst;
}
#[inline]
fn is_lock_free() -> bool {
#[cfg(any(target_feature = "zacas", portable_atomic_target_feature = "zacas"))]
{
true
}
#[cfg(not(any(target_feature = "zacas", portable_atomic_target_feature = "zacas")))]
{
detect::detect().zacas()
}
}
const IS_ALWAYS_LOCK_FREE: bool =
cfg!(any(target_feature = "zacas", portable_atomic_target_feature = "zacas"));
atomic128!(AtomicI128, i128, atomic_max, atomic_min);
atomic128!(AtomicU128, u128, atomic_umax, atomic_umin);
#[cfg(test)]
mod tests {
use super::*;
test_atomic_int!(i128);
test_atomic_int!(u128);
stress_test!(u128);
}