Skip to main content

scope_local/
scope.rs

1use alloc::alloc::{alloc, dealloc, handle_alloc_error};
2use core::{alloc::Layout, iter::zip, mem::MaybeUninit, ptr::NonNull};
3
4use ax_kernel_guard::NoPreempt;
5use ax_percpu::CpuPin;
6use spin::Once;
7
8use crate::{
9    boxed::ItemBox,
10    item::{Item, Registry},
11};
12
13/// A scope is a collection of items.
14pub struct Scope {
15    ptr: NonNull<ItemSlot>,
16}
17
18unsafe impl Send for Scope {}
19unsafe impl Sync for Scope {}
20
21impl Scope {
22    fn len() -> usize {
23        Registry.len()
24    }
25
26    fn layout() -> Layout {
27        Layout::array::<ItemSlot>(Self::len()).unwrap()
28    }
29
30    /// Create a new namespace with all resources initialized as their default
31    /// value.
32    pub fn new() -> Self {
33        let layout = Self::layout();
34        let ptr = NonNull::new(unsafe { alloc(layout) })
35            .unwrap_or_else(|| handle_alloc_error(layout))
36            .cast();
37
38        let slice = unsafe {
39            core::slice::from_raw_parts_mut(ptr.cast::<MaybeUninit<_>>().as_ptr(), Registry.len())
40        };
41        for (item, d) in zip(&*Registry, slice) {
42            d.write(ItemSlot::new(item));
43        }
44
45        Self { ptr }
46    }
47
48    pub(crate) fn get(&self, item: &'static Item) -> &ItemBox {
49        let index = item.index();
50        unsafe { self.ptr.add(index).as_ref() }.get()
51    }
52
53    pub(crate) fn get_mut(&mut self, item: &'static Item) -> &mut ItemBox {
54        let index = item.index();
55        unsafe { self.ptr.add(index).as_mut() }.get_mut()
56    }
57}
58
59impl Default for Scope {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65impl Drop for Scope {
66    fn drop(&mut self) {
67        let ptr = NonNull::slice_from_raw_parts(self.ptr, Self::len());
68        unsafe {
69            ptr.drop_in_place();
70            dealloc(self.ptr.cast().as_ptr(), Self::layout());
71        }
72    }
73}
74
75struct ItemSlot {
76    item: &'static Item,
77    value: Once<ItemBox>,
78}
79
80impl ItemSlot {
81    fn new(item: &'static Item) -> Self {
82        Self {
83            item,
84            value: Once::new(),
85        }
86    }
87
88    fn get(&self) -> &ItemBox {
89        self.value.call_once(|| ItemBox::new(self.item))
90    }
91
92    fn get_mut(&mut self) -> &mut ItemBox {
93        if !self.value.is_completed() {
94            let item = self.item;
95            self.value.call_once(|| ItemBox::new(item));
96        }
97        self.value
98            .get_mut()
99            .expect("scope-local item must be initialized")
100    }
101
102    fn try_get(&self) -> Option<&ItemBox> {
103        self.value.get()
104    }
105}
106
107static GLOBAL_SCOPE: Once<Scope> = Once::new();
108
109#[ax_percpu::def_percpu]
110pub(crate) static ACTIVE_SCOPE_PTR: usize = 0;
111
112/// Currently active scope.
113pub struct ActiveScope;
114
115impl ActiveScope {
116    /// Sets the active scope pointer to the given scope.
117    ///
118    /// # Safety
119    ///
120    /// The caller must ensure that the provided `scope` reference is valid for
121    /// the duration in which it is set as the active scope, and that no data
122    /// races or aliasing violations occur.
123    pub unsafe fn set(scope: &Scope) {
124        let _guard = NoPreempt::new();
125        // SAFETY: the guard prevents migration while the per-CPU pointer is
126        // selected and updated.
127        unsafe {
128            ax_percpu::with_cpu_pin(|pin| Self::set_pinned(scope, pin))
129                .expect("scope-local access requires an installed CPU area")
130        };
131    }
132
133    /// Sets the active scope under an existing CPU pin.
134    ///
135    /// # Safety
136    ///
137    /// `scope` must remain alive until a later pinned replacement or reset.
138    pub unsafe fn set_pinned(scope: &Scope, pin: &CpuPin<'_>) {
139        ACTIVE_SCOPE_PTR.write_current(pin, scope.ptr.addr().get());
140    }
141
142    /// Set the active scope to the global scope.
143    pub fn set_global() {
144        let _guard = NoPreempt::new();
145        // SAFETY: the guard prevents migration while the per-CPU pointer is
146        // cleared.
147        unsafe {
148            ax_percpu::with_cpu_pin(Self::set_global_pinned)
149                .expect("scope-local access requires an installed CPU area")
150        };
151    }
152
153    /// Sets the active scope to global under an existing CPU pin.
154    pub fn set_global_pinned(pin: &CpuPin<'_>) {
155        ACTIVE_SCOPE_PTR.write_current(pin, 0);
156    }
157
158    /// Returns true if the active scope is the global scope.
159    pub fn is_global() -> bool {
160        let _guard = NoPreempt::new();
161        // SAFETY: the guard prevents migration for the complete read.
162        unsafe {
163            ax_percpu::with_cpu_pin(Self::is_global_pinned)
164                .expect("scope-local access requires an installed CPU area")
165        }
166    }
167
168    /// Returns whether the active scope is global under an existing pin.
169    pub fn is_global_pinned(pin: &CpuPin<'_>) -> bool {
170        ACTIVE_SCOPE_PTR.read_current(pin) == 0
171    }
172
173    pub(crate) fn with_item<R>(
174        item: &'static Item,
175        pin: &CpuPin<'_>,
176        operation: impl for<'access> FnOnce(&'access ItemBox) -> R,
177    ) -> R {
178        let ptr = ACTIVE_SCOPE_PTR.read_current(pin);
179        let ptr = NonNull::new(ptr as *mut ItemSlot)
180            .unwrap_or_else(|| GLOBAL_SCOPE.call_once(Scope::new).ptr);
181        let index = item.index();
182        operation(unsafe { ptr.add(index).as_ref() }.get())
183    }
184
185    pub(crate) fn try_with_item<R>(
186        item: &'static Item,
187        pin: &CpuPin<'_>,
188        operation: impl for<'access> FnOnce(&'access ItemBox) -> R,
189    ) -> Option<R> {
190        let ptr = ACTIVE_SCOPE_PTR.read_current(pin);
191        let ptr = if ptr == 0 {
192            GLOBAL_SCOPE.get()?.ptr
193        } else {
194            NonNull::new(ptr as *mut ItemSlot)?
195        };
196        let index = item.index();
197        Some(operation(unsafe { ptr.add(index).as_ref() }.try_get()?))
198    }
199}