use core::arch::asm;
use core::arch::x86_64::{__cpuid, __cpuid_count, _rdtsc};
use core::time::Duration;
use crate::core::line::CompletionLine;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Flavor {
Umwait,
Mwaitx,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WordWait {
flavor: Flavor,
tsc_hz: u64,
}
const WAITPKG: u32 = 1 << 5;
const MONITORX: u32 = 1 << 29;
fn tsc() -> u64 {
#[allow(unused_unsafe)]
unsafe {
_rdtsc()
}
}
fn cpuid(leaf: u32, sub: u32) -> (u32, u32, u32, u32) {
#[allow(unused_unsafe)]
let r = unsafe { __cpuid_count(leaf, sub) };
(r.eax, r.ebx, r.ecx, r.edx)
}
fn tsc_hz() -> u64 {
#[allow(unused_unsafe)]
let max = unsafe { __cpuid(0) }.eax;
if max >= 0x15 {
let (den, num, crystal, _) = cpuid(0x15, 0);
if den != 0 && num != 0 && crystal != 0 {
return u64::from(crystal) * u64::from(num) / u64::from(den);
}
}
let t0 = std::time::Instant::now();
let c0 = tsc();
while t0.elapsed() < Duration::from_millis(2) {
core::hint::spin_loop();
}
let ns = u64::try_from(t0.elapsed().as_nanos()).unwrap_or(u64::MAX).max(1);
let ticks = tsc().wrapping_sub(c0);
u64::try_from(u128::from(ticks) * 1_000_000_000 / u128::from(ns)).unwrap_or(u64::MAX)
}
impl WordWait {
#[must_use]
pub fn flavor(&self) -> Flavor {
self.flavor
}
#[must_use]
pub fn tsc_hz(&self) -> u64 {
self.tsc_hz
}
pub(super) fn probe_arch() -> Result<Self, &'static str> {
let (max, _, _, _) = cpuid(0, 0);
let (max_ext, _, _, _) = cpuid(0x8000_0000, 0);
let waitpkg = max >= 7 && cpuid(7, 0).2 & WAITPKG != 0;
let flavor = if waitpkg {
Flavor::Umwait
} else {
let monitorx = max_ext >= 0x8000_0001 && cpuid(0x8000_0001, 0).2 & MONITORX != 0;
if !monitorx {
return Err("no user-mode monitor/wait: the CPU has neither WAITPKG nor MONITORX");
}
Flavor::Mwaitx
};
Ok(Self {
flavor,
tsc_hz: tsc_hz(),
})
}
pub(super) fn park_arch(
&self,
line: &CompletionLine,
ready: impl Fn(&CompletionLine) -> bool,
bound: Duration,
) -> bool {
let ticks = u64::try_from(
bound.as_nanos().saturating_mul(u128::from(self.tsc_hz)) / 1_000_000_000,
)
.unwrap_or(u64::MAX);
let addr = line.words[0].as_ptr();
match self.flavor {
Flavor::Umwait => {
unsafe { asm!("umonitor {a}", a = in(reg) addr, options(nostack, preserves_flags)) };
if !ready(line) {
let deadline = tsc().saturating_add(ticks);
#[expect(clippy::cast_possible_truncation, reason = "EDX:EAX split of a u64")]
unsafe {
asm!(
"umwait {c:e}",
c = in(reg) 0u32,
in("edx") (deadline >> 32) as u32,
in("eax") deadline as u32,
options(nostack, nomem),
)
};
}
}
Flavor::Mwaitx => {
unsafe {
asm!("monitorx", in("rax") addr, in("ecx") 0u32, in("edx") 0u32,
options(nostack, preserves_flags))
};
if !ready(line) {
let timeout = u32::try_from(ticks).unwrap_or(u32::MAX);
unsafe {
asm!(
"xchg {t:r}, rbx",
"mwaitx",
"xchg {t:r}, rbx",
t = inout(reg) u64::from(timeout) => _,
in("eax") 0xF0u32,
in("ecx") 2u32,
options(nostack, nomem),
)
};
}
}
}
ready(line)
}
}