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}