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 const fn depth(self) -> u32 {
23 self.depth
24 }
25
26 pub const fn is_pending(self) -> bool {
28 self.pending
29 }
30}
31
32#[must_use = "every entered preemption token must be finished exactly once"]
34pub struct PreemptionToken {
35 owner: NonNull<PreemptionState>,
36 _not_send_or_sync: PhantomData<*mut ()>,
37}
38
39#[must_use = "pending preemption must be released by the external safe-point owner"]
41pub struct PendingPreemption {
42 owner: NonNull<PreemptionState>,
43 _not_send_or_sync: PhantomData<*mut ()>,
44}
45
46#[must_use]
48pub enum PreemptionExit {
49 Nested,
51 Enabled,
53 Pending(PendingPreemption),
55}
56
57#[repr(transparent)]
65pub(crate) struct PreemptionState(AtomicU32);
66
67impl PreemptionState {
68 pub(crate) const fn new() -> Self {
69 Self(AtomicU32::new(PREEMPT_NO_PENDING))
70 }
71
72 pub(crate) const fn bootstrap_disabled() -> Self {
73 Self(AtomicU32::new(PREEMPT_NO_PENDING | 1))
74 }
75
76 fn snapshot(&self) -> PreemptionSnapshot {
77 let state = self.0.load(Ordering::Relaxed);
78 PreemptionSnapshot {
79 depth: state & PREEMPT_DEPTH_MASK,
80 pending: state & PREEMPT_NO_PENDING == 0,
81 }
82 }
83
84 fn set_pending(&self) {
85 self.0.fetch_and(PREEMPT_DEPTH_MASK, Ordering::Relaxed);
86 }
87
88 fn clear_pending(&self) {
89 self.0.fetch_or(PREEMPT_NO_PENDING, Ordering::Relaxed);
90 }
91
92 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
93 fn enter(&self) {
94 let previous = self.0.fetch_add(1, Ordering::Relaxed);
95 assert_ne!(
96 previous & PREEMPT_DEPTH_MASK,
97 PREEMPT_DEPTH_MASK,
98 "preemption nesting overflow"
99 );
100 }
101
102 fn finish(&self) -> PreemptionExit {
103 loop {
104 let state = self.0.load(Ordering::Relaxed);
105 let depth = state & PREEMPT_DEPTH_MASK;
106 assert!(depth > 0, "unbalanced preemption exit");
107
108 if depth == 1 && state & PREEMPT_NO_PENDING == 0 {
109 return PreemptionExit::Pending(PendingPreemption::new(self));
110 }
111
112 let next = state - 1;
113 if self
114 .0
115 .compare_exchange_weak(state, next, Ordering::Relaxed, Ordering::Relaxed)
116 .is_ok()
117 {
118 return if depth == 1 {
119 PreemptionExit::Enabled
120 } else {
121 PreemptionExit::Nested
122 };
123 }
124 }
125 }
126
127 fn release_pending(&self) {
128 assert_eq!(
129 self.0
130 .compare_exchange(1, 0, Ordering::Relaxed, Ordering::Relaxed),
131 Ok(1),
132 "pending preemption no longer owns the final depth"
133 );
134 }
135
136 fn release_bootstrap(&self) {
137 assert_eq!(
138 self.0.compare_exchange(
139 PREEMPT_NO_PENDING | 1,
140 PREEMPT_NO_PENDING,
141 Ordering::Relaxed,
142 Ordering::Relaxed,
143 ),
144 Ok(PREEMPT_NO_PENDING | 1),
145 "bootstrap preemption depth must be released exactly once"
146 );
147 }
148
149 #[cfg(any(all(target_arch = "x86_64", not(feature = "host-test")), test))]
150 fn release_initial_switch(&self) -> bool {
151 loop {
152 let state = self.0.load(Ordering::Relaxed);
153 if state == PREEMPT_NO_PENDING {
154 return false;
155 }
156 if state != (PREEMPT_NO_PENDING | 1) && state != 1 {
157 panic!("initial context switch found invalid preemption state {state:#x}");
158 }
159 if self
164 .0
165 .compare_exchange_weak(
166 state,
167 PREEMPT_NO_PENDING,
168 Ordering::Relaxed,
169 Ordering::Relaxed,
170 )
171 .is_ok()
172 {
173 return true;
174 }
175 }
176 }
177}
178
179impl PreemptionToken {
180 fn new(owner: &PreemptionState) -> Self {
181 Self {
182 owner: NonNull::from(owner),
183 _not_send_or_sync: PhantomData,
184 }
185 }
186
187 #[doc(hidden)]
190 pub fn into_raw(self) -> usize {
191 let token = ManuallyDrop::new(self);
192 token.owner.as_ptr() as usize
193 }
194
195 #[doc(hidden)]
202 pub unsafe fn from_raw(raw: usize) -> Option<Self> {
203 if !raw.is_multiple_of(core::mem::align_of::<PreemptionState>()) {
204 return None;
205 }
206 NonNull::new(raw as *mut PreemptionState).map(|owner| Self {
207 owner,
208 _not_send_or_sync: PhantomData,
209 })
210 }
211
212 fn state(&self) -> &PreemptionState {
213 unsafe { self.owner.as_ref() }
216 }
217
218 #[cfg(any(all(target_arch = "x86_64", not(feature = "host-test")), test))]
219 fn handoff_after_context_switch(self, resumed_owner: &PreemptionState) -> Self {
220 if self.owner == NonNull::from(resumed_owner) {
221 self
222 } else {
223 Self::new(resumed_owner)
227 }
228 }
229}
230
231impl PendingPreemption {
232 fn new(owner: &PreemptionState) -> Self {
233 Self {
234 owner: NonNull::from(owner),
235 _not_send_or_sync: PhantomData,
236 }
237 }
238
239 pub fn release(self) {
241 unsafe { self.owner.as_ref() }.release_pending();
243 }
244}
245
246#[inline(always)]
248pub fn enter_preemption() -> PreemptionToken {
249 #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
250 {
251 unsafe { crate::register::enter_x86_preemption() };
254 let owner = crate::register::current_area()
255 .unwrap_or_else(|_| crate::register::fatal_register_invariant())
256 .runtime_anchor()
257 .preemption_state();
258 PreemptionToken::new(owner)
259 }
260
261 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
262 {
263 let current = unsafe { crate::current_context_unpinned() }
264 .unwrap_or_else(|_| crate::register::fatal_register_invariant());
265 let owner = unsafe { current.as_ref() }.preemption_state();
271 owner.enter();
272 PreemptionToken::new(owner)
273 }
274}
275
276pub fn preemption_snapshot(pin: &CpuPin<'_>) -> Result<PreemptionSnapshot, CpuLocalError> {
278 Ok(selected_state(pin)?.snapshot())
279}
280
281pub fn set_preemption_pending(pin: &CpuPin<'_>) -> Result<(), CpuLocalError> {
283 selected_state(pin)?.set_pending();
284 Ok(())
285}
286
287pub fn clear_preemption_pending(pin: &CpuPin<'_>) -> Result<(), CpuLocalError> {
289 selected_state(pin)?.clear_pending();
290 Ok(())
291}
292
293pub fn finish_preemption(token: PreemptionToken) -> PreemptionExit {
295 token.state().finish()
296}
297
298#[doc(hidden)]
314pub fn handoff_preemption_after_context_switch(
315 pin: &CpuPin<'_>,
316 token: PreemptionToken,
317) -> Result<PreemptionToken, CpuLocalError> {
318 let owner = selected_state(pin)?;
319 #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
320 {
321 Ok(token.handoff_after_context_switch(owner))
322 }
323
324 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
325 {
326 assert_eq!(
327 token.owner,
328 NonNull::from(owner),
329 "context-owned preemption token changed owner across a context switch"
330 );
331 Ok(token)
332 }
333}
334
335#[doc(hidden)]
340pub fn release_bootstrap_preemption(pin: &CpuPin<'_>) -> Result<(), CpuLocalError> {
341 selected_state(pin)?.release_bootstrap();
342 Ok(())
343}
344
345#[doc(hidden)]
353pub fn release_initial_context_preemption(pin: &CpuPin<'_>) -> Result<bool, CpuLocalError> {
354 #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
355 {
356 Ok(selected_state(pin)?.release_initial_switch())
357 }
358
359 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
360 {
361 let snapshot = selected_state(pin)?.snapshot();
362 assert_eq!(
363 snapshot.depth(),
364 0,
365 "new context-owned preemption state must start enabled"
366 );
367 Ok(false)
368 }
369}
370
371fn selected_state(pin: &CpuPin<'_>) -> Result<&'static PreemptionState, CpuLocalError> {
372 #[cfg(all(target_arch = "x86_64", not(feature = "host-test")))]
373 {
374 Ok(pin.area().runtime_anchor().preemption_state())
375 }
376
377 #[cfg(any(not(target_arch = "x86_64"), feature = "host-test"))]
378 {
379 let current = crate::current_context(pin)?;
380 Ok(unsafe { current.as_ref() }.preemption_state())
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 fn enter_on(state: &PreemptionState) -> PreemptionToken {
391 state.enter();
392 PreemptionToken::new(state)
393 }
394
395 #[test]
396 fn nested_and_final_exits_are_linear() {
397 let state = PreemptionState::new();
398 let outer = enter_on(&state);
399 let inner = enter_on(&state);
400
401 assert!(matches!(finish_preemption(inner), PreemptionExit::Nested));
402 assert_eq!(state.snapshot().depth(), 1);
403 assert!(matches!(finish_preemption(outer), PreemptionExit::Enabled));
404 assert_eq!(state.snapshot().depth(), 0);
405 }
406
407 #[test]
408 fn pending_exit_reserves_depth_until_release() {
409 let state = PreemptionState::new();
410 let token = enter_on(&state);
411 state.set_pending();
412
413 let PreemptionExit::Pending(pending) = finish_preemption(token) else {
414 panic!("final pending exit must retain its depth");
415 };
416 assert_eq!(state.snapshot().depth(), 1);
417 pending.release();
418 assert_eq!(state.snapshot().depth(), 0);
419 assert!(state.snapshot().is_pending());
420 }
421
422 #[test]
423 fn bootstrap_starts_disabled() {
424 let state = PreemptionState::bootstrap_disabled();
425 assert_eq!(state.snapshot().depth(), 1);
426 assert!(!state.snapshot().is_pending());
427 }
428
429 #[test]
430 fn initial_switch_discards_outgoing_pending_mirror() {
431 let state = PreemptionState::bootstrap_disabled();
432 state.set_pending();
433
434 assert!(state.release_initial_switch());
435 assert_eq!(state.snapshot().depth(), 0);
436 assert!(!state.snapshot().is_pending());
437 }
438
439 #[test]
440 #[should_panic(expected = "pending preemption no longer owns the final depth")]
441 fn pending_depth_cannot_be_consumed_twice() {
442 let state = PreemptionState(AtomicU32::new(1));
443 let first = PendingPreemption::new(&state);
444 let duplicate = PendingPreemption::new(&state);
445
446 first.release();
447 duplicate.release();
448 }
449
450 #[test]
451 #[should_panic(expected = "bootstrap preemption depth must be released exactly once")]
452 fn bootstrap_depth_cannot_be_released_twice() {
453 let state = PreemptionState::bootstrap_disabled();
454
455 state.release_bootstrap();
456 state.release_bootstrap();
457 }
458
459 #[test]
460 fn token_stays_bound_to_the_entry_owner() {
461 let original_context = PreemptionState::new();
462 let migrated_context = PreemptionState::new();
463 let original_cpu = PreemptionState::new();
464 let migrated_cpu = PreemptionState::new();
465
466 let context_token = enter_on(&original_context);
467 let cpu_token = enter_on(&original_cpu);
468 migrated_context.enter();
469 migrated_cpu.enter();
470
471 assert!(matches!(
472 finish_preemption(context_token),
473 PreemptionExit::Enabled
474 ));
475 assert!(matches!(
476 finish_preemption(cpu_token),
477 PreemptionExit::Enabled
478 ));
479 assert_eq!(original_context.snapshot().depth(), 0);
480 assert_eq!(original_cpu.snapshot().depth(), 0);
481 assert_eq!(migrated_context.snapshot().depth(), 1);
482 assert_eq!(migrated_cpu.snapshot().depth(), 1);
483 }
484
485 #[test]
486 fn cpu_owned_switch_token_handoff_follows_the_resumed_cpu() {
487 let original_cpu = PreemptionState::new();
488 let resumed_cpu = PreemptionState::new();
489
490 let suspended_switch = enter_on(&original_cpu);
491 assert!(original_cpu.release_initial_switch());
494 resumed_cpu.enter();
497
498 let resumed_switch = suspended_switch.handoff_after_context_switch(&resumed_cpu);
499 assert!(matches!(
500 finish_preemption(resumed_switch),
501 PreemptionExit::Enabled
502 ));
503 assert_eq!(original_cpu.snapshot().depth(), 0);
504 assert_eq!(resumed_cpu.snapshot().depth(), 0);
505 }
506
507 #[test]
508 fn malformed_raw_owner_is_rejected() {
509 assert!(unsafe { PreemptionToken::from_raw(1) }.is_none());
511 }
512}