fast_clock/lib.rs
1//! Low-overhead timing without hiding the details from you.
2//!
3//! # Core abstractions
4//!
5//! - [`Time`]: Defines the [`Instant`](Time::Instant) and [`Duration`](Time::Duration) types
6//! for a clock domain and the arithmetic between them.
7//! - [`Clock`]: Provides [`Clock::now`] and names the associated [`Time`] and
8//! [`DurationCalibration`] types.
9//! - [`DurationCalibration`]: Converts durations to/from nanoseconds as `u64`.
10//! - [`CalibratedClock`]: Bundles a [`Clock`] with its calibration for convenient passing.
11//! - [`ClockSynchronization`]: Correlates instants between two clock domains.
12//!
13//! # Standard library clocks
14//!
15//! [`std_clocks::InstantClock`] and [`std_clocks::SystemClock`] wrap
16//! [`std::time::Instant`] and [`std::time::SystemTime`]. They use [`InherentlyCalibrated`],
17//! as their duration type is directly convertible to/from nanoseconds.
18//!
19//! # Hardware clocks
20//!
21//! These are gated on their target architecture, so only the matching one is present.
22//!
23//! [`tsc::Tsc`] reads the x86_64 timestamp counter. Its ticks are not nanoseconds,
24//! so calibration via [`wrapping_u64::U64Calibration`] is required. Calibration also
25//! produces a [`ClockSynchronization`] that can convert TSC instants to
26//! [`std::time::Instant`] and vice versa.
27//!
28//! [`generic_timer::GenericTimer`] is the aarch64 counterpart. It can be calibrated the same
29//! way, or without a measurement from the frequency the hardware reports.
30//!
31//! ```
32//! # #[cfg(all(feature = "x86_64-tsc", target_arch = "x86_64"))]
33//! # {
34//! # use fast_clock::{Clock, DurationCalibration, CalibratedClock, InherentlyCalibrated};
35//! # use fast_clock::tsc::Tsc;
36//! # use fast_clock::wrapping_u64::{U64Calibration, WrappingU64Time};
37//! # use fast_clock::Time;
38//!
39//! let tsc = Tsc::try_new_assume_stable().unwrap();
40//! let (calibration, sync) = U64Calibration::new_with_std_instant(
41//! &tsc,
42//! std::time::Duration::from_millis(100),
43//! );
44//! let clock = CalibratedClock { clock: tsc, calibration };
45//!
46//! let t0 = clock.clock.now();
47//! // ... timed section ...
48//! let t1 = clock.clock.now();
49//!
50//! let duration = WrappingU64Time::instant_sub(t1, t0);
51//! let duration_ns: u64 = clock.calibration.convert_to_ns(duration);
52//!
53//! // Convert a TSC instant to std::time::Instant using the synchronization point.
54//! let std_instant = sync.to_a(t0, &InherentlyCalibrated, &clock.calibration);
55//! # }
56//! ```
57//!
58//! # Fast clock
59//!
60//! [`FastClock`] is a type alias for a fast default clock for the target architecture.
61//! This allows code using the clock to be portable.
62//! Note that the associated types of the clock may vary between architectures.
63//!
64//! It is currently implemented only for `x86_64` and `aarch64`.
65//! Both have very similar initialization options and are zero sized.
66//! On aarch64 the `fast-clock-aarch64-56bit` feature selects [`GenericTimer<56>`](generic_timer::GenericTimer) instead of [`GenericTimer<64>`](generic_timer::GenericTimer).
67//! See [`generic_timer::GenericTimer`] for the tradeoffs involved.
68//!
69//! # Features
70//!
71//! | Feature | Default | Description |
72//! |---------|---------|-------------|
73//! | `std` | yes | Enables [`std_clocks`] and `std`-dependent methods. |
74//! | `x86_64-tsc` | yes | Enables [`tsc`] (x86_64 only). |
75//! | `aarch64-generic-timer` | yes | Enables [`generic_timer`] (aarch64 only). |
76//! | `fast-clock` | yes | Enables [`FastClock`]. Implies the hardware clock features above. |
77//! | `fast-clock-aarch64-56bit` | no | Makes [`FastClock`] use a 56 bit counter on aarch64. Implies `fast-clock`. |
78//!
79//! Contributions adding more clocks are welcome.
80
81#![no_std]
82
83#[cfg(feature = "std")]
84extern crate std;
85
86mod clock_synchronization;
87use core::cmp::{self};
88
89pub use clock_synchronization::ClockSynchronization;
90
91#[cfg(all(feature = "aarch64-generic-timer", any(target_arch = "aarch64", doc)))]
92pub mod generic_timer;
93#[cfg(feature = "std")]
94pub mod std_clocks;
95#[cfg(all(feature = "x86_64-tsc", any(target_arch = "x86_64", doc)))]
96pub mod tsc;
97pub mod wrapping_u64;
98
99/// Arithmetic types and operations for a clock domain.
100///
101/// Most users will use the provided implementations: [`std_clocks::InstantTime`],
102/// [`std_clocks::SystemTimeTime`], and [`wrapping_u64::WrappingU64Time`].
103pub trait Time {
104 type Instant: Copy;
105 type Duration: Copy;
106 /// Returns `a - b`. Behavior when `a < b` is unspecified: implementations may
107 /// panic or return a meaningless value. Use [`instant_cmp`](Self::instant_cmp) to
108 /// check ordering first if unsure.
109 fn instant_sub(a: Self::Instant, b: Self::Instant) -> Self::Duration;
110 /// Returns `a - b`. Behavior when `a < b` is unspecified: implementations may
111 /// panic or return a meaningless value.
112 fn duration_sub(a: Self::Duration, b: Self::Duration) -> Self::Duration;
113 fn duration_add(a: Self::Duration, b: Self::Duration) -> Self::Duration;
114 fn mixed_sub(a: Self::Instant, b: Self::Duration) -> Self::Instant;
115 fn mixed_add(a: Self::Instant, b: Self::Duration) -> Self::Instant;
116 fn instant_cmp(a: Self::Instant, b: Self::Instant) -> cmp::Ordering;
117}
118
119/// A source of time readings.
120///
121/// Call [`Clock::now`] to sample an instant.
122pub trait Clock {
123 type Time: Time;
124 type Calibration: DurationCalibration<<Self::Time as Time>::Duration>;
125 /// Returns the current instant.
126 fn now(&self) -> <Self::Time as Time>::Instant;
127}
128
129/// Converts a clock's native duration type to and from nanoseconds.
130pub trait DurationCalibration<D> {
131 /// Converts a duration to nanoseconds, rounding to the nearest nanosecond.
132 fn convert_to_ns(&self, d: D) -> u64;
133 /// Converts a nanosecond value to this clock's native duration type.
134 fn convert_from_ns(&self, ns: u64) -> D;
135 #[cfg(feature = "std")]
136 fn to_std(&self, d: D) -> std::time::Duration
137 where
138 Self: Sized,
139 {
140 std::time::Duration::from_nanos(self.convert_to_ns(d))
141 }
142 #[cfg(feature = "std")]
143 fn convert_from_std(&self, d: std::time::Duration) -> D
144 where
145 Self: Sized,
146 {
147 self.convert_from_ns(InherentlyCalibrated.convert_to_ns(d))
148 }
149}
150
151/// A [`Clock`] bundled with its [`DurationCalibration`].
152#[derive(Clone, Copy, Debug)]
153pub struct CalibratedClock<C: Clock> {
154 pub clock: C,
155 pub calibration: C::Calibration,
156}
157
158impl<C: Clock<Calibration = InherentlyCalibrated>> CalibratedClock<C> {
159 /// Construct a [`CalibratedClock`] for a [`Clock`] that is inherently calibrated.
160 pub fn inherent(clock: C) -> Self {
161 CalibratedClock {
162 clock,
163 calibration: InherentlyCalibrated,
164 }
165 }
166}
167
168/// Calibration type for clocks whose native duration is already in nanoseconds.
169///
170/// Used with [`std_clocks::InstantClock`] and [`std_clocks::SystemClock`].
171#[derive(Clone, Copy, Debug)]
172pub struct InherentlyCalibrated;
173
174#[allow(
175 unused_macros,
176 reason = "no arm matches on targets without a hardware clock"
177)]
178macro_rules! declare_fast_clock {
179 ($T:ty) => {
180 /// A type alias for a fast default clock for the target architecture.
181 ///
182 /// This allows code using the clock to be portable.
183 /// Note that the associated types of the clock may vary between architectures.
184 /// See [`FastClockCalibration`] and [`FastClockTime`].
185 /// It is architecture dependent whether the clock is the same across all threads.
186 ///
187 /// It is currently implemented only for `x86_64` ([`Tsc`](tsc::Tsc)) and `aarch64` ([`GenericTimer`](generic_timer::GenericTimer)).
188 /// Both have very similar initialization options and are zero sized.
189 /// On `aarch64` the `fast-clock-aarch64-56bit` feature selects [`GenericTimer<56>`](generic_timer::GenericTimer) instead of [`GenericTimer<64>`](generic_timer::GenericTimer).
190 /// See [`GenericTimer`](generic_timer::GenericTimer) for the tradeoffs involved.
191 /// On other architectures, the type alias is absent.
192 pub type FastClock = $T;
193 /// The [`DurationCalibration`] of [`FastClock`].
194 pub type FastClockCalibration = <FastClock as Clock>::Calibration;
195 /// The [`Time`] of [`FastClock`].
196 pub type FastClockTime = <FastClock as Clock>::Time;
197 };
198}
199
200#[cfg(all(
201 feature = "fast-clock",
202 target_arch = "aarch64",
203 not(feature = "fast-clock-aarch64-56bit")
204))]
205declare_fast_clock!(generic_timer::GenericTimer<64>);
206
207#[cfg(all(
208 feature = "fast-clock",
209 target_arch = "aarch64",
210 feature = "fast-clock-aarch64-56bit"
211))]
212declare_fast_clock!(generic_timer::GenericTimer<56>);
213
214#[cfg(all(feature = "fast-clock", target_arch = "x86_64"))]
215declare_fast_clock!(tsc::Tsc);