Skip to content

Commit 3dbee5b

Browse files
committed
Optimize AtomicBool for target that don't support byte-sized atomics
`AtomicBool` is defined to have the same layout as `bool`, which means that we guarantee that it has a size of 1 byte. However on certain architectures such as RISC-V, LLVM will emulate byte atomics using a masked CAS loop on an aligned word. We can take advantage of the fact that `bool` only ever has a value of 0 or 1 to replace `swap` operations with `and`/`or` operations that LLVM can lower to word-sized atomic `and`/`or` operations. This takes advantage of the fact that the incoming value to a `swap` or `compare_exchange` for `AtomicBool` is often a compile-time constant.
1 parent 8ca44ef commit 3dbee5b

File tree

1 file changed

+54
-8
lines changed

1 file changed

+54
-8
lines changed

library/core/src/sync/atomic.rs

+54-8
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,17 @@ use crate::intrinsics;
131131

132132
use crate::hint::spin_loop;
133133

134+
// Some architectures don't have byte-sized atomics, which results in LLVM
135+
// emulating them using a LL/SC loop. However for AtomicBool we can take
136+
// advantage of the fact that it only ever contains 0 or 1 and use atomic OR/AND
137+
// instead, which LLVM can emulate using a larger atomic OR/AND operation.
138+
//
139+
// This list should only contain architectures which have word-sized atomic-or/
140+
// atomic-and instructions but don't natively support byte-sized atomics.
141+
#[cfg(target_has_atomic = "8")]
142+
const EMULATE_ATOMIC_BOOL: bool =
143+
cfg!(any(target_arch = "riscv32", target_arch = "riscv64", target_arch = "loongarch64"));
144+
134145
/// A boolean type which can be safely shared between threads.
135146
///
136147
/// This type has the same in-memory representation as a [`bool`].
@@ -553,8 +564,12 @@ impl AtomicBool {
553564
#[cfg(target_has_atomic = "8")]
554565
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
555566
pub fn swap(&self, val: bool, order: Ordering) -> bool {
556-
// SAFETY: data races are prevented by atomic intrinsics.
557-
unsafe { atomic_swap(self.v.get(), val as u8, order) != 0 }
567+
if EMULATE_ATOMIC_BOOL {
568+
if val { self.fetch_or(true, order) } else { self.fetch_and(false, order) }
569+
} else {
570+
// SAFETY: data races are prevented by atomic intrinsics.
571+
unsafe { atomic_swap(self.v.get(), val as u8, order) != 0 }
572+
}
558573
}
559574

560575
/// Stores a value into the [`bool`] if the current value is the same as the `current` value.
@@ -664,12 +679,39 @@ impl AtomicBool {
664679
success: Ordering,
665680
failure: Ordering,
666681
) -> Result<bool, bool> {
667-
// SAFETY: data races are prevented by atomic intrinsics.
668-
match unsafe {
669-
atomic_compare_exchange(self.v.get(), current as u8, new as u8, success, failure)
670-
} {
671-
Ok(x) => Ok(x != 0),
672-
Err(x) => Err(x != 0),
682+
if EMULATE_ATOMIC_BOOL {
683+
// Pick the strongest ordering from success and failure.
684+
let order = match (success, failure) {
685+
(SeqCst, _) => SeqCst,
686+
(_, SeqCst) => SeqCst,
687+
(AcqRel, _) => AcqRel,
688+
(_, AcqRel) => {
689+
panic!("there is no such thing as an acquire-release failure ordering")
690+
}
691+
(Release, Acquire) => AcqRel,
692+
(Acquire, _) => Acquire,
693+
(_, Acquire) => Acquire,
694+
(Release, Relaxed) => Release,
695+
(_, Release) => panic!("there is no such thing as a release failure ordering"),
696+
(Relaxed, Relaxed) => Relaxed,
697+
};
698+
let old = if current == new {
699+
// This is a no-op, but we still need to perform the operation
700+
// for memory ordering reasons.
701+
self.fetch_or(false, order)
702+
} else {
703+
// This sets the value to the new one and returns the old one.
704+
self.swap(new, order)
705+
};
706+
if old == current { Ok(old) } else { Err(old) }
707+
} else {
708+
// SAFETY: data races are prevented by atomic intrinsics.
709+
match unsafe {
710+
atomic_compare_exchange(self.v.get(), current as u8, new as u8, success, failure)
711+
} {
712+
Ok(x) => Ok(x != 0),
713+
Err(x) => Err(x != 0),
714+
}
673715
}
674716
}
675717

@@ -719,6 +761,10 @@ impl AtomicBool {
719761
success: Ordering,
720762
failure: Ordering,
721763
) -> Result<bool, bool> {
764+
if EMULATE_ATOMIC_BOOL {
765+
return self.compare_exchange(current, new, success, failure);
766+
}
767+
722768
// SAFETY: data races are prevented by atomic intrinsics.
723769
match unsafe {
724770
atomic_compare_exchange_weak(self.v.get(), current as u8, new as u8, success, failure)

0 commit comments

Comments
 (0)