1use alloc::{
2 alloc::{alloc, dealloc, handle_alloc_error},
3 boxed::Box,
4};
5use core::{
6 alloc::Layout,
7 cell::UnsafeCell,
8 iter::zip,
9 mem::MaybeUninit,
10 ptr::NonNull,
11 sync::atomic::{AtomicUsize, Ordering},
12};
13
14use ax_lazyinit::OnceLock;
15use ax_percpu::CpuPin;
16use ax_sync::PreemptGuard;
17
18use crate::{
19 boxed::ItemBox,
20 item::{Item, Registry},
21};
22
23const SCOPE_GATE_WRITER: usize = 1 << (usize::BITS - 1);
24const SCOPE_GATE_ACTIVE: usize = 1 << (usize::BITS - 2);
25const SCOPE_GATE_READERS: usize = SCOPE_GATE_ACTIVE - 1;
26
27struct ScopeGate {
32 state: AtomicUsize,
33}
34
35impl ScopeGate {
36 const fn new() -> Self {
37 Self {
38 state: AtomicUsize::new(0),
39 }
40 }
41
42 fn try_lock_shared(&self) -> bool {
43 let state = self.state.fetch_add(1, Ordering::Acquire);
49 self.finish_shared_reservation(state)
50 }
51
52 #[cfg(test)]
53 fn try_lock_shared_with(&self, interleave: impl FnOnce()) -> bool {
54 let state = self.state.fetch_add(1, Ordering::Acquire);
55 interleave();
56 self.finish_shared_reservation(state)
57 }
58
59 fn finish_shared_reservation(&self, state: usize) -> bool {
60 if state & SCOPE_GATE_WRITER != 0 || state & SCOPE_GATE_READERS == SCOPE_GATE_READERS {
61 self.state.fetch_sub(1, Ordering::Release);
62 return false;
63 }
64 true
65 }
66
67 fn try_lock_exclusive(&self) -> bool {
68 self.state
69 .compare_exchange(0, SCOPE_GATE_WRITER, Ordering::Acquire, Ordering::Relaxed)
70 .is_ok()
71 }
72
73 fn try_upgrade_active_shared_to_exclusive(&self) -> bool {
74 self.state
75 .compare_exchange(
76 SCOPE_GATE_ACTIVE,
77 SCOPE_GATE_WRITER,
78 Ordering::AcqRel,
79 Ordering::Acquire,
80 )
81 .is_ok()
82 }
83
84 unsafe fn downgrade_exclusive_to_active_shared(&self) {
85 self.state
86 .compare_exchange(
87 SCOPE_GATE_WRITER,
88 SCOPE_GATE_ACTIVE,
89 Ordering::Release,
90 Ordering::Relaxed,
91 )
92 .expect("scope downgrade requires one exclusive lease");
93 }
94
95 fn try_activate(&self) -> Result<(), ScopeActivationError> {
96 let mut state = self.state.load(Ordering::Acquire);
97 loop {
98 if state & SCOPE_GATE_WRITER != 0 {
99 return Err(ScopeActivationError::ExclusiveLease);
100 }
101 if state & SCOPE_GATE_ACTIVE != 0 {
102 return Err(ScopeActivationError::AlreadyActive);
103 }
104 match self.state.compare_exchange_weak(
105 state,
106 state | SCOPE_GATE_ACTIVE,
107 Ordering::AcqRel,
108 Ordering::Acquire,
109 ) {
110 Ok(_) => return Ok(()),
111 Err(observed) => state = observed,
112 }
113 }
114 }
115
116 fn deactivate(&self) {
117 let old = self.state.fetch_and(!SCOPE_GATE_ACTIVE, Ordering::Release);
118 assert_ne!(
119 old & SCOPE_GATE_ACTIVE,
120 0,
121 "scope deactivation without a matching activation"
122 );
123 }
124
125 fn is_active(&self) -> bool {
126 self.state.load(Ordering::Acquire) & SCOPE_GATE_ACTIVE != 0
127 }
128
129 unsafe fn unlock_shared(&self) {
130 let old = self.state.fetch_sub(1, Ordering::Release);
131 assert_ne!(
132 old & SCOPE_GATE_READERS,
133 0,
134 "scope shared unlock without a matching lease"
135 );
136 }
137
138 unsafe fn unlock_exclusive(&self) {
139 let old = self.state.fetch_and(SCOPE_GATE_READERS, Ordering::Release);
140 assert_ne!(
141 old & SCOPE_GATE_WRITER,
142 0,
143 "scope exclusive unlock without a matching lease"
144 );
145 }
146
147 fn is_locked(&self) -> bool {
148 self.state.load(Ordering::Acquire) != 0
149 }
150}
151
152#[cfg(test)]
153mod scope_gate_tests {
154 use std::sync::atomic::{AtomicBool, Ordering};
155
156 use super::{ScopeActivationError, ScopeCell, ScopeGate};
157
158 crate::scope_local! {
159 static GATE_TEST_ITEM: usize = 0;
160 }
161
162 #[test]
163 fn exclusive_attempt_is_bounded_by_live_readers() {
164 let gate = ScopeGate::new();
165 assert!(gate.try_lock_shared());
166 assert!(!gate.try_lock_exclusive());
167 assert!(gate.try_lock_shared());
168 unsafe {
170 gate.unlock_shared();
171 gate.unlock_shared();
172 }
173 assert!(gate.try_lock_exclusive());
174 unsafe { gate.unlock_exclusive() };
176 assert!(!gate.is_locked());
177 }
178
179 #[test]
180 fn active_mutation_publishes_writer_before_releasing_its_lease() {
181 let _retain_registry_entry = &GATE_TEST_ITEM;
182 let cell = ScopeCell::new();
183 assert_eq!(cell.try_acquire_active_lease(), Ok(()));
184 let barged = AtomicBool::new(false);
185
186 assert!(cell.try_withdraw_active_lease_for_writer(|| {
187 let admitted = cell.scope.inner().gate.try_lock_shared();
188 barged.store(admitted, Ordering::Relaxed);
189 if admitted {
190 unsafe { cell.scope.inner().unlock_shared() };
192 }
193 }));
194 unsafe { cell.scope.inner().unlock_exclusive() };
196
197 assert!(
198 !barged.load(Ordering::Relaxed),
199 "a new active lease entered after mutation began but before writer intent was visible"
200 );
201 }
202
203 #[test]
204 fn compatible_reader_interleave_does_not_report_busy() {
205 let gate = ScopeGate::new();
206 assert!(
207 gate.try_lock_shared_with(|| {
208 assert!(
209 gate.try_lock_shared(),
210 "the interleaved compatible reader must acquire its lease"
211 );
212 }),
213 "reader-count movement must not look like writer contention"
214 );
215 unsafe {
218 gate.unlock_shared();
219 gate.unlock_shared();
220 }
221 assert!(!gate.is_locked());
222 }
223
224 #[test]
225 fn activation_reports_an_exclusive_lease_separately() {
226 let cell = ScopeCell::new();
227 assert!(cell.scope.inner().gate.try_lock_exclusive());
228 assert_eq!(
229 cell.try_acquire_active_lease(),
230 Err(ScopeActivationError::ExclusiveLease)
231 );
232 unsafe { cell.scope.inner().gate.unlock_exclusive() };
234 }
235}
236
237pub struct Scope {
239 inner: Box<ScopeInner>,
240}
241
242struct ScopeInner {
243 gate: ScopeGate,
244 slots: NonNull<UnsafeCell<ItemSlot>>,
245}
246
247unsafe impl Send for Scope {}
250unsafe impl Sync for Scope {}
253
254impl Scope {
255 pub fn new() -> Self {
261 Self {
262 inner: Box::new(ScopeInner::new()),
263 }
264 }
265
266 fn inner(&self) -> &ScopeInner {
267 &self.inner
268 }
269
270 fn inner_ptr(&self) -> *const ScopeInner {
271 self.inner.as_ref()
272 }
273
274 pub(crate) fn read_item(&self, item: &'static Item) -> ScopeItemLease<'_> {
275 self.inner.read_item(item)
276 }
277
278 pub(crate) fn get_mut_unlocked(&mut self, item: &'static Item) -> &mut ItemBox {
279 unsafe { (&mut *self.inner.slot_ptr(item)).get_mut() }
282 }
283}
284
285impl Default for Scope {
286 fn default() -> Self {
287 Self::new()
288 }
289}
290
291impl ScopeInner {
292 fn len() -> usize {
293 Registry.len()
294 }
295
296 fn layout() -> Layout {
297 Layout::array::<UnsafeCell<ItemSlot>>(Self::len()).unwrap()
298 }
299
300 fn new() -> Self {
301 let layout = Self::layout();
302 let ptr = NonNull::new(unsafe { alloc(layout) })
303 .unwrap_or_else(|| handle_alloc_error(layout))
304 .cast();
305
306 let slice = unsafe {
307 core::slice::from_raw_parts_mut(ptr.cast::<MaybeUninit<_>>().as_ptr(), Registry.len())
308 };
309 for (item, d) in zip(&*Registry, slice) {
310 d.write(UnsafeCell::new(ItemSlot::new(item)));
311 }
312
313 Self {
314 gate: ScopeGate::new(),
315 slots: ptr,
316 }
317 }
318
319 fn try_lock_shared(&self) -> bool {
320 self.gate.try_lock_shared()
321 }
322
323 fn try_lock_exclusive(&self) -> bool {
324 self.gate.try_lock_exclusive()
325 }
326
327 unsafe fn unlock_shared(&self) {
328 unsafe { self.gate.unlock_shared() };
330 }
331
332 unsafe fn unlock_exclusive(&self) {
333 unsafe { self.gate.unlock_exclusive() };
335 }
336
337 pub(crate) fn read_item(&self, item: &'static Item) -> ScopeItemLease<'_> {
338 assert!(
339 self.try_lock_shared(),
340 "an exclusively borrowed scope cannot have a concurrent writer"
341 );
342 ScopeItemLease { inner: self, item }
343 }
344
345 fn get_shared(&self, item: &'static Item) -> &ItemBox {
346 let index = item.index();
347 unsafe { (&*self.slots.add(index).as_ref().get()).get() }
350 }
351
352 fn try_get_shared(&self, item: &'static Item) -> Option<&ItemBox> {
353 let index = item.index();
354 unsafe { (&*self.slots.add(index).as_ref().get()).try_get() }
357 }
358
359 fn slot_ptr(&self, item: &'static Item) -> *mut ItemSlot {
360 let index = item.index();
361 unsafe { self.slots.add(index).as_ref().get() }
364 }
365}
366
367pub(crate) struct ScopeItemLease<'scope> {
368 inner: &'scope ScopeInner,
369 item: &'static Item,
370}
371
372impl ScopeItemLease<'_> {
373 pub(crate) fn item(&self) -> &ItemBox {
374 self.inner.get_shared(self.item)
375 }
376}
377
378impl Drop for ScopeItemLease<'_> {
379 fn drop(&mut self) {
380 unsafe { self.inner.unlock_shared() };
382 }
383}
384
385impl Drop for ScopeInner {
386 fn drop(&mut self) {
387 let ptr = NonNull::slice_from_raw_parts(self.slots, Self::len());
388 unsafe {
389 ptr.drop_in_place();
390 dealloc(self.slots.cast().as_ptr(), Self::layout());
391 }
392 }
393}
394
395pub struct ScopeCell {
402 scope: Scope,
403}
404
405#[derive(Clone, Copy, Debug, Eq, PartialEq)]
407pub struct ScopeCellBusy;
408
409impl core::fmt::Display for ScopeCellBusy {
410 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
411 formatter.write_str("scope cell is busy")
412 }
413}
414
415impl core::error::Error for ScopeCellBusy {}
416
417#[derive(Clone, Copy, Debug, Eq, PartialEq)]
419pub enum ScopeActivationError {
420 ExclusiveLease,
422 AlreadyActive,
424}
425
426impl core::fmt::Display for ScopeActivationError {
427 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
428 match self {
429 Self::ExclusiveLease => formatter.write_str("scope cell has an exclusive lease"),
430 Self::AlreadyActive => {
431 formatter.write_str("scope cell already has a scheduler activation")
432 }
433 }
434 }
435}
436
437impl core::error::Error for ScopeActivationError {}
438
439impl ScopeCell {
440 pub fn new() -> Self {
442 Self::from_scope(Scope::new())
443 }
444
445 pub fn from_scope(scope: Scope) -> Self {
447 Self { scope }
448 }
449
450 pub fn try_read(&self) -> Result<ScopeCellReadGuard<'_>, ScopeCellBusy> {
453 let preempt = PreemptGuard::new();
454 if !self.scope.inner().gate.try_lock_shared() {
455 return Err(ScopeCellBusy);
456 }
457 Ok(ScopeCellReadGuard {
458 scope: &self.scope,
459 _preempt: preempt,
460 })
461 }
462
463 pub fn try_write(&self) -> Result<ScopeCellWriteGuard<'_>, ScopeCellBusy> {
466 let preempt = PreemptGuard::new();
467 let inner = self.scope.inner();
468 if !inner.try_lock_exclusive() {
469 return Err(ScopeCellBusy);
470 }
471 Ok(ScopeCellWriteGuard {
472 inner,
473 _preempt: Some(preempt),
474 owns_exclusive: true,
475 })
476 }
477
478 pub unsafe fn try_activate_pinned(&self, pin: &CpuPin<'_>) -> Result<(), ScopeActivationError> {
493 assert_eq!(
494 ActiveScope::current_scope_ptr_pinned(pin),
495 0,
496 "scope activation requires the global scope to be current"
497 );
498 self.try_acquire_active_lease()?;
499 unsafe { ActiveScope::set_pinned(&self.scope, pin) };
502 Ok(())
503 }
504
505 pub unsafe fn deactivate_pinned(&self, pin: &CpuPin<'_>) {
512 assert_eq!(
513 ActiveScope::current_scope_ptr_pinned(pin),
514 self.scope_ptr(),
515 "scope deactivation does not match the active scope"
516 );
517 unsafe { ActiveScope::set_global_pinned(pin) };
520 self.release_active_lease();
521 }
522
523 pub unsafe fn try_with_active_mut_pinned<R>(
535 &self,
536 pin: &CpuPin<'_>,
537 operation: impl for<'scope> FnOnce(&'scope mut ScopeCellWriteGuard<'_>) -> R,
538 ) -> Result<R, ScopeCellBusy> {
539 assert_eq!(
540 ActiveScope::current_scope_ptr_pinned(pin),
541 self.scope_ptr(),
542 "active scope mutation does not match the current scope"
543 );
544 if !self.try_withdraw_active_lease_for_writer(|| {}) {
545 return Err(ScopeCellBusy);
546 }
547 unsafe { ActiveScope::set_global_pinned(pin) };
549 let inner = self.scope.inner();
550 let mut mutation = ActiveScopeMutation {
551 cell: self,
552 pin,
553 writer: Some(ScopeCellWriteGuard {
554 inner,
555 _preempt: None,
556 owns_exclusive: true,
557 }),
558 };
559 let result = operation(mutation.writer());
560 drop(mutation);
561 Ok(result)
562 }
563
564 fn scope_ptr(&self) -> usize {
565 self.scope.inner_ptr().expose_provenance()
566 }
567
568 fn try_acquire_active_lease(&self) -> Result<(), ScopeActivationError> {
569 self.scope.inner().gate.try_activate()
570 }
571
572 fn release_active_lease(&self) {
573 self.scope.inner().gate.deactivate();
574 }
575
576 fn try_withdraw_active_lease_for_writer(&self, writer_pending: impl FnOnce()) -> bool {
577 if !self
578 .scope
579 .inner()
580 .gate
581 .try_upgrade_active_shared_to_exclusive()
582 {
583 return false;
584 }
585 writer_pending();
586 true
587 }
588
589 fn restore_active_lease_from_writer(&self, pin: &CpuPin<'_>) {
590 unsafe {
594 ActiveScope::set_pinned(&self.scope, pin);
595 self.scope
596 .inner()
597 .gate
598 .downgrade_exclusive_to_active_shared();
599 }
600 }
601}
602
603struct ActiveScopeMutation<'cell, 'pin_ref, 'cpu> {
604 cell: &'cell ScopeCell,
605 pin: &'pin_ref CpuPin<'cpu>,
606 writer: Option<ScopeCellWriteGuard<'cell>>,
607}
608
609impl<'cell> ActiveScopeMutation<'cell, '_, '_> {
610 fn writer(&mut self) -> &mut ScopeCellWriteGuard<'cell> {
611 self.writer
612 .as_mut()
613 .expect("active scope mutation writer must be present")
614 }
615}
616
617impl Drop for ActiveScopeMutation<'_, '_, '_> {
618 fn drop(&mut self) {
619 let mut writer = self
620 .writer
621 .take()
622 .expect("active scope mutation writer must be present");
623 self.cell.restore_active_lease_from_writer(self.pin);
624 writer.owns_exclusive = false;
625 }
626}
627
628impl Default for ScopeCell {
629 fn default() -> Self {
630 Self::new()
631 }
632}
633
634impl Drop for ScopeCell {
635 fn drop(&mut self) {
636 assert!(
637 !self.scope.inner().gate.is_active(),
638 "cannot drop a scope with live scheduler activations"
639 );
640 assert!(
641 !self.scope.inner().gate.is_locked(),
642 "cannot drop a locked scope"
643 );
644 }
645}
646
647pub struct ScopeCellReadGuard<'a> {
649 scope: &'a Scope,
650 _preempt: PreemptGuard,
651}
652
653impl ScopeCellReadGuard<'_> {
654 pub(crate) fn get(&self, item: &'static Item) -> &ItemBox {
655 self.scope.inner().get_shared(item)
659 }
660}
661
662impl Drop for ScopeCellReadGuard<'_> {
663 fn drop(&mut self) {
664 unsafe { self.scope.inner().unlock_shared() };
667 }
668}
669
670pub struct ScopeCellWriteGuard<'a> {
676 inner: &'a ScopeInner,
677 _preempt: Option<PreemptGuard>,
678 owns_exclusive: bool,
679}
680
681impl ScopeCellWriteGuard<'_> {
682 pub(crate) fn get_mut(&mut self, item: &'static Item) -> &mut ItemBox {
683 unsafe { (&mut *self.inner.slot_ptr(item)).get_mut() }
686 }
687}
688
689impl Drop for ScopeCellWriteGuard<'_> {
690 fn drop(&mut self) {
691 if !self.owns_exclusive {
692 return;
693 }
694 unsafe { self.inner.unlock_exclusive() };
697 }
698}
699
700struct ItemSlot {
701 value: ItemBox,
702}
703
704impl ItemSlot {
705 fn new(item: &'static Item) -> Self {
706 Self {
707 value: ItemBox::new(item),
708 }
709 }
710
711 fn get(&self) -> &ItemBox {
712 &self.value
713 }
714
715 fn get_mut(&mut self) -> &mut ItemBox {
716 &mut self.value
717 }
718
719 fn try_get(&self) -> Option<&ItemBox> {
720 Some(&self.value)
721 }
722}
723
724static GLOBAL_SCOPE: OnceLock<Scope> = OnceLock::new();
725static GLOBAL_SCOPE_STATE: AtomicUsize = AtomicUsize::new(GlobalScopeState::Uninitialized as usize);
726
727#[derive(Clone, Copy, Debug, Eq, PartialEq)]
728#[repr(usize)]
729enum GlobalScopeState {
730 Uninitialized,
731 Ready,
732}
733
734#[derive(Clone, Copy, Debug, Eq, PartialEq)]
735enum GlobalScopeAction {
736 Ready,
737 Recursive,
738 Claim,
739 Wait,
740}
741
742fn global_scope_action(state: usize, owner_context: usize) -> GlobalScopeAction {
743 if state == GlobalScopeState::Ready as usize {
744 GlobalScopeAction::Ready
745 } else if state == owner_context {
746 GlobalScopeAction::Recursive
747 } else if state == GlobalScopeState::Uninitialized as usize {
748 GlobalScopeAction::Claim
749 } else {
750 GlobalScopeAction::Wait
751 }
752}
753
754struct GlobalInitialization<'state> {
755 state: &'state AtomicUsize,
756 owner_context: usize,
757 published: bool,
758}
759
760impl<'state> GlobalInitialization<'state> {
761 fn begin(state: &'state AtomicUsize, owner_context: usize) -> Self {
762 Self {
763 state,
764 owner_context,
765 published: false,
766 }
767 }
768
769 fn publish(mut self, scope: Scope) {
770 GLOBAL_SCOPE.call_once(|| scope);
771 self.state
772 .store(GlobalScopeState::Ready as usize, Ordering::Release);
773 self.published = true;
774 }
775}
776
777impl Drop for GlobalInitialization<'_> {
778 fn drop(&mut self) {
779 if !self.published {
780 let _ = self.state.compare_exchange(
781 self.owner_context,
782 GlobalScopeState::Uninitialized as usize,
783 Ordering::Release,
784 Ordering::Relaxed,
785 );
786 }
787 }
788}
789
790#[ax_percpu::def_percpu]
791pub(crate) static ACTIVE_SCOPE_PTR: usize = 0;
792
793pub struct ActiveScope;
795
796impl ActiveScope {
797 pub unsafe fn set(scope: &Scope) {
805 let _guard = PreemptGuard::new();
806 unsafe {
809 ax_percpu::with_cpu_pin(|pin| Self::set_pinned(scope, pin))
810 .expect("scope-local access requires an installed CPU area")
811 };
812 }
813
814 pub unsafe fn set_pinned(scope: &Scope, pin: &CpuPin<'_>) {
825 ACTIVE_SCOPE_PTR.write_current(pin, scope.inner_ptr().expose_provenance());
826 }
827
828 pub unsafe fn set_global() {
836 let _guard = PreemptGuard::new();
837 unsafe {
839 ax_percpu::with_cpu_pin(|pin| Self::set_global_pinned(pin))
840 .expect("scope-local access requires an installed CPU area")
841 };
842 }
843
844 pub unsafe fn set_global_pinned(pin: &CpuPin<'_>) {
851 ACTIVE_SCOPE_PTR.write_current(pin, 0);
852 }
853
854 pub fn is_global() -> bool {
856 let _guard = PreemptGuard::new();
857 unsafe { ax_percpu::with_cpu_pin(Self::is_global_pinned) }
859 .expect("scope-local access requires an installed CPU area")
860 }
861
862 pub fn is_global_pinned(pin: &CpuPin<'_>) -> bool {
864 ACTIVE_SCOPE_PTR.read_current(pin) == 0
865 }
866
867 pub fn is_pinned(scope: &Scope, pin: &CpuPin<'_>) -> bool {
873 Self::current_scope_ptr_pinned(pin) == scope.inner_ptr().expose_provenance()
874 }
875
876 pub(crate) fn with_item<'pin, R>(
877 item: &'static Item,
878 pin: &CpuPin<'pin>,
879 operation: impl for<'access> FnOnce(&'access ItemBox) -> R,
880 ) -> R {
881 operation(Self::current_inner(pin).get_shared(item))
882 }
883
884 pub(crate) fn try_with_item<'pin, R>(
885 item: &'static Item,
886 pin: &CpuPin<'pin>,
887 operation: impl for<'access> FnOnce(&'access ItemBox) -> R,
888 ) -> Option<R> {
889 Self::try_current_inner(pin)?
890 .try_get_shared(item)
891 .map(operation)
892 }
893
894 fn current_inner<'pin>(pin: &CpuPin<'pin>) -> &'pin ScopeInner {
895 let ptr = ACTIVE_SCOPE_PTR.read_current(pin);
896 let ptr = if ptr == 0 {
897 NonNull::from_ref(
898 GLOBAL_SCOPE
899 .get()
900 .expect("scope-local global scope must be initialized")
901 .inner(),
902 )
903 } else {
904 NonNull::new(core::ptr::with_exposed_provenance_mut::<ScopeInner>(ptr))
905 .expect("nonzero active scope address must reconstruct a pointer")
906 };
907 unsafe { ptr.as_ref() }
912 }
913
914 fn try_current_inner<'pin>(pin: &CpuPin<'pin>) -> Option<&'pin ScopeInner> {
915 let ptr = ACTIVE_SCOPE_PTR.read_current(pin);
916 let ptr = if ptr == 0 {
917 NonNull::from_ref(GLOBAL_SCOPE.get()?.inner())
918 } else {
919 NonNull::new(core::ptr::with_exposed_provenance_mut::<ScopeInner>(ptr))?
920 };
921 Some(unsafe { ptr.as_ref() })
925 }
926
927 pub(crate) fn initialize_global() {
928 let owner_context = current_context_identity();
929 loop {
930 match global_scope_action(GLOBAL_SCOPE_STATE.load(Ordering::Acquire), owner_context) {
931 GlobalScopeAction::Ready => return,
932 GlobalScopeAction::Recursive => {
933 panic!("scope-local global scope initialization is already in progress")
934 }
935 GlobalScopeAction::Claim => {
936 if GLOBAL_SCOPE_STATE
937 .compare_exchange(
938 GlobalScopeState::Uninitialized as usize,
939 owner_context,
940 Ordering::AcqRel,
941 Ordering::Acquire,
942 )
943 .is_ok()
944 {
945 let initialization =
946 GlobalInitialization::begin(&GLOBAL_SCOPE_STATE, owner_context);
947 initialization.publish(Scope::new());
948 return;
949 }
950 }
951 GlobalScopeAction::Wait => core::hint::spin_loop(),
952 }
953 }
954 }
955
956 fn current_scope_ptr_pinned(pin: &CpuPin<'_>) -> usize {
957 ACTIVE_SCOPE_PTR.read_current(pin)
958 }
959}
960
961#[cfg(test)]
962mod global_scope_state_tests {
963 use core::sync::atomic::{AtomicUsize, Ordering};
964
965 use super::{GlobalInitialization, GlobalScopeAction, GlobalScopeState, global_scope_action};
966
967 #[test]
968 fn initialization_action_distinguishes_owner_and_competing_contexts() {
969 let owner = 17;
970
971 assert_eq!(
972 global_scope_action(GlobalScopeState::Uninitialized as usize, owner),
973 GlobalScopeAction::Claim
974 );
975 assert_eq!(
976 global_scope_action(owner, owner),
977 GlobalScopeAction::Recursive
978 );
979 assert_eq!(global_scope_action(29, owner), GlobalScopeAction::Wait);
980 assert_eq!(
981 global_scope_action(GlobalScopeState::Ready as usize, owner),
982 GlobalScopeAction::Ready
983 );
984 }
985
986 #[test]
987 fn abandoned_initialization_restores_the_retryable_state() {
988 let owner = 17;
989 let state = AtomicUsize::new(owner);
990
991 drop(GlobalInitialization::begin(&state, owner));
992
993 assert_eq!(
994 state.load(Ordering::Acquire),
995 GlobalScopeState::Uninitialized as usize
996 );
997 assert_eq!(
998 global_scope_action(state.load(Ordering::Acquire), owner),
999 GlobalScopeAction::Claim
1000 );
1001 }
1002
1003 #[test]
1004 fn recursive_owner_unwind_restores_a_retryable_initialization() {
1005 let owner = 17;
1006 let state = AtomicUsize::new(GlobalScopeState::Uninitialized as usize);
1007 assert!(
1008 state
1009 .compare_exchange(
1010 GlobalScopeState::Uninitialized as usize,
1011 owner,
1012 Ordering::AcqRel,
1013 Ordering::Acquire,
1014 )
1015 .is_ok()
1016 );
1017
1018 let initialization = GlobalInitialization::begin(&state, owner);
1019 assert_eq!(
1020 global_scope_action(state.load(Ordering::Acquire), owner),
1021 GlobalScopeAction::Recursive
1022 );
1023 drop(initialization);
1024
1025 assert_eq!(
1026 global_scope_action(state.load(Ordering::Acquire), owner),
1027 GlobalScopeAction::Claim
1028 );
1029 }
1030}
1031
1032fn current_context_identity() -> usize {
1033 let _guard = PreemptGuard::new();
1034 let context = unsafe {
1038 ax_percpu::with_cpu_pin(|pin| {
1039 cpu_local::current_context(pin)
1040 .expect("scope-local current context must be valid")
1041 .as_ptr() as usize
1042 })
1043 .expect("scope-local access requires an installed CPU area")
1044 };
1045 assert!(
1046 context > GlobalScopeState::Ready as usize,
1047 "scope-local initialization requires a valid current context"
1048 );
1049 context
1050}