use core::arch::asm;
use core::time::Duration;
use crate::core::line::CompletionLine;
const HWCAP2_WFXT: u64 = 1 << 31;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WordWait {
timed: bool,
counter_hz: u64,
}
fn counter() -> u64 {
let v: u64;
unsafe {
asm!("mrs {v}, cntvct_el0", v = out(reg) v, options(nomem, nostack, preserves_flags))
};
v
}
impl WordWait {
#[must_use]
pub fn timed(&self) -> bool {
self.timed
}
#[must_use]
pub fn counter_hz(&self) -> u64 {
self.counter_hz
}
pub(super) fn probe_arch() -> Result<Self, &'static str> {
let hz: u64;
unsafe {
asm!("mrs {v}, cntfrq_el0", v = out(reg) hz, options(nomem, nostack, preserves_flags))
};
let hwcap2 = unsafe { libc::getauxval(libc::AT_HWCAP2) };
Ok(Self {
timed: hwcap2 & HWCAP2_WFXT != 0,
counter_hz: 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.counter_hz)) / 1_000_000_000,
)
.unwrap_or(u64::MAX);
let deadline = counter().saturating_add(ticks);
let _armed: u64;
unsafe {
asm!("ldxr {v}, [{p}]", p = in(reg) line.words[0].as_ptr(), v = out(reg) _armed,
options(nostack, preserves_flags, readonly));
}
if !ready(line) {
if self.timed {
unsafe {
asm!(".inst 0xd5031000", in("x0") deadline, options(nomem, nostack, preserves_flags))
};
} else {
unsafe { asm!("wfe", options(nomem, nostack, preserves_flags)) };
}
}
ready(line)
}
}
#[cfg(test)]
mod tests {
use super::*;
extern crate std;
use core::sync::atomic::Ordering;
fn slot_is(slot: usize, v: u64) -> impl Fn(&CompletionLine) -> bool {
move |l| l.words[slot].load(Ordering::Acquire) == v
}
#[test]
fn a_slot_already_at_expect_returns_true_at_once() {
let w = WordWait::probe().expect("aarch64 Linux has a word wait");
assert!(w.counter_hz > 0);
let line = CompletionLine::default();
line.words[5].store(7, Ordering::Release);
let t = std::time::Instant::now();
assert!(w.park(&line, slot_is(5, 7), Duration::from_millis(50)));
assert!(
t.elapsed() < Duration::from_millis(5),
"must not park when the slot is set"
);
}
#[test]
fn a_slot_never_written_returns_false_within_the_bound() {
let w = WordWait::probe().expect("aarch64 Linux has a word wait");
let line = CompletionLine::default();
let t = std::time::Instant::now();
let mut fired = false;
while t.elapsed() < Duration::from_millis(2) {
fired |= w.park(&line, slot_is(0, 1), Duration::from_micros(100));
}
assert!(!fired, "an unwritten slot must never read as written");
}
#[test]
fn a_store_to_another_slot_of_the_line_wakes_the_park() {
let w = WordWait::probe().expect("aarch64 Linux has a word wait");
let line = std::sync::Arc::new(CompletionLine::default());
let writer = {
let line = std::sync::Arc::clone(&line);
std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(2));
line.words[6].store(5, Ordering::Release);
})
};
let t = std::time::Instant::now();
while !w.park(&line, slot_is(6, 5), Duration::from_millis(1)) {
assert!(
t.elapsed() < Duration::from_secs(2),
"the store never woke the park"
);
}
writer.join().expect("writer");
}
fn parks_until_a_store_in_10_ms(w: WordWait) -> u64 {
let line = std::sync::Arc::new(CompletionLine::default());
let writer = {
let line = std::sync::Arc::clone(&line);
std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(10));
line.words[0].store(1, Ordering::Release);
})
};
let mut parks = 0u64;
while !w.park(&line, slot_is(0, 1), Duration::from_micros(100)) {
parks += 1;
}
writer.join().expect("writer");
parks
}
#[test]
fn a_park_sleeps_until_the_store_instead_of_spinning() {
let probed = WordWait::probe().expect("aarch64 Linux has a word wait");
let plain = WordWait {
timed: false,
..probed
};
for w in [probed, plain, probed] {
let parks = parks_until_a_store_in_10_ms(w);
assert!(parks <= 5_000, "{parks} parks in ~10 ms (timed {}): the park spins", w.timed);
}
}
}