Skip to main content

limen_core/platform/
linux.rs

1//! Platform impls for linux systems.
2
3use crate::platform::PlatformClock;
4use crate::types::Ticks;
5
6/// Number of nanoseconds in one second.
7const NANOSECONDS_PER_SECOND: u128 = 1_000_000_000;
8
9/// Linux monotonic clock implementation backed by `clock_gettime(CLOCK_MONOTONIC)`.
10#[derive(Debug, Default, Clone, Copy)]
11pub struct NoStdLinuxMonotonicClock;
12
13impl NoStdLinuxMonotonicClock {
14    /// Create a new `NoStdLinuxMonotonicClock` instance.
15    #[inline]
16    pub const fn new() -> NoStdLinuxMonotonicClock {
17        NoStdLinuxMonotonicClock
18    }
19
20    /// Read the current monotonic time in nanoseconds since an unspecified epoch.
21    ///
22    /// This is a best-effort implementation. On any detected error or invalid
23    /// `timespec` values, it returns `0` rather than invoking undefined behavior.
24    #[allow(unsafe_code)]
25    #[inline]
26    fn read_monotonic_nanoseconds() -> u64 {
27        // Initialize to a known state so we do not need MaybeUninit or assume_init.
28        let mut timespec_value = libc::timespec {
29            tv_sec: 0,
30            tv_nsec: 0,
31        };
32
33        // The only unsafe in this module: the FFI call itself.
34        let return_code =
35            unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut timespec_value) };
36
37        // On error, fall back to 0. The trait does not allow us to return a Result.
38        if return_code != 0 {
39            return 0;
40        }
41
42        // Defensive validation of the returned timespec.
43        if timespec_value.tv_sec < 0 as libc::time_t {
44            return 0;
45        }
46
47        if timespec_value.tv_nsec < 0 as libc::c_long
48            || timespec_value.tv_nsec >= 1_000_000_000 as libc::c_long
49        {
50            return 0;
51        }
52
53        let seconds_as_u64 = timespec_value.tv_sec as u64;
54        let nanoseconds_as_u64 = timespec_value.tv_nsec as u64;
55
56        // Compute total nanoseconds in u128, then saturate down to u64.
57        let total_nanoseconds_as_u128 = (seconds_as_u64 as u128)
58            .saturating_mul(NANOSECONDS_PER_SECOND)
59            .saturating_add(nanoseconds_as_u64 as u128);
60
61        if total_nanoseconds_as_u128 > u64::MAX as u128 {
62            u64::MAX
63        } else {
64            total_nanoseconds_as_u128 as u64
65        }
66    }
67}
68
69impl PlatformClock for NoStdLinuxMonotonicClock {
70    #[inline]
71    fn now_ticks(&self) -> Ticks {
72        // Define ticks as nanoseconds of CLOCK_MONOTONIC.
73        Ticks::new(Self::read_monotonic_nanoseconds())
74    }
75
76    #[inline]
77    fn ticks_to_nanos(&self, ticks: Ticks) -> u64 {
78        // Identity mapping: ticks are nanoseconds.
79        *ticks.as_u64()
80    }
81
82    #[inline]
83    fn nanos_to_ticks(&self, nanoseconds: u64) -> Ticks {
84        // Identity mapping: ticks are nanoseconds.
85        Ticks::new(nanoseconds)
86    }
87}
88
89/// Monotonic clock backed by `std::time::Instant`.
90///
91/// Ticks are defined as nanoseconds elapsed since the clock was constructed.
92/// This is strictly monotonic (subject to `Instant`'s guarantees).
93#[cfg(feature = "std")]
94#[derive(Debug, Clone)]
95pub struct StdLinuxMonotonicClock {
96    origin: std::time::Instant,
97}
98
99#[cfg(feature = "std")]
100impl StdLinuxMonotonicClock {
101    /// Create a new `StdLinuxMonotonicClock` with the current instant as origin.
102    #[inline]
103    pub fn new() -> StdLinuxMonotonicClock {
104        StdLinuxMonotonicClock {
105            origin: std::time::Instant::now(),
106        }
107    }
108}
109
110#[cfg(feature = "std")]
111impl Default for StdLinuxMonotonicClock {
112    #[inline]
113    fn default() -> StdLinuxMonotonicClock {
114        StdLinuxMonotonicClock::new()
115    }
116}
117
118#[cfg(feature = "std")]
119impl PlatformClock for StdLinuxMonotonicClock {
120    #[inline]
121    fn now_ticks(&self) -> Ticks {
122        let duration_since_origin = std::time::Instant::now().duration_since(self.origin);
123
124        // Duration::as_nanos returns u128.
125        let total_nanoseconds_as_u128 = duration_since_origin.as_nanos();
126
127        // Saturate down to u64 if it ever exceeds the range.
128        if total_nanoseconds_as_u128 > u64::MAX as u128 {
129            Ticks::new(u64::MAX)
130        } else {
131            Ticks::new(total_nanoseconds_as_u128 as u64)
132        }
133    }
134
135    #[inline]
136    fn ticks_to_nanos(&self, ticks: Ticks) -> u64 {
137        // Identity mapping: ticks are nanoseconds.
138        *ticks.as_u64()
139    }
140
141    #[inline]
142    fn nanos_to_ticks(&self, nanoseconds: u64) -> Ticks {
143        // Identity mapping: ticks are nanoseconds.
144        Ticks::new(nanoseconds)
145    }
146}