use core::ops::RangeInclusive;
use arch::{discover_rate, read};
use t2t_core::{TickRate, Tickstamp};
use crate::{Clock, CounterError};
const PLAUSIBLE_RATES: RangeInclusive<u64> = 1_000_000..=10_000_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Counter {
rate: TickRate,
}
impl Counter {
pub fn discover() -> Result<Self, CounterError> {
discover_rate().map(Self::new)
}
#[inline]
#[must_use]
pub const fn new(rate: TickRate) -> Self {
Self { rate }
}
#[inline]
#[must_use]
pub const fn rate(self) -> TickRate {
self.rate
}
}
impl Clock for Counter {
type Reading = Tickstamp;
#[inline(always)]
#[expect(clippy::inline_always, reason = "a call around the read would move it")]
fn now(&self) -> Tickstamp {
Tickstamp::from_ticks(read())
}
}
fn plausible_rate(hertz: u64) -> Result<TickRate, CounterError> {
TickRate::from_hertz(hertz)
.filter(|_| PLAUSIBLE_RATES.contains(&hertz))
.ok_or(CounterError::ImplausibleRate { hertz })
}
#[cfg(target_arch = "aarch64")]
mod arch {
use core::arch::asm;
use t2t_core::TickRate;
use super::plausible_rate;
use crate::CounterError;
#[inline(always)]
#[expect(clippy::inline_always, reason = "a call around the read would move it")]
pub(super) fn read() -> i64 {
let ticks: u64;
#[expect(unsafe_code, reason = "a system register is read only through inline assembly")]
unsafe {
asm!(
"mrs {ticks}, cntvct_el0",
ticks = out(reg) ticks,
options(nomem, nostack, preserves_flags),
);
}
ticks.cast_signed()
}
pub(super) fn discover_rate() -> Result<TickRate, CounterError> {
let hertz: u64;
#[expect(unsafe_code, reason = "a system register is read only through inline assembly")]
unsafe {
asm!(
"mrs {hertz}, cntfrq_el0",
hertz = out(reg) hertz,
options(nomem, nostack, preserves_flags),
);
}
plausible_rate(hertz)
}
}
#[cfg(target_arch = "x86_64")]
mod arch {
use core::arch::x86_64::_rdtsc;
#[cfg(os_clocks)]
use core::hint;
use raw_cpuid::CpuId;
use t2t_core::TickRate;
#[cfg(os_clocks)]
use t2t_core::Timedelta;
use super::{plausible_rate, tsc};
use crate::CounterError;
#[cfg(os_clocks)]
use crate::{Clock, RawMonotonicClock};
#[inline(always)]
#[expect(clippy::inline_always, reason = "a call around the read would move it")]
pub(super) fn read() -> i64 {
#[expect(unsafe_code, reason = "`_rdtsc` is an `unsafe` intrinsic")]
let ticks = unsafe { _rdtsc() };
ticks.cast_signed()
}
pub(super) fn discover_rate() -> Result<TickRate, CounterError> {
plausible_rate(tsc::hertz(&CpuId::new(), measure)?)
}
#[cfg(os_clocks)]
fn measure() -> Option<u64> {
const WINDOW: Timedelta = Timedelta::from_millis(10);
let start_ticks = read();
let start = RawMonotonicClock.now();
let mut span = Timedelta::ZERO;
while span < WINDOW {
hint::spin_loop();
span = RawMonotonicClock.now() - start;
}
let ticks = i128::from(read().saturating_sub(start_ticks));
let second = i128::from(Timedelta::SECOND.as_nanos());
let hertz = ticks.checked_mul(second)?.checked_div(i128::from(span.as_nanos()))?;
u64::try_from(hertz).ok()
}
#[cfg(not(os_clocks))]
const fn measure() -> Option<u64> {
None
}
}
#[cfg(any(target_arch = "x86_64", test))]
mod tsc {
use raw_cpuid::{CpuId, CpuIdReader};
use crate::CounterError;
const HERTZ_PER_KILOHERTZ: u64 = 1_000;
pub(super) fn hertz<R, M>(cpuid: &CpuId<R>, measure: M) -> Result<u64, CounterError>
where
R: CpuIdReader,
M: FnOnce() -> Option<u64>,
{
let is_invariant =
cpuid.get_advanced_power_mgmt_info().is_some_and(|power| power.has_invariant_tsc());
if !is_invariant {
return Err(CounterError::NotInvariant);
}
let hypervisor_rate = || {
let kilohertz = cpuid.get_hypervisor_info()?.tsc_frequency()?;
u64::from(kilohertz).checked_mul(HERTZ_PER_KILOHERTZ)
};
cpuid
.get_tsc_info()
.and_then(|leaf| leaf.tsc_frequency())
.or_else(hypervisor_rate)
.filter(|&hertz| hertz > 0)
.or_else(measure)
.ok_or(CounterError::NoRate)
}
#[cfg(test)]
mod tests {
use raw_cpuid::{CpuId, CpuIdReader, CpuIdResult};
use super::hertz;
use crate::CounterError;
const INVARIANT: [(u32, [u32; 4]); 3] = [
(0, [0x15, 0, 0, 0]),
(0x8000_0000, [0x8000_0007, 0, 0, 0]),
(0x8000_0007, [0, 0, 0, 1 << 8]),
];
const RATE_LEAF: (u32, [u32; 4]) = (0x15, [2, 176, 38_400_000, 0]);
fn cpu<const COUNT: usize>(leaves: [(u32, [u32; 4]); COUNT]) -> CpuId<impl CpuIdReader> {
CpuId::with_cpuid_reader(move |leaf, _subleaf| {
let answer = leaves.iter().find(|&&(number, _)| number == leaf);
let [eax, ebx, ecx, edx] = answer.map_or([0; 4], |&(_, registers)| registers);
CpuIdResult { eax, ebx, ecx, edx }
})
}
const fn no_measurement() -> Option<u64> {
None
}
#[test]
fn an_invariant_counter_runs_at_the_rate_its_leaf_reports() {
let [highest, extended, power] = INVARIANT;
let cpuid = cpu([highest, extended, power, RATE_LEAF]);
assert_eq!(hertz(&cpuid, no_measurement), Ok(3_379_200_000));
}
#[test]
fn a_counter_that_is_not_invariant_is_refused() {
let [highest, extended, _] = INVARIANT;
let variable_rate = cpu([highest, extended, RATE_LEAF]);
assert_eq!(hertz(&variable_rate, no_measurement), Err(CounterError::NotInvariant));
let [highest, _, power] = INVARIANT;
let old_cpu = cpu([highest, (0x8000_0000, [0x8000_0001, 0, 0, 0]), power, RATE_LEAF]);
assert_eq!(hertz(&old_cpu, no_measurement), Err(CounterError::NotInvariant));
}
#[test]
fn a_virtual_counter_runs_at_the_rate_its_hypervisor_reports() {
let [_, extended, power] = INVARIANT;
let cpuid = cpu([
(0, [0x0D, 0, 0, 0]),
(1, [0, 0, 1 << 31, 0]),
extended,
power,
(0x4000_0000, [0x4000_0010, 0, 0, 0]),
(0x4000_0010, [2_100_000, 0, 0, 0]),
]);
assert_eq!(hertz(&cpuid, no_measurement), Ok(2_100_000_000));
}
#[test]
fn a_counter_whose_cpu_reports_no_rate_is_measured_or_refused() {
let silent_leaf = cpu(INVARIANT);
assert_eq!(hertz(&silent_leaf, || Some(2_100_000_000)), Ok(2_100_000_000));
assert_eq!(hertz(&silent_leaf, no_measurement), Err(CounterError::NoRate));
let [highest, extended, power] = INVARIANT;
let crystal_unknown = cpu([highest, extended, power, (0x15, [2, 168, 0, 0])]);
assert_eq!(hertz(&crystal_unknown, no_measurement), Err(CounterError::NoRate));
}
}
}
#[cfg(test)]
mod tests {
use core::hint::black_box;
use t2t_core::{TickRate, Tickdelta, Timedelta};
use super::{PLAUSIBLE_RATES, plausible_rate};
use crate::{Clock, Counter, CounterError};
#[test]
fn a_rate_outside_the_plausible_range_is_refused() {
let refused = |hertz| Err(CounterError::ImplausibleRate { hertz });
assert_eq!(plausible_rate(0), refused(0), "a counter that never ticks");
assert_eq!(plausible_rate(999_999), refused(999_999), "one too slow to measure by");
}
#[test]
fn a_rate_a_counter_runs_at_is_taken() {
let apple_silicon = TickRate::from_hertz(24_000_000).expect("a nonzero rate");
assert_eq!(plausible_rate(24_000_000), Ok(apple_silicon), "Apple silicon's");
assert_eq!(plausible_rate(1_000_000_000), Ok(TickRate::GIGAHERTZ), "and Armv8.6's");
}
#[test]
#[cfg_attr(miri, ignore = "Miri cannot execute the counter's read")]
fn the_counter_never_runs_backwards() {
let counter = Counter::new(TickRate::GIGAHERTZ);
let start = counter.now();
for value in 0_u64..10_000 {
black_box(value);
}
let span = counter.now() - start;
assert!(span >= Tickdelta::ZERO, "the later reading is no earlier: {span}");
}
#[test]
#[cfg_attr(miri, ignore = "Miri cannot execute the counter's read")]
fn the_discovered_counter_runs_at_a_plausible_rate() {
let discovered = Counter::discover();
let refused_by_the_cpu = match discovered {
Err(CounterError::NotInvariant) => cfg!(target_arch = "x86_64"),
Err(CounterError::NoRate) => cfg!(all(target_arch = "x86_64", not(os_clocks))),
Ok(_) | Err(CounterError::ImplausibleRate { .. }) => false,
};
if refused_by_the_cpu {
return;
}
let counter = discovered.expect("a counter with a rate");
let rate = counter.rate();
assert!(PLAUSIBLE_RATES.contains(&rate.as_hertz()), "a plausible rate: {rate}");
let start = counter.now();
for value in 0_u64..10_000 {
black_box(value);
}
let took = (counter.now() - start).to_timedelta(rate);
assert!(took < Timedelta::from_millis(10), "ten thousand loops in under 10 ms: {took}");
}
}