#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WaitStrategy {
BusySpin,
YieldSpin,
BackoffSpin,
Adaptive {
spin_iters: u32,
yield_iters: u32,
},
MonitorWait {
addr: *const u8,
},
MonitorWaitFallback,
}
impl WaitStrategy {
#[inline]
pub fn monitor_wait(stamp: &core::sync::atomic::AtomicU64) -> Self {
WaitStrategy::MonitorWait {
addr: stamp as *const core::sync::atomic::AtomicU64 as *const u8,
}
}
}
impl Default for WaitStrategy {
fn default() -> Self {
WaitStrategy::Adaptive {
spin_iters: 64,
yield_iters: 64,
}
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
#[inline]
fn has_waitpkg() -> bool {
#[cfg(target_arch = "x86_64")]
{
let result = core::arch::x86_64::__cpuid_count(7, 0);
result.ecx & (1 << 5) != 0
}
#[cfg(target_arch = "x86")]
{
let result = core::arch::x86::__cpuid_count(7, 0);
result.ecx & (1 << 5) != 0
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
static WAITPKG_SUPPORT: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
#[inline]
fn waitpkg_supported() -> bool {
let cached = WAITPKG_SUPPORT.load(core::sync::atomic::Ordering::Relaxed);
if cached != 0 {
return cached == 2;
}
let supported = has_waitpkg();
WAITPKG_SUPPORT.store(
if supported { 2 } else { 1 },
core::sync::atomic::Ordering::Relaxed,
);
supported
}
#[cfg(target_arch = "x86_64")]
mod umwait {
#[inline(always)]
fn deadline_100us() -> (u32, u32) {
let tsc = unsafe { core::arch::x86_64::_rdtsc() };
let deadline = tsc.wrapping_add(300_000); (deadline as u32, (deadline >> 32) as u32) }
#[inline(always)]
pub(super) unsafe fn umonitor(addr: *const u8) {
core::arch::asm!(
".byte 0xf3, 0x0f, 0xae, 0xf0", in("rax") addr,
options(nostack, preserves_flags),
);
}
#[inline(always)]
pub(super) unsafe fn umwait(ctrl: u32) {
let (lo, hi) = deadline_100us();
core::arch::asm!(
".byte 0xf2, 0x0f, 0xae, 0xf1", in("ecx") ctrl,
in("edx") hi,
in("eax") lo,
options(nostack, preserves_flags),
);
}
#[inline(always)]
pub(super) unsafe fn tpause(ctrl: u32) {
let (lo, hi) = deadline_100us();
core::arch::asm!(
".byte 0x66, 0x0f, 0xae, 0xf1", in("ecx") ctrl,
in("edx") hi,
in("eax") lo,
options(nostack, preserves_flags),
);
}
}
unsafe impl Send for WaitStrategy {}
unsafe impl Sync for WaitStrategy {}
impl WaitStrategy {
#[inline]
pub(crate) fn wait(&self, iter: u32) {
match self {
WaitStrategy::BusySpin => {
}
WaitStrategy::YieldSpin => {
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!("sevl", options(nomem, nostack));
core::arch::asm!("wfe", options(nomem, nostack));
}
#[cfg(not(target_arch = "aarch64"))]
core::hint::spin_loop();
}
WaitStrategy::BackoffSpin => {
let pauses = 1u32.wrapping_shl(iter.min(6)); for _ in 0..pauses {
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!("wfe", options(nomem, nostack));
}
#[cfg(not(target_arch = "aarch64"))]
core::hint::spin_loop();
}
}
WaitStrategy::Adaptive {
spin_iters,
yield_iters,
} => {
if iter < *spin_iters {
} else if iter < spin_iters + yield_iters {
core::hint::spin_loop();
} else {
for _ in 0..8 {
core::hint::spin_loop();
}
}
}
WaitStrategy::MonitorWait { addr } => {
#[cfg(target_arch = "x86_64")]
{
if waitpkg_supported() {
unsafe {
umwait::umonitor(*addr);
umwait::umwait(1); }
} else {
core::hint::spin_loop();
}
}
#[cfg(target_arch = "aarch64")]
{
let _ = addr;
unsafe {
core::arch::asm!("sevl", options(nomem, nostack));
core::arch::asm!("wfe", options(nomem, nostack));
}
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
let _ = addr;
core::hint::spin_loop();
}
}
WaitStrategy::MonitorWaitFallback => {
#[cfg(target_arch = "x86_64")]
{
if waitpkg_supported() {
unsafe {
umwait::tpause(1); }
} else {
core::hint::spin_loop();
}
}
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!("sevl", options(nomem, nostack));
core::arch::asm!("wfe", options(nomem, nostack));
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
core::hint::spin_loop();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_adaptive() {
let ws = WaitStrategy::default();
assert_eq!(
ws,
WaitStrategy::Adaptive {
spin_iters: 64,
yield_iters: 64,
}
);
}
#[test]
fn busy_spin_returns_immediately() {
let ws = WaitStrategy::BusySpin;
for i in 0..1000 {
ws.wait(i);
}
}
#[test]
fn yield_spin_returns() {
let ws = WaitStrategy::YieldSpin;
for i in 0..100 {
ws.wait(i);
}
}
#[test]
fn backoff_spin_returns() {
let ws = WaitStrategy::BackoffSpin;
for i in 0..20 {
ws.wait(i);
}
}
#[test]
fn adaptive_phases() {
let ws = WaitStrategy::Adaptive {
spin_iters: 4,
yield_iters: 4,
};
for i in 0..20 {
ws.wait(i);
}
}
#[test]
fn clone_and_copy() {
let ws = WaitStrategy::BusySpin;
let ws2 = ws;
#[allow(clippy::clone_on_copy)]
let ws3 = ws.clone();
assert_eq!(ws, ws2);
assert_eq!(ws, ws3);
}
#[test]
fn debug_format() {
use alloc::format;
let ws = WaitStrategy::BusySpin;
let s = format!("{ws:?}");
assert!(s.contains("BusySpin"));
}
}