Skip to main content

cpu_local/
preempt.rs

1use core::{
2    marker::PhantomData,
3    mem::ManuallyDrop,
4    ptr::NonNull,
5    sync::atomic::{AtomicU32, Ordering},
6};
7
8use crate::{CpuLocalError, CpuPin};
9
10const PREEMPT_NO_PENDING: u32 = 1 << 31;
11const PREEMPT_DEPTH_MASK: u32 = !PREEMPT_NO_PENDING;
12
13/// Snapshot of one architecture-selected preemption state.
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub struct PreemptionSnapshot {
16    depth: u32,
17    pending: bool,
18}
19
20impl PreemptionSnapshot {
21    pub(crate) const fn from_raw(state: u32) -> Self {
22        Self {
23            depth: state & PREEMPT_DEPTH_MASK,
24            pending: state & PREEMPT_NO_PENDING == 0,
25        }
26    }
27
28    /// Returns the number of active preemption exclusions.
29    pub const fn depth(self) -> u32 {
30        self.depth
31    }
32
33    /// Returns whether work is pending at the next preemptible boundary.
34    pub const fn is_pending(self) -> bool {
35        self.pending
36    }
37}
38
39/// Linear proof of one entered preemption exclusion.
40#[must_use = "every entered preemption token must be finished exactly once"]
41pub struct PreemptionToken {
42    #[cfg(any(test, not(target_arch = "x86_64"), feature = "host-test"))]
43    owner: NonNull<PreemptionState>,
44    _not_send_or_sync: PhantomData<*mut ()>,
45}
46
47/// Linear proof that the final preemption depth is reserved for a safe point.
48#[must_use = "pending preemption must be released by the external safe-point owner"]
49pub struct PendingPreemption {
50    owner: NonNull<PreemptionState>,
51    _not_send_or_sync: PhantomData<*mut ()>,
52}
53
54/// Result of finishing one preemption exclusion.
55#[must_use]
56pub enum PreemptionExit {
57    /// A nested depth was consumed; preemption remains excluded.
58    Nested,
59    /// The final depth was consumed and execution is preemptible.
60    Enabled,
61    /// The final depth remains reserved until an external safe point claims it.
62    Pending(PendingPreemption),
63}
64
65/// Architecture-neutral preemption word.
66///
67/// The high bit uses inverted pending polarity, allowing a newly initialized
68/// context to start enabled without a runtime initialization write. The low
69/// bits contain the exclusion depth. All updates are local to the selected CPU
70/// or pinned context; external safe-point serialization provides ordering for
71/// protected state, so this word does not publish cross-CPU data.
72#[repr(transparent)]
73pub(crate) struct PreemptionState(AtomicU32);
74
75impl PreemptionState {
76    pub(crate) const fn new() -> Self {
77        Self(AtomicU32::new(PREEMPT_NO_PENDING))
78    }
79
80    pub(crate) const fn bootstrap_disabled() -> Self {
81        Self(AtomicU32::new(PREEMPT_NO_PENDING | 1))
82    }
83
84    pub(crate) fn snapshot(&self) -> PreemptionSnapshot {
85        PreemptionSnapshot::from_raw(self.0.load(Ordering::Relaxed))
86    }
87
88    fn set_pending(&self) {
89        self.0.fetch_and(PREEMPT_DEPTH_MASK, Ordering::Relaxed);
90    }
91
92    fn clear_pending(&self) {
93        self.0.fetch_or(PREEMPT_NO_PENDING, Ordering::Relaxed);
94    }
95
96    #[cfg(all(test, target_arch = "x86_64", not(feature = "host-test")))]
97    pub(crate) fn as_mut_ptr(&self) -> *mut u32 {
98        self.0.as_ptr()
99    }
100
101    #[cfg(any(test, not(target_arch = "x86_64"), feature = "host-test"))]
102    #[inline(always)]
103    fn compare_exchange_local(&self, current: u32, next: u32) -> bool {
104        #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
105        {
106            // SAFETY: a live token retains positive depth on this exact
107            // CPU-owned word. No remote CPU may update it; a local interrupt
108            // can update it only before or after the single instruction.
109            unsafe { crate::register::compare_exchange_x86_preemption_state(self, current, next) }
110        }
111
112        #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
113        {
114            self.0
115                .compare_exchange_weak(current, next, Ordering::Relaxed, Ordering::Relaxed)
116                .is_ok()
117        }
118    }
119
120    #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
121    fn enter(&self) {
122        let previous = self.0.fetch_add(1, Ordering::Relaxed);
123        assert_ne!(
124            previous & PREEMPT_DEPTH_MASK,
125            PREEMPT_DEPTH_MASK,
126            "preemption nesting overflow"
127        );
128    }
129
130    #[cfg(any(test, not(target_arch = "x86_64"), feature = "host-test"))]
131    fn finish(&self) -> PreemptionExit {
132        loop {
133            let state = self.0.load(Ordering::Relaxed);
134            let depth = state & PREEMPT_DEPTH_MASK;
135            assert!(depth > 0, "unbalanced preemption exit");
136
137            if depth == 1 && state & PREEMPT_NO_PENDING == 0 {
138                return PreemptionExit::Pending(PendingPreemption::new(self));
139            }
140
141            // A local interrupt may update the sticky pending bit, and weak
142            // LL/SC compare-exchange may also fail spuriously. Retry at a
143            // fixed stack depth, matching Linux's bounded IRQ-return loop.
144            if self.compare_exchange_local(state, state - 1) {
145                return if depth == 1 {
146                    PreemptionExit::Enabled
147                } else {
148                    PreemptionExit::Nested
149                };
150            }
151        }
152    }
153
154    fn release_pending(&self) {
155        assert_eq!(
156            self.0
157                .compare_exchange(1, 0, Ordering::Relaxed, Ordering::Relaxed),
158            Ok(1),
159            "pending preemption no longer owns the final depth"
160        );
161    }
162
163    fn release_bootstrap(&self) {
164        loop {
165            let state = self.0.load(Ordering::Relaxed);
166            assert!(
167                state == (PREEMPT_NO_PENDING | 1) || state == 1,
168                "bootstrap preemption depth must be released exactly once"
169            );
170            if self
171                .0
172                .compare_exchange_weak(state, state - 1, Ordering::Relaxed, Ordering::Relaxed)
173                .is_ok()
174            {
175                return;
176            }
177        }
178    }
179
180    #[cfg(any(all(target_arch = "x86_64", not(feature = "host-test")), test))]
181    fn release_initial_switch(&self) -> bool {
182        loop {
183            let state = self.0.load(Ordering::Relaxed);
184            if state == PREEMPT_NO_PENDING {
185                return false;
186            }
187            if state != (PREEMPT_NO_PENDING | 1) && state != 1 {
188                panic!("initial context switch found invalid preemption state {state:#x}");
189            }
190            // A nested outgoing guard may mirror its owner's pending bit after
191            // the switch guard was entered. The incoming context has its own
192            // upper-layer publication, so consume the inherited depth and
193            // reset that stale mirror as one transition.
194            if self
195                .0
196                .compare_exchange_weak(
197                    state,
198                    PREEMPT_NO_PENDING,
199                    Ordering::Relaxed,
200                    Ordering::Relaxed,
201                )
202                .is_ok()
203            {
204                return true;
205            }
206        }
207    }
208}
209
210impl PreemptionToken {
211    #[cfg(any(test, not(target_arch = "x86_64"), feature = "host-test"))]
212    fn new(owner: &PreemptionState) -> Self {
213        Self {
214            owner: NonNull::from(owner),
215            _not_send_or_sync: PhantomData,
216        }
217    }
218
219    #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
220    fn new_current() -> Self {
221        Self {
222            _not_send_or_sync: PhantomData,
223        }
224    }
225
226    /// Converts this token into an opaque value for an ABI that transports
227    /// guard state as one machine word.
228    #[doc(hidden)]
229    pub fn into_raw(self) -> usize {
230        #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
231        {
232            let _token = ManuallyDrop::new(self);
233            1
234        }
235        #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
236        {
237            let token = ManuallyDrop::new(self);
238            token.owner.as_ptr() as usize
239        }
240    }
241
242    /// Reconstructs a token produced by [`Self::into_raw`].
243    ///
244    /// # Safety
245    ///
246    /// `raw` must come from one still-live token, refer to a live CPU-local
247    /// preemption owner, and be reconstructed and finished exactly once.
248    #[doc(hidden)]
249    pub unsafe fn from_raw(raw: usize) -> Option<Self> {
250        #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
251        {
252            (raw == 1).then(Self::new_current)
253        }
254        #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
255        {
256            if !raw.is_multiple_of(core::mem::align_of::<PreemptionState>()) {
257                return None;
258            }
259            NonNull::new(raw as *mut PreemptionState).map(|owner| Self {
260                owner,
261                _not_send_or_sync: PhantomData,
262            })
263        }
264    }
265
266    #[cfg(any(test, not(target_arch = "x86_64"), feature = "host-test"))]
267    fn state(&self) -> &PreemptionState {
268        // SAFETY: construction and raw reconstruction require the selected
269        // owner to remain live through this token's single finish operation.
270        unsafe { self.owner.as_ref() }
271    }
272
273    #[cfg(any(all(target_arch = "x86_64", not(feature = "host-test")), test))]
274    fn handoff_after_context_switch(self, resumed_owner: &PreemptionState) -> Self {
275        #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
276        {
277            let _ = resumed_owner;
278            self
279        }
280        #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
281        {
282            if self.owner == NonNull::from(resumed_owner) {
283                self
284            } else {
285                // The old CPU's incoming context consumed the depth represented by
286                // this proof. Consume the proof itself and adopt the equivalent
287                // depth left by the outgoing context on the resumed CPU.
288                Self::new(resumed_owner)
289            }
290        }
291    }
292}
293
294impl PendingPreemption {
295    fn new(owner: &PreemptionState) -> Self {
296        Self {
297            owner: NonNull::from(owner),
298            _not_send_or_sync: PhantomData,
299        }
300    }
301
302    /// Atomically consumes the final reserved preemption depth.
303    pub fn release(self) {
304        // SAFETY: the linear token retains its exact owner until this consume.
305        unsafe { self.owner.as_ref() }.release_pending();
306    }
307}
308
309/// Enters preemption exclusion on the owner selected by this architecture.
310#[inline(always)]
311pub fn enter_preemption() -> PreemptionToken {
312    #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
313    {
314        // Increment through GS before resolving the token owner. Once the
315        // increment is visible this execution cannot migrate away from it.
316        unsafe { crate::register::enter_x86_preemption() };
317        PreemptionToken::new_current()
318    }
319
320    #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
321    {
322        let current = unsafe { crate::current_context_unpinned() }
323            .unwrap_or_else(|_| crate::register::fatal_register_invariant());
324        // SAFETY: the architecture current register identifies the executing
325        // context itself. An interrupt may suspend and later migrate that
326        // context between this read and the increment, but it cannot resume
327        // this instruction stream as a different context; the pinned header
328        // stays live and therefore remains the exact token owner.
329        let owner = unsafe { current.as_ref() }.preemption_state();
330        owner.enter();
331        PreemptionToken::new(owner)
332    }
333}
334
335/// Observes the current architecture-selected preemption state.
336#[inline(always)]
337pub fn preemption_snapshot(pin: &CpuPin<'_>) -> Result<PreemptionSnapshot, CpuLocalError> {
338    Ok(selected_state(pin)?.snapshot())
339}
340
341/// Tests the current execution context's advisory preemption-pending state.
342///
343/// This is an architecture-local safe-point query. It neither publishes nor
344/// consumes external work. A `false` result is only a snapshot; a later
345/// interrupt may publish work immediately after the observation.
346#[inline(always)]
347pub fn current_preemption_pending() -> Result<bool, CpuLocalError> {
348    Ok(crate::register::current_preemption_snapshot()?.is_pending())
349}
350
351/// Marks work pending at the current preemptible boundary.
352#[inline(always)]
353pub fn set_preemption_pending(pin: &CpuPin<'_>) -> Result<(), CpuLocalError> {
354    selected_state(pin)?.set_pending();
355    Ok(())
356}
357
358/// Clears the pending mark after the external owner has drained its work.
359#[inline(always)]
360pub fn clear_preemption_pending(pin: &CpuPin<'_>) -> Result<(), CpuLocalError> {
361    selected_state(pin)?.clear_pending();
362    Ok(())
363}
364
365/// Finishes the exact owner captured by [`enter_preemption`].
366#[inline(always)]
367pub fn finish_preemption(token: PreemptionToken) -> PreemptionExit {
368    #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
369    {
370        let _token = ManuallyDrop::new(token);
371        finish_current_x86_preemption()
372    }
373    #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
374    {
375        token.state().finish()
376    }
377}
378
379#[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
380#[inline(always)]
381fn finish_current_x86_preemption() -> PreemptionExit {
382    // SAFETY: the consumed token proves a positive depth on the GS-selected
383    // word until this transition completes.
384    let state = unsafe { crate::register::read_current_x86_preemption_state_raw() };
385    let depth = state & PREEMPT_DEPTH_MASK;
386    assert!(depth > 0, "unbalanced preemption exit");
387
388    if depth == 1 {
389        if state & PREEMPT_NO_PENDING == 0 {
390            // SAFETY: the retained final depth still pins the current owner.
391            let owner = unsafe { crate::register::current_x86_preemption_state() };
392            return PreemptionExit::Pending(PendingPreemption::new(owner));
393        }
394        return if unsafe {
395            crate::register::compare_exchange_current_x86_preemption_state(state, state - 1)
396        } {
397            PreemptionExit::Enabled
398        } else {
399            finish_current_x86_preemption()
400        };
401    }
402
403    // SAFETY: a nested depth remains positive after the single local update.
404    unsafe { crate::register::decrement_current_x86_preemption_state() };
405    PreemptionExit::Nested
406}
407
408/// Transfers a CPU-owned switch exclusion to the CPU where its context resumed.
409///
410/// Context-owned tokens keep their original owner across migration. A CPU-owned
411/// token is replaced only when a context switch resumes it on another CPU: the
412/// old CPU's incoming context has already consumed the old switch depth, while
413/// this CPU's outgoing context left the matching depth for the resumed guard.
414///
415/// # Errors
416///
417/// Returns an error when the pinned CPU has no valid selected preemption owner.
418///
419/// # Panics
420///
421/// Panics when a context-owned architecture observes a different owner after
422/// the context switch. Such architectures migrate the owner with the context.
423#[doc(hidden)]
424pub fn handoff_preemption_after_context_switch(
425    pin: &CpuPin<'_>,
426    token: PreemptionToken,
427) -> Result<PreemptionToken, CpuLocalError> {
428    let owner = selected_state(pin)?;
429    #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
430    {
431        Ok(token.handoff_after_context_switch(owner))
432    }
433
434    #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
435    {
436        assert_eq!(
437            token.owner,
438            NonNull::from(owner),
439            "context-owned preemption token changed owner across a context switch"
440        );
441        Ok(token)
442    }
443}
444
445/// Releases the single preemption depth inherited by a bootstrap context.
446///
447/// The caller uses this only after the context and every local safe-point
448/// dependency have been published.
449#[doc(hidden)]
450pub fn release_bootstrap_preemption(pin: &CpuPin<'_>) -> Result<(), CpuLocalError> {
451    selected_state(pin)?.release_bootstrap();
452    Ok(())
453}
454
455/// Releases the preemption exclusion transferred to a context on its first
456/// architecture switch.
457///
458/// CPU-owned preemption architectures carry the outgoing switch exclusion
459/// across the raw transfer, but a new context has no suspended caller whose
460/// guard can finish it. Context-owned architectures need no action because the
461/// new header begins enabled.
462#[doc(hidden)]
463pub fn release_initial_context_preemption(pin: &CpuPin<'_>) -> Result<bool, CpuLocalError> {
464    #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
465    {
466        Ok(selected_state(pin)?.release_initial_switch())
467    }
468
469    #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
470    {
471        let snapshot = selected_state(pin)?.snapshot();
472        assert_eq!(
473            snapshot.depth(),
474            0,
475            "new context-owned preemption state must start enabled"
476        );
477        Ok(false)
478    }
479}
480
481#[inline(always)]
482fn selected_state(pin: &CpuPin<'_>) -> Result<&'static PreemptionState, CpuLocalError> {
483    #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
484    {
485        Ok(pin.area().runtime_anchor().preemption_state())
486    }
487
488    #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
489    {
490        let current = crate::current_context(pin)?;
491        // SAFETY: current publication keeps the pinned context live, and the
492        // caller's CpuPin prevents it from changing during this observation.
493        Ok(unsafe { current.as_ref() }.preemption_state())
494    }
495}
496
497#[cfg(test)]
498mod tests {
499    use super::*;
500
501    fn enter_on(state: &PreemptionState) -> PreemptionToken {
502        state.enter();
503        PreemptionToken::new(state)
504    }
505
506    #[test]
507    fn nested_and_final_exits_are_linear() {
508        let state = PreemptionState::new();
509        let outer = enter_on(&state);
510        let inner = enter_on(&state);
511
512        assert!(matches!(finish_preemption(inner), PreemptionExit::Nested));
513        assert_eq!(state.snapshot().depth(), 1);
514        assert!(matches!(finish_preemption(outer), PreemptionExit::Enabled));
515        assert_eq!(state.snapshot().depth(), 0);
516    }
517
518    #[test]
519    fn pending_exit_reserves_depth_until_release() {
520        let state = PreemptionState::new();
521        let token = enter_on(&state);
522        state.set_pending();
523
524        let PreemptionExit::Pending(pending) = finish_preemption(token) else {
525            panic!("final pending exit must retain its depth");
526        };
527        assert_eq!(state.snapshot().depth(), 1);
528        pending.release();
529        assert_eq!(state.snapshot().depth(), 0);
530        assert!(state.snapshot().is_pending());
531    }
532
533    #[test]
534    fn bootstrap_starts_disabled() {
535        let state = PreemptionState::bootstrap_disabled();
536        assert_eq!(state.snapshot().depth(), 1);
537        assert!(!state.snapshot().is_pending());
538    }
539
540    #[test]
541    fn bootstrap_release_preserves_pending_external_work() {
542        let state = PreemptionState::bootstrap_disabled();
543        state.set_pending();
544
545        state.release_bootstrap();
546
547        assert_eq!(state.snapshot().depth(), 0);
548        assert!(state.snapshot().is_pending());
549    }
550
551    #[test]
552    fn initial_switch_discards_outgoing_pending_mirror() {
553        let state = PreemptionState::bootstrap_disabled();
554        state.set_pending();
555
556        assert!(state.release_initial_switch());
557        assert_eq!(state.snapshot().depth(), 0);
558        assert!(!state.snapshot().is_pending());
559    }
560
561    #[test]
562    #[should_panic(expected = "pending preemption no longer owns the final depth")]
563    fn pending_depth_cannot_be_consumed_twice() {
564        let state = PreemptionState(AtomicU32::new(1));
565        let first = PendingPreemption::new(&state);
566        let duplicate = PendingPreemption::new(&state);
567
568        first.release();
569        duplicate.release();
570    }
571
572    #[test]
573    #[should_panic(expected = "bootstrap preemption depth must be released exactly once")]
574    fn bootstrap_depth_cannot_be_released_twice() {
575        let state = PreemptionState::bootstrap_disabled();
576
577        state.release_bootstrap();
578        state.release_bootstrap();
579    }
580
581    #[test]
582    fn token_stays_bound_to_the_entry_owner() {
583        let original_context = PreemptionState::new();
584        let migrated_context = PreemptionState::new();
585        let original_cpu = PreemptionState::new();
586        let migrated_cpu = PreemptionState::new();
587
588        let context_token = enter_on(&original_context);
589        let cpu_token = enter_on(&original_cpu);
590        migrated_context.enter();
591        migrated_cpu.enter();
592
593        assert!(matches!(
594            finish_preemption(context_token),
595            PreemptionExit::Enabled
596        ));
597        assert!(matches!(
598            finish_preemption(cpu_token),
599            PreemptionExit::Enabled
600        ));
601        assert_eq!(original_context.snapshot().depth(), 0);
602        assert_eq!(original_cpu.snapshot().depth(), 0);
603        assert_eq!(migrated_context.snapshot().depth(), 1);
604        assert_eq!(migrated_cpu.snapshot().depth(), 1);
605    }
606
607    #[test]
608    fn cpu_owned_switch_token_handoff_follows_the_resumed_cpu() {
609        let original_cpu = PreemptionState::new();
610        let resumed_cpu = PreemptionState::new();
611
612        let suspended_switch = enter_on(&original_cpu);
613        // Another incoming context consumes the switch depth left on the old
614        // CPU while this context is suspended.
615        assert!(original_cpu.release_initial_switch());
616        // The outgoing context on the destination CPU leaves the equivalent
617        // switch depth for the context that is about to resume there.
618        resumed_cpu.enter();
619
620        let resumed_switch = suspended_switch.handoff_after_context_switch(&resumed_cpu);
621        assert!(matches!(
622            finish_preemption(resumed_switch),
623            PreemptionExit::Enabled
624        ));
625        assert_eq!(original_cpu.snapshot().depth(), 0);
626        assert_eq!(resumed_cpu.snapshot().depth(), 0);
627    }
628
629    #[test]
630    fn malformed_raw_owner_is_rejected() {
631        // SAFETY: no token is reconstructed because the value is misaligned.
632        assert!(unsafe { PreemptionToken::from_raw(1) }.is_none());
633    }
634}