Skip to main content

limen_core/
platform.rs

1//! Platform abstractions.
2//!
3//! ## Boundary: what enters `StepContext`
4//! Only a **monotonic clock** enters `StepContext`. Nodes may **observe** time,
5//! but **do not** control scheduling (sleep/yield) or placement (affinity) via the
6//! context. This preserves portability, `no_std` viability, determinism in tests,
7//! and keeps scheduling policy in the runtime.
8//!
9//! ## Runtime-level aides (do **not** enter `StepContext`)
10//! - [`Timers`]: sleeping/yielding belongs to the runtime/scheduler.
11//! - [`Affinity`]: core/NUMA placement is a runtime concern.
12//!
13//! Implementations of these traits are provided by `limen-platform` or host runtimes.
14
15pub mod linux;
16
17use crate::types::Ticks;
18
19/// A monotonic platform clock.
20///
21/// The epoch is implementation-defined; monotonicity is required.
22/// Conversions allow runtimes to expose a fast tick domain and still
23/// provide nanosecond correlation for telemetry and tracing.
24pub trait PlatformClock {
25    /// Return the current monotonic tick count.
26    fn now_ticks(&self) -> Ticks;
27
28    /// Convert ticks to nanoseconds.
29    fn ticks_to_nanos(&self, ticks: Ticks) -> u64;
30
31    /// Convert nanoseconds to ticks.
32    fn nanos_to_ticks(&self, ns: u64) -> Ticks;
33}
34
35/// A no-op clock implementation that always returns tick zero, useful for testing.
36#[derive(Debug, Default, Clone, Copy)]
37pub struct NoopClock;
38
39impl PlatformClock for NoopClock {
40    #[inline]
41    fn now_ticks(&self) -> Ticks {
42        Ticks::new(0)
43    }
44
45    #[inline]
46    fn ticks_to_nanos(&self, ticks: Ticks) -> u64 {
47        *ticks.as_u64()
48    }
49
50    #[inline]
51    fn nanos_to_ticks(&self, ns: u64) -> Ticks {
52        Ticks::new(ns)
53    }
54}
55
56impl PlatformClock for () {
57    #[inline]
58    fn now_ticks(&self) -> Ticks {
59        Ticks::new(0)
60    }
61
62    #[inline]
63    fn ticks_to_nanos(&self, ticks: Ticks) -> u64 {
64        *ticks.as_u64()
65    }
66
67    #[inline]
68    fn nanos_to_ticks(&self, ns: u64) -> Ticks {
69        Ticks::new(ns)
70    }
71}
72
73/// Timing span helper backed by a `PlatformClock`.
74///
75/// A span records a start tick from the provided clock and can be closed to
76/// obtain an elapsed duration in nanoseconds.
77pub struct Span<'a, C: PlatformClock> {
78    /// Clock used to obtain ticks and convert them to nanoseconds.
79    clk: &'a C,
80    /// Tick value at the start of the span.
81    start: Ticks,
82}
83
84impl<'a, C: PlatformClock> Span<'a, C> {
85    /// Start a new span using the given platform clock.
86    #[inline]
87    pub fn start(clk: &'a C) -> Self {
88        Self {
89            clk,
90            start: clk.now_ticks(),
91        }
92    }
93
94    /// End the span and return the elapsed time in nanoseconds.
95    #[inline]
96    pub fn end_ns(self) -> u64 {
97        let end = self.clk.now_ticks();
98        let t0 = self.clk.ticks_to_nanos(self.start);
99        let t1 = self.clk.ticks_to_nanos(end);
100        t1.saturating_sub(t0)
101    }
102}
103
104/// Optional timers service (P1/P2).
105///
106/// **Does not** enter `StepContext`. If a node requires timers, the host
107/// should inject a handle at construction time (out-of-band), not via the context.
108pub trait Timers {
109    /// Sleep/yield until the given tick, if supported.
110    fn sleep_until(&self, ticks: Ticks);
111
112    /// Sleep/yield for the given number of ticks.
113    fn sleep_for(&self, ticks: Ticks);
114}
115
116/// Optional affinities/NUMA hints (P2).
117///
118/// **Does not** enter `StepContext`. Placement and topology hints are owned
119/// by the runtime/scheduler, not by nodes.
120pub trait Affinity {
121    /// Pin the current worker to a logical core or group.
122    fn pin_to_core(&self, core_id: u32);
123
124    /// Provide NUMA node hint.
125    fn set_numa_node(&self, node_id: u32);
126}