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#[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 pub const fn depth(self) -> u32 {
30 self.depth
31 }
32
33 pub const fn is_pending(self) -> bool {
35 self.pending
36 }
37}
38
39#[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#[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#[must_use]
56pub enum PreemptionExit {
57 Nested,
59 Enabled,
61 Pending(PendingPreemption),
63}
64
65#[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 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 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 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 #[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 #[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 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 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 pub fn release(self) {
304 unsafe { self.owner.as_ref() }.release_pending();
306 }
307}
308
309#[inline(always)]
311pub fn enter_preemption() -> PreemptionToken {
312 #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
313 {
314 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 let owner = unsafe { current.as_ref() }.preemption_state();
330 owner.enter();
331 PreemptionToken::new(owner)
332 }
333}
334
335#[inline(always)]
337pub fn preemption_snapshot(pin: &CpuPin<'_>) -> Result<PreemptionSnapshot, CpuLocalError> {
338 Ok(selected_state(pin)?.snapshot())
339}
340
341#[inline(always)]
347pub fn current_preemption_pending() -> Result<bool, CpuLocalError> {
348 Ok(crate::register::current_preemption_snapshot()?.is_pending())
349}
350
351#[inline(always)]
353pub fn set_preemption_pending(pin: &CpuPin<'_>) -> Result<(), CpuLocalError> {
354 selected_state(pin)?.set_pending();
355 Ok(())
356}
357
358#[inline(always)]
360pub fn clear_preemption_pending(pin: &CpuPin<'_>) -> Result<(), CpuLocalError> {
361 selected_state(pin)?.clear_pending();
362 Ok(())
363}
364
365#[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 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 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 unsafe { crate::register::decrement_current_x86_preemption_state() };
405 PreemptionExit::Nested
406}
407
408#[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#[doc(hidden)]
450pub fn release_bootstrap_preemption(pin: &CpuPin<'_>) -> Result<(), CpuLocalError> {
451 selected_state(pin)?.release_bootstrap();
452 Ok(())
453}
454
455#[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 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 assert!(original_cpu.release_initial_switch());
616 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 assert!(unsafe { PreemptionToken::from_raw(1) }.is_none());
633 }
634}