Next Generation WASM Microkernel Operating System
1// Copyright 2025. Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8mod entry;
9mod wheel;
10
11use crate::time::{Clock, NANOS_PER_SEC, TimeError, max_duration};
12use crate::{loom::sync::atomic::Ordering, time::Instant};
13use cordyceps::List;
14use core::{pin::Pin, ptr::NonNull, task::Poll, time::Duration};
15use spin::Mutex;
16use util::loom_const_fn;
17use wheel::Wheel;
18
19pub(in crate::time) use entry::Entry;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub struct Ticks(pub u64);
23
24#[derive(Copy, Clone, Debug)]
25pub struct Deadline {
26 ticks: Ticks,
27 slot: usize,
28 wheel: usize,
29}
30
31#[derive(Debug)]
32pub struct Timer {
33 clock: Clock,
34 tick_duration: Duration,
35 tick_duration_nanos: u64,
36 tick_ratio: u64,
37 core: Mutex<Core>,
38}
39
40#[derive(Debug)]
41struct Core {
42 /// The ticks that have elapsed since the wheel started.
43 now: Ticks,
44 /// Timer wheels
45 ///
46 /// Each timer has 6 wheels with 64 slots, giving each wheel a precision multiplier
47 /// of `64^x` where `x` is the wheel level:
48 ///
49 /// Levels:
50 /// - 64^0 slots
51 /// - 64^1 slots
52 /// - 64^2 slots
53 /// - 64^3 slots
54 /// - 64^4 slots
55 /// - 64^5 slots
56 ///
57 /// For example, a timer constructed from the default QEMU RISC-V CPU timebase frequency of `10_000_000 hz`
58 /// and therefore a `tick_duration` of `100 ns` (10000000 hz => 1/10000000 s => 0,0000001 s => 100ns)
59 /// will have the following wheel configuration:
60 ///
61 /// | wheel | multiplier | |
62 /// |-------|------------|-------------------------------|
63 /// | 0 | 64^0 | 100 ns slots / ~6 µs range |
64 /// | 1 | 64^1 | ~6 µs slots / ~4 ms range |
65 /// | 2 | 64^2 | ~4 ms slots / ~262 ms range |
66 /// | 3 | 64^3 | ~262 ms slots / ~17 sec range |
67 /// | 4 | 64^4 | 17 sec range / ~18 min range |
68 /// | 5 | 64^5 | 18 min range / ~19 hr range |
69 ///
70 /// As you can see, such a timer configuration can track time up to 19 hours into the future with
71 /// a precision of 100 nanoseconds. Quite high precision at the cost of a quite maximum timeout
72 /// duration.
73 ///
74 /// Here is another example of a "lower precision" clock configuration where the `tick_duration`
75 /// is `1 ms`:
76 ///
77 /// | wheel | multiplier | |
78 /// |-------|------------|-------------------------------|
79 /// | 0 | 64^0 | 1 ms slots / 64 ms range |
80 /// | 1 | 64^1 | 64 ms slots / ~ 4 sec range |
81 /// | 2 | 64^2 | ~ 4 sec slots / ~ 4 min range |
82 /// | 3 | 64^3 | ~ 4 min slots / ~ 4 hr range |
83 /// | 4 | 64^4 | ~ 4 hr slots / ~ 12 day range |
84 /// | 5 | 64^5 | ~ 12 day slots / ~ 2 yr range |
85 ///
86 /// As you can see with this configuration we are able to track time up to 2 years into the future
87 /// with a precision of 1 millisecond which should be an acceptable tradeoff for most applications.
88 wheels: [Wheel; Core::WHEELS],
89}
90
91// === impl Deadline ===
92
93impl Deadline {
94 pub fn as_ticks(&self) -> Ticks {
95 self.ticks
96 }
97
98 pub fn as_instant(&self, timer: &Timer) -> Instant {
99 Instant::from_ticks(timer, self.ticks)
100 }
101}
102
103// === impl Timer ===
104
105impl Timer {
106 loom_const_fn! {
107 pub const fn new(tick_duration: Duration, clock: Clock) -> Self {
108 let tick_duration_nanos = duration_to_nanos(tick_duration);
109
110 let tick_ratio = tick_duration_nanos / duration_to_nanos(clock.tick_duration());
111
112 Self {
113 clock,
114 tick_duration,
115 tick_duration_nanos,
116 tick_ratio,
117 core: Mutex::new(Core::new()),
118 }
119 }
120 }
121
122 /// Returns the current `now` timestamp, in [`Ticks`] of this timer's base
123 /// tick duration.
124 pub fn now(&self) -> Ticks {
125 Ticks(self.clock.now() / self.tick_ratio)
126 }
127
128 /// Returns the hardware clock backing this timer.
129 pub fn clock(&self) -> &Clock {
130 &self.clock
131 }
132
133 /// Returns the [`Duration`] of one tick of this Timer.
134 #[must_use]
135 pub const fn tick_duration(&self) -> Duration {
136 self.tick_duration
137 }
138
139 /// Returns the maximum duration of this Timer.
140 #[must_use]
141 pub fn max_duration(&self) -> Duration {
142 max_duration(self.tick_duration())
143 }
144
145 /// Convert the given raw [`Ticks`] into a [`Duration`] using this timers
146 /// internal tick duration.
147 ///
148 /// # Panics
149 ///
150 /// This method panics if the conversion from the given [`Ticks`] would overflow.
151 pub fn ticks_to_duration(&self, ticks: Ticks) -> Duration {
152 Duration::from_nanos(ticks.0 * self.tick_duration_nanos)
153 }
154
155 /// Convert the given [`Duration`] into a raw [`Ticks`] using this timers
156 /// internal tick duration.
157 ///
158 /// # Errors
159 ///
160 /// This method returns a `[TimeError::DurationTooLong`] if the conversion from the given [`Duration`]
161 /// into the 64-bits [`Ticks`] would overflow.
162 pub fn duration_to_ticks(&self, duration: Duration) -> Result<Ticks, TimeError> {
163 let duration_nanos =
164 checked_duration_to_nanos(duration).ok_or(TimeError::DurationTooLong {
165 requested: duration,
166 max: self.max_duration(),
167 })?;
168
169 Ok(Ticks(duration_nanos / self.tick_duration_nanos))
170 }
171
172 /// Advance the timer to the current time, waking any ready tasks.
173 ///
174 /// The return value indicates the number of tasks woken during this turn
175 /// as well as the next deadline (if any) at which new tasks will become ready.
176 ///
177 /// It is a good idea for the caller to wait until that deadline is reached.
178 pub fn turn(&self) -> (usize, Option<Deadline>) {
179 let mut core = self.core.lock();
180 self.turn_locked(&mut core)
181 }
182
183 /// Try to advance the timer to the current time, waking any ready tasks.
184 ///
185 /// This method *does not* block when the inner timer mutex lock cannot be acquired,
186 /// making it suitable to call in interrupt handlers.
187 ///
188 /// The return value indicates the number of tasks woken during this turn
189 /// as well as the next deadline (if any) at which new tasks will become ready.
190 ///
191 /// It is a good idea for the caller to wait until that deadline is reached.
192 pub fn try_turn(&self) -> Option<(usize, Option<Deadline>)> {
193 let mut core = self.core.try_lock()?;
194 Some(self.turn_locked(&mut core))
195 }
196
197 fn turn_locked(&self, core: &mut Core) -> (usize, Option<Deadline>) {
198 let mut now = self.now();
199 tracing::info!(now = ?now, "turn_locked");
200
201 if now < core.now {
202 tracing::warn!("time went backwards!");
203 now = core.now;
204 }
205
206 let mut expired = 0;
207 loop {
208 let (_expired, next_deadline) = core.poll(now);
209 expired += _expired;
210 if let Some(next) = next_deadline {
211 now = self.now();
212 if now >= next.as_ticks() {
213 // we've advanced past the next deadline, so we need to
214 // advance again.
215 continue;
216 }
217 }
218
219 return (expired, next_deadline);
220 }
221 }
222
223 /// Schedule a wakeup using this timers hardware [`Clock`].
224 ///
225 /// If the provided argument is `Some()` the clock is instructed to schedule a wakeup
226 /// at that deadline, if `None` is provided, a deadline *maximally far in the future* is chosen.
227 pub fn schedule_wakeup(&self, maybe_next_deadline: Option<Deadline>) {
228 if let Some(next_deadline) = maybe_next_deadline {
229 let virt = next_deadline.as_ticks();
230 let phys = virt.0 * self.tick_ratio;
231 self.clock.schedule_wakeup(phys);
232 } else {
233 self.clock.schedule_wakeup(u64::MAX);
234 }
235 }
236
237 pub(super) fn cancel(&self, entry: Pin<&mut Entry>) {
238 let mut core = self.core.lock();
239 core.cancel(entry);
240
241 self.schedule_wakeup(core.next_deadline());
242 }
243
244 pub(super) fn register(&self, entry: Pin<&mut Entry>) -> Poll<()> {
245 let mut core = self.core.lock();
246 let poll = core.register(entry);
247
248 self.schedule_wakeup(core.next_deadline());
249
250 poll
251 }
252}
253
254// === impl Core ===
255
256impl Core {
257 const WHEELS: usize = Wheel::BITS;
258 const MAX_SLEEP_TICKS: u64 = (1 << (Wheel::BITS * Self::WHEELS)) - 1;
259
260 #[inline]
261 const fn new() -> Self {
262 Self {
263 now: Ticks(0),
264 #[cfg(not(loom))]
265 wheels: [
266 Wheel::new(0),
267 Wheel::new(1),
268 Wheel::new(2),
269 Wheel::new(3),
270 Wheel::new(4),
271 Wheel::new(5),
272 ],
273 #[cfg(loom)]
274 wheels: [Wheel::new(0), Wheel::new(1)],
275 }
276 }
277
278 fn poll(&mut self, now: Ticks) -> (usize, Option<Deadline>) {
279 // sleeps that need to be rescheduled on lower-level wheels need to be
280 // processed after we have finished turning the wheel, to avoid looping
281 // infinitely.
282 let mut pending_reschedule = List::<Entry>::new();
283
284 let mut expired = 0;
285
286 // we will stop looping if the next deadline is after `now`, but we
287 // still need to be able to return it.
288 let mut next_deadline = self.next_deadline();
289 while let Some(deadline) = next_deadline {
290 // if the deadline is in the future we don't need to continue
291 if deadline.ticks > now {
292 break;
293 }
294
295 // Note that we need to take _all_ of the entries off the list before
296 // processing any of them. This is important because it's possible that
297 // those entries might need to be reinserted into the same slot.
298 //
299 // This happens only on the highest level, when an entry is inserted
300 // more than MAX_DURATION into the future. When this happens, we wrap
301 // around, and process some entries a multiple of MAX_DURATION before
302 // they actually need to be dropped down a level. We then reinsert them
303 // back into the same position; we must make sure we don't then process
304 // those entries again, or we'll end up in an infinite loop.
305 let entries = self.wheels[deadline.wheel].take_slot(deadline.slot);
306 for entry in entries {
307 // Safety: upon registering the caller promised the entry is valid
308 let entry_deadline = unsafe { entry.as_ref().deadline };
309
310 if entry_deadline > now {
311 // this timer was on the top-level wheel and needs to be
312 // rescheduled on a lower-level wheel, rather than firing now.
313 debug_assert_ne!(
314 deadline.wheel, 0,
315 "if a timer is being rescheduled, it must not have been on the lowest-level wheel"
316 );
317 tracing::trace!(
318 "rescheduling entry {entry:?} because deadline {entry_deadline:?} is later than now {now:?}"
319 );
320 // this timer will need to be rescheduled.
321 pending_reschedule.push_front(entry);
322 } else {
323 // otherwise, fire the timer
324 // Safety: upon registering the caller promised the entry is valid
325 unsafe {
326 expired += 1;
327 entry.as_ref().fire();
328 }
329 }
330 }
331
332 self.now = deadline.ticks;
333 next_deadline = self.next_deadline();
334 }
335
336 self.now = now;
337
338 let any = !pending_reschedule.is_empty();
339
340 for entry in pending_reschedule {
341 // Safety: upon registering the caller promised the entry is valid
342 let entry_deadline = unsafe { entry.as_ref().deadline };
343
344 debug_assert!(entry_deadline > self.now);
345 debug_assert_ne!(entry_deadline, Ticks(0));
346 self.insert_at(entry_deadline, entry);
347 }
348
349 if any {
350 next_deadline = self.next_deadline();
351 }
352
353 (expired, next_deadline)
354 }
355
356 fn next_deadline(&self) -> Option<Deadline> {
357 self.wheels.iter().find_map(|wheel| {
358 let next_deadline = wheel.next_deadline(self.now)?;
359 Some(next_deadline)
360 })
361 }
362
363 fn cancel(&mut self, entry: Pin<&mut Entry>) {
364 let deadline = entry.deadline;
365 tracing::trace!("canceling entry={entry:?};now={:?}", self.now);
366 let wheel = self.wheel_index(deadline);
367 self.wheels[wheel].remove(deadline, entry);
368 }
369
370 fn register(&mut self, entry: Pin<&mut Entry>) -> Poll<()> {
371 let deadline = {
372 tracing::trace!("registering entry={entry:?};now={:?}", self.now);
373
374 if entry.deadline <= self.now {
375 tracing::trace!("timer already completed, firing immediately");
376 entry.fire();
377 return Poll::Ready(());
378 }
379
380 let _did_link = entry.is_registered.compare_exchange(
381 false,
382 true,
383 Ordering::AcqRel,
384 Ordering::Acquire,
385 );
386 debug_assert!(
387 _did_link.is_ok(),
388 "tried to register a sleep that was already registered"
389 );
390
391 entry.deadline
392 };
393
394 // Safety: TODO
395 let ptr = unsafe { NonNull::from(Pin::into_inner_unchecked(entry)) };
396
397 self.insert_at(deadline, ptr);
398 Poll::Pending
399 }
400
401 fn insert_at(&mut self, deadline: Ticks, ptr: NonNull<Entry>) {
402 let wheel = self.wheel_index(deadline);
403 tracing::trace!("inserting entry={ptr:?};deadline={deadline:?}");
404 self.wheels[wheel].insert(deadline, ptr);
405 }
406
407 #[inline]
408 fn wheel_index(&self, ticks: Ticks) -> usize {
409 wheel_index(self.now, ticks)
410 }
411}
412
413fn wheel_index(now: Ticks, ticks: Ticks) -> usize {
414 const WHEEL_MASK: u64 = (1 << Wheel::BITS) - 1;
415
416 // mask out the bits representing the index in the wheel
417 let mut wheel_indices = now.0 ^ ticks.0 | WHEEL_MASK;
418
419 // put sleeps over the max duration in the top level wheel
420 if wheel_indices >= Core::MAX_SLEEP_TICKS {
421 wheel_indices = Core::MAX_SLEEP_TICKS - 1;
422 }
423
424 let zeros = wheel_indices.leading_zeros();
425 let rest = u64::BITS - 1 - zeros;
426
427 rest as usize / Core::WHEELS
428}
429
430#[inline]
431const fn duration_to_nanos(duration: Duration) -> u64 {
432 duration.as_secs() * NANOS_PER_SEC + duration.subsec_nanos() as u64
433}
434
435#[inline]
436fn checked_duration_to_nanos(duration: Duration) -> Option<u64> {
437 duration
438 .as_secs()
439 .checked_mul(NANOS_PER_SEC)?
440 .checked_add(u64::from(duration.subsec_nanos()))
441}