Skip to main content

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);