1use core::ops::RangeInclusive;
4
5use arch::{discover_rate, read};
6use t2t_core::{TickRate, Tickstamp};
7
8use crate::{Clock, CounterError};
9
10const PLAUSIBLE_RATES: RangeInclusive<u64> = 1_000_000..=10_000_000_000;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub struct Counter {
42 rate: TickRate,
44}
45
46impl Counter {
47 pub fn discover() -> Result<Self, CounterError> {
55 discover_rate().map(Self::new)
56 }
57
58 #[inline]
60 #[must_use]
61 pub const fn new(rate: TickRate) -> Self {
62 Self { rate }
63 }
64
65 #[inline]
67 #[must_use]
68 pub const fn rate(self) -> TickRate {
69 self.rate
70 }
71}
72
73impl Clock for Counter {
74 type Reading = Tickstamp;
75
76 #[inline(always)]
77 #[expect(clippy::inline_always, reason = "a call around the read would move it")]
78 fn now(&self) -> Tickstamp {
79 Tickstamp::from_ticks(read())
80 }
81}
82
83fn plausible_rate(hertz: u64) -> Result<TickRate, CounterError> {
88 TickRate::from_hertz(hertz)
89 .filter(|_| PLAUSIBLE_RATES.contains(&hertz))
90 .ok_or(CounterError::ImplausibleRate { hertz })
91}
92
93#[cfg(target_arch = "aarch64")]
98mod arch {
99 use core::arch::asm;
100
101 use t2t_core::TickRate;
102
103 use super::plausible_rate;
104 use crate::CounterError;
105
106 #[inline(always)]
108 #[expect(clippy::inline_always, reason = "a call around the read would move it")]
109 pub(super) fn read() -> i64 {
110 let ticks: u64;
111 #[expect(unsafe_code, reason = "a system register is read only through inline assembly")]
114 unsafe {
115 asm!(
116 "mrs {ticks}, cntvct_el0",
117 ticks = out(reg) ticks,
118 options(nomem, nostack, preserves_flags),
119 );
120 }
121 ticks.cast_signed()
122 }
123
124 pub(super) fn discover_rate() -> Result<TickRate, CounterError> {
129 let hertz: u64;
130 #[expect(unsafe_code, reason = "a system register is read only through inline assembly")]
133 unsafe {
134 asm!(
135 "mrs {hertz}, cntfrq_el0",
136 hertz = out(reg) hertz,
137 options(nomem, nostack, preserves_flags),
138 );
139 }
140 plausible_rate(hertz)
141 }
142}
143
144#[cfg(target_arch = "x86_64")]
146mod arch {
147 use core::arch::x86_64::_rdtsc;
148 #[cfg(os_clocks)]
149 use core::hint;
150
151 use raw_cpuid::CpuId;
152 use t2t_core::TickRate;
153 #[cfg(os_clocks)]
154 use t2t_core::Timedelta;
155
156 use super::{plausible_rate, tsc};
157 use crate::CounterError;
158 #[cfg(os_clocks)]
159 use crate::{Clock, RawMonotonicClock};
160
161 #[inline(always)]
163 #[expect(clippy::inline_always, reason = "a call around the read would move it")]
164 pub(super) fn read() -> i64 {
165 #[expect(unsafe_code, reason = "`_rdtsc` is an `unsafe` intrinsic")]
168 let ticks = unsafe { _rdtsc() };
169 ticks.cast_signed()
170 }
171
172 pub(super) fn discover_rate() -> Result<TickRate, CounterError> {
177 plausible_rate(tsc::hertz(&CpuId::new(), measure)?)
178 }
179
180 #[cfg(os_clocks)]
182 fn measure() -> Option<u64> {
183 const WINDOW: Timedelta = Timedelta::from_millis(10);
185
186 let start_ticks = read();
187 let start = RawMonotonicClock.now();
188 let mut span = Timedelta::ZERO;
189 while span < WINDOW {
190 hint::spin_loop();
191 span = RawMonotonicClock.now() - start;
192 }
193 let ticks = i128::from(read().saturating_sub(start_ticks));
194 let second = i128::from(Timedelta::SECOND.as_nanos());
195 let hertz = ticks.checked_mul(second)?.checked_div(i128::from(span.as_nanos()))?;
196 u64::try_from(hertz).ok()
197 }
198
199 #[cfg(not(os_clocks))]
201 const fn measure() -> Option<u64> {
202 None
203 }
204}
205
206#[cfg(any(target_arch = "x86_64", test))]
208mod tsc {
209 use raw_cpuid::{CpuId, CpuIdReader};
210
211 use crate::CounterError;
212
213 const HERTZ_PER_KILOHERTZ: u64 = 1_000;
215
216 pub(super) fn hertz<R, M>(cpuid: &CpuId<R>, measure: M) -> Result<u64, CounterError>
225 where
226 R: CpuIdReader,
227 M: FnOnce() -> Option<u64>,
228 {
229 let is_invariant =
230 cpuid.get_advanced_power_mgmt_info().is_some_and(|power| power.has_invariant_tsc());
231 if !is_invariant {
232 return Err(CounterError::NotInvariant);
233 }
234 let hypervisor_rate = || {
235 let kilohertz = cpuid.get_hypervisor_info()?.tsc_frequency()?;
236 u64::from(kilohertz).checked_mul(HERTZ_PER_KILOHERTZ)
237 };
238 cpuid
239 .get_tsc_info()
240 .and_then(|leaf| leaf.tsc_frequency())
241 .or_else(hypervisor_rate)
242 .filter(|&hertz| hertz > 0)
243 .or_else(measure)
244 .ok_or(CounterError::NoRate)
245 }
246
247 #[cfg(test)]
248 mod tests {
249 use raw_cpuid::{CpuId, CpuIdReader, CpuIdResult};
250
251 use super::hertz;
252 use crate::CounterError;
253
254 const INVARIANT: [(u32, [u32; 4]); 3] = [
257 (0, [0x15, 0, 0, 0]),
258 (0x8000_0000, [0x8000_0007, 0, 0, 0]),
259 (0x8000_0007, [0, 0, 0, 1 << 8]),
260 ];
261
262 const RATE_LEAF: (u32, [u32; 4]) = (0x15, [2, 176, 38_400_000, 0]);
264
265 fn cpu<const COUNT: usize>(leaves: [(u32, [u32; 4]); COUNT]) -> CpuId<impl CpuIdReader> {
268 CpuId::with_cpuid_reader(move |leaf, _subleaf| {
269 let answer = leaves.iter().find(|&&(number, _)| number == leaf);
270 let [eax, ebx, ecx, edx] = answer.map_or([0; 4], |&(_, registers)| registers);
271 CpuIdResult { eax, ebx, ecx, edx }
272 })
273 }
274
275 const fn no_measurement() -> Option<u64> {
277 None
278 }
279
280 #[test]
281 fn an_invariant_counter_runs_at_the_rate_its_leaf_reports() {
282 let [highest, extended, power] = INVARIANT;
283 let cpuid = cpu([highest, extended, power, RATE_LEAF]);
284 assert_eq!(hertz(&cpuid, no_measurement), Ok(3_379_200_000));
285 }
286
287 #[test]
288 fn a_counter_that_is_not_invariant_is_refused() {
289 let [highest, extended, _] = INVARIANT;
290 let variable_rate = cpu([highest, extended, RATE_LEAF]);
291 assert_eq!(hertz(&variable_rate, no_measurement), Err(CounterError::NotInvariant));
292
293 let [highest, _, power] = INVARIANT;
294 let old_cpu = cpu([highest, (0x8000_0000, [0x8000_0001, 0, 0, 0]), power, RATE_LEAF]);
295 assert_eq!(hertz(&old_cpu, no_measurement), Err(CounterError::NotInvariant));
296 }
297
298 #[test]
299 fn a_virtual_counter_runs_at_the_rate_its_hypervisor_reports() {
300 let [_, extended, power] = INVARIANT;
301 let cpuid = cpu([
302 (0, [0x0D, 0, 0, 0]),
303 (1, [0, 0, 1 << 31, 0]),
304 extended,
305 power,
306 (0x4000_0000, [0x4000_0010, 0, 0, 0]),
307 (0x4000_0010, [2_100_000, 0, 0, 0]),
308 ]);
309 assert_eq!(hertz(&cpuid, no_measurement), Ok(2_100_000_000));
310 }
311
312 #[test]
313 fn a_counter_whose_cpu_reports_no_rate_is_measured_or_refused() {
314 let silent_leaf = cpu(INVARIANT);
315 assert_eq!(hertz(&silent_leaf, || Some(2_100_000_000)), Ok(2_100_000_000));
316 assert_eq!(hertz(&silent_leaf, no_measurement), Err(CounterError::NoRate));
317
318 let [highest, extended, power] = INVARIANT;
319 let crystal_unknown = cpu([highest, extended, power, (0x15, [2, 168, 0, 0])]);
320 assert_eq!(hertz(&crystal_unknown, no_measurement), Err(CounterError::NoRate));
321 }
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use core::hint::black_box;
328
329 use t2t_core::{TickRate, Tickdelta, Timedelta};
330
331 use super::{PLAUSIBLE_RATES, plausible_rate};
332 use crate::{Clock, Counter, CounterError};
333
334 #[test]
335 fn a_rate_outside_the_plausible_range_is_refused() {
336 let refused = |hertz| Err(CounterError::ImplausibleRate { hertz });
337 assert_eq!(plausible_rate(0), refused(0), "a counter that never ticks");
338 assert_eq!(plausible_rate(999_999), refused(999_999), "one too slow to measure by");
339 }
340
341 #[test]
342 fn a_rate_a_counter_runs_at_is_taken() {
343 let apple_silicon = TickRate::from_hertz(24_000_000).expect("a nonzero rate");
344 assert_eq!(plausible_rate(24_000_000), Ok(apple_silicon), "Apple silicon's");
345 assert_eq!(plausible_rate(1_000_000_000), Ok(TickRate::GIGAHERTZ), "and Armv8.6's");
346 }
347
348 #[test]
349 #[cfg_attr(miri, ignore = "Miri cannot execute the counter's read")]
350 fn the_counter_never_runs_backwards() {
351 let counter = Counter::new(TickRate::GIGAHERTZ);
353 let start = counter.now();
354 for value in 0_u64..10_000 {
355 black_box(value);
356 }
357 let span = counter.now() - start;
358 assert!(span >= Tickdelta::ZERO, "the later reading is no earlier: {span}");
359 }
360
361 #[test]
362 #[cfg_attr(miri, ignore = "Miri cannot execute the counter's read")]
363 fn the_discovered_counter_runs_at_a_plausible_rate() {
364 let discovered = Counter::discover();
365 let refused_by_the_cpu = match discovered {
369 Err(CounterError::NotInvariant) => cfg!(target_arch = "x86_64"),
370 Err(CounterError::NoRate) => cfg!(all(target_arch = "x86_64", not(os_clocks))),
371 Ok(_) | Err(CounterError::ImplausibleRate { .. }) => false,
372 };
373 if refused_by_the_cpu {
374 return;
375 }
376 let counter = discovered.expect("a counter with a rate");
377 let rate = counter.rate();
378 assert!(PLAUSIBLE_RATES.contains(&rate.as_hertz()), "a plausible rate: {rate}");
379
380 let start = counter.now();
381 for value in 0_u64..10_000 {
382 black_box(value);
383 }
384 let took = (counter.now() - start).to_timedelta(rate);
385 assert!(took < Timedelta::from_millis(10), "ten thousand loops in under 10 ms: {took}");
386 }
387}