Skip to main content

scope_local/
scope.rs

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
27/// Bounded raw gate for scheduler-owned scope leases.
28///
29/// Scheduler and IRQ-adjacent callers only attempt one state transition. They
30/// never wait for a reader or writer while preemption is disabled.
31struct 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        // Reserve one reader count before inspecting writer ownership. Unlike
44        // a load/CAS pair, this cannot report a false conflict merely because
45        // another compatible reader changed the count. A writer may coexist
46        // only with transient reservations whose callers observe its bit and
47        // immediately roll them back without touching protected state.
48        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        // SAFETY: the test acquired exactly two shared counts.
169        unsafe {
170            gate.unlock_shared();
171            gate.unlock_shared();
172        }
173        assert!(gate.try_lock_exclusive());
174        // SAFETY: the test acquired the exclusive count above.
175        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                // SAFETY: this callback acquired exactly one shared count.
191                unsafe { cell.scope.inner().unlock_shared() };
192            }
193        }));
194        // SAFETY: the production transition returned with the exclusive count.
195        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        // SAFETY: the nested acquisition and the outer acquisition each own
216        // one shared count when the bounded operation succeeds.
217        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        // SAFETY: the test acquired the sole exclusive lease above.
233        unsafe { cell.scope.inner().gate.unlock_exclusive() };
234    }
235}
236
237/// A scope is a collection of items.
238pub struct Scope {
239    inner: Box<ScopeInner>,
240}
241
242struct ScopeInner {
243    gate: ScopeGate,
244    slots: NonNull<UnsafeCell<ItemSlot>>,
245}
246
247// SAFETY: the public registration path admits only `Send + Sync + 'static`
248// payloads, and ownership of every initialized slot moves with the Scope.
249unsafe impl Send for Scope {}
250// SAFETY: shared payload access is admitted only for `Sync` values and is
251// serialized against mutation by `gate`.
252unsafe impl Sync for Scope {}
253
254impl Scope {
255    /// Creates a new namespace and eagerly initializes every registered item.
256    ///
257    /// Initializers run in the caller's ordinary context. Once this function
258    /// returns, pinned access to the scope performs no allocation or lazy
259    /// initialization.
260    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        // SAFETY: `&mut Scope` gives exclusive ownership of this namespace and
280        // therefore of the selected UnsafeCell-backed slot.
281        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        // SAFETY: forwarded to the caller, which must own one shared count.
329        unsafe { self.gate.unlock_shared() };
330    }
331
332    unsafe fn unlock_exclusive(&self) {
333        // SAFETY: forwarded to the caller, which must own the exclusive count.
334        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        // SAFETY: callers either own a shared gate count or access the
348        // immutable global scope. Both cases exclude the only mutable path.
349        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        // SAFETY: the same shared-lease or immutable-global invariant as
355        // `get_shared` applies.
356        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        // SAFETY: address calculation does not create a reference. Callers must
362        // own either `&mut Scope` or the exclusive gate before dereferencing.
363        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        // SAFETY: construction acquired exactly one shared count.
381        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
395/// A scope whose scheduler binding owns one shared lease while active.
396///
397/// Scheduler hooks acquire the lease before publishing the pinned pointer and
398/// release it after clearing that pointer. Scope-local hot reads therefore need
399/// no per-access lock. Every contended operation returns [`ScopeCellBusy`]
400/// without waiting in a non-preemptible context.
401pub struct ScopeCell {
402    scope: Scope,
403}
404
405/// A bounded scope-cell lease could not be acquired immediately.
406#[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/// A scheduler activation could not acquire its unique scope lease.
418#[derive(Clone, Copy, Debug, Eq, PartialEq)]
419pub enum ScopeActivationError {
420    /// An exclusive scope mutation currently owns the raw gate.
421    ExclusiveLease,
422    /// Another CPU already owns the scheduler activation.
423    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    /// Creates a managed scope with no active scheduler binding.
441    pub fn new() -> Self {
442        Self::from_scope(Scope::new())
443    }
444
445    /// Wraps an existing scope with a managed scheduler binding.
446    pub fn from_scope(scope: Scope) -> Self {
447        Self { scope }
448    }
449
450    /// Attempts to acquire an ordinary shared scope reference while preventing
451    /// migration. It returns immediately when an exclusive lease is active.
452    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    /// Attempts to acquire an ordinary exclusive scope reference while
464    /// preventing migration. It returns immediately while any lease is live.
465    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    /// Attempts to install this scope for the pinned CPU and retain its sole
479    /// scheduler lease.
480    ///
481    /// This operation does not enter a new IRQ or preemption context, so it is
482    /// suitable for a scheduler switch-in hook that already owns its CPU-local
483    /// baton.
484    ///
485    /// # Safety
486    ///
487    /// Only one CPU may activate a cell at a time. On success, the caller must
488    /// keep this `ScopeCell` alive, retain the current CPU pin, and invoke
489    /// [`deactivate_pinned`](Self::deactivate_pinned) exactly once before
490    /// another scope is installed or the cell can be dropped. The scheduler
491    /// must run both hooks while holding its switch baton.
492    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        // SAFETY: the caller contract keeps the cell live until deactivation,
500        // and the retained shared lease excludes slot mutation.
501        unsafe { ActiveScope::set_pinned(&self.scope, pin) };
502        Ok(())
503    }
504
505    /// Clears this scope from the pinned CPU and retires its active identity.
506    ///
507    /// # Safety
508    ///
509    /// The current CPU must own exactly one activation previously established
510    /// by [`try_activate_pinned`](Self::try_activate_pinned) for this cell.
511    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        // SAFETY: the caller owns this managed activation and therefore may
518        // clear its raw per-CPU pointer.
519        unsafe { ActiveScope::set_global_pinned(pin) };
520        self.release_active_lease();
521    }
522
523    /// Mutates the calling task's active scope.
524    ///
525    /// The active shared lease is atomically upgraded to the writer state and
526    /// restored before this function returns. A remote reader or a second CPU
527    /// activation returns [`ScopeCellBusy`] instead of making the caller spin.
528    ///
529    /// # Safety
530    ///
531    /// This cell must have exactly one activation, owned by the current CPU,
532    /// and `pin` must remain valid for the complete call. The caller must
533    /// prevent reentrant scope-local access while `operation` runs.
534    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        // SAFETY: the caller owns the sole activation verified above.
548        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        // SAFETY: the exclusive lease keeps the scope stable while the pinned
591        // identity is restored. Downgrading publishes its shared lease before
592        // the caller may re-enter scope-local access.
593        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
647/// Shared ordinary-access guard returned by [`ScopeCell::try_read`].
648pub 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        // This guard already owns the shared count. Reacquiring it here could
656        // deadlock behind a pending upgradable writer while retaining the
657        // original count.
658        self.scope.inner().get_shared(item)
659    }
660}
661
662impl Drop for ScopeCellReadGuard<'_> {
663    fn drop(&mut self) {
664        // SAFETY: this guard owns one raw shared count. Its preemption guard is
665        // dropped afterwards, preserving raw unlock -> preempt exit ordering.
666        unsafe { self.scope.inner().unlock_shared() };
667    }
668}
669
670/// Slot-level exclusive guard returned by [`ScopeCell::try_write`].
671///
672/// It intentionally does not dereference to `Scope`: active CPUs may retain a
673/// shared identity for the stable inner object, so writers receive only the
674/// item-level mutation capability authorized by the exclusive gate.
675pub 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        // SAFETY: this guard owns the writer-preferred exclusive count. Slots
684        // are UnsafeCell-backed so no `&mut Scope` aliases a published inner.
685        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        // SAFETY: this guard owns the raw exclusive count. Its preemption guard
695        // is dropped afterwards, preserving raw unlock -> preempt exit ordering.
696        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
793/// Currently active scope.
794pub struct ActiveScope;
795
796impl ActiveScope {
797    /// Sets the active scope pointer to the given scope.
798    ///
799    /// # Safety
800    ///
801    /// The caller must ensure that the provided `scope` reference is valid for
802    /// the duration in which it is set as the active scope, and that no data
803    /// races or aliasing violations occur.
804    pub unsafe fn set(scope: &Scope) {
805        let _guard = PreemptGuard::new();
806        // SAFETY: the public contract supplies the scope lifetime and aliasing
807        // invariants; PreemptGuard keeps the guarded callback on this CPU.
808        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    /// Sets the active scope while borrowing an existing CPU pin.
815    ///
816    /// This variant performs no context transition and is therefore suitable
817    /// for scheduler and hard-IRQ code that already owns a pin.
818    ///
819    /// # Safety
820    ///
821    /// The caller must keep `scope` alive for every current-CPU access until a
822    /// later [`Self::set_global_pinned`] or pinned replacement, and must prevent
823    /// concurrent mutable access to that scope's items.
824    pub unsafe fn set_pinned(scope: &Scope, pin: &CpuPin<'_>) {
825        ACTIVE_SCOPE_PTR.write_current(pin, scope.inner_ptr().expose_provenance());
826    }
827
828    /// Set the active scope to the global scope.
829    ///
830    /// # Safety
831    ///
832    /// The caller must own the current raw activation. In particular, this
833    /// function must not clear a scheduler-managed [`ScopeCell`] activation;
834    /// that activation must be released through [`ScopeCell::deactivate_pinned`].
835    pub unsafe fn set_global() {
836        let _guard = PreemptGuard::new();
837        // SAFETY: forwarded caller ownership applies to this pinned CPU.
838        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    /// Sets the active scope to the global scope under an existing CPU pin.
845    ///
846    /// # Safety
847    ///
848    /// The caller must own the current raw activation and must not bypass a
849    /// scheduler-managed [`ScopeCell`] activation.
850    pub unsafe fn set_global_pinned(pin: &CpuPin<'_>) {
851        ACTIVE_SCOPE_PTR.write_current(pin, 0);
852    }
853
854    /// Returns true if the active scope is the global scope.
855    pub fn is_global() -> bool {
856        let _guard = PreemptGuard::new();
857        // SAFETY: PreemptGuard prevents migration for the complete callback.
858        unsafe { ax_percpu::with_cpu_pin(Self::is_global_pinned) }
859            .expect("scope-local access requires an installed CPU area")
860    }
861
862    /// Returns true if the active scope is global under an existing CPU pin.
863    pub fn is_global_pinned(pin: &CpuPin<'_>) -> bool {
864        ACTIVE_SCOPE_PTR.read_current(pin) == 0
865    }
866
867    /// Returns whether `scope` is the active scope selected by `pin`.
868    ///
869    /// This does not acquire a scope lease. Callers must already own the
870    /// scheduler or task-local serialization that keeps the selected scope
871    /// alive and excludes mutation for the duration of their operation.
872    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        // SAFETY: set_pinned's contract keeps the selected scope live. A
908        // scheduler-managed scope retains one shared lease for the activation;
909        // the global scope is immutable after publication. The borrow is
910        // shortened to the CPU pin lifetime.
911        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        // SAFETY: the same scope lifetime and pinning invariants as current_inner
922        // apply. Unlike that path, GLOBAL_SCOPE.get never runs an
923        // initializer and therefore remains valid in hard-IRQ context.
924        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    // SAFETY: the guard keeps the architecture-selected current context stable
1035    // while its header address is acquired. That header stays pinned for the
1036    // context lifetime, so the identity survives later migration.
1037    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}