1use alloc::{boxed::Box, vec::Vec};
2use core::{
3 ptr::NonNull,
4 sync::atomic::{AtomicUsize, Ordering},
5};
6
7use ax_lazyinit::OnceLock;
8use ax_percpu::CpuPin;
9use ax_sync::PreemptGuard;
10
11use crate::{
12 boxed::ItemBox,
13 item::{Item, Registry},
14};
15
16pub struct Scope {
18 items: Box<[ItemBox]>,
19}
20
21impl Scope {
22 pub fn new() -> Self {
28 let items = Registry
29 .iter()
30 .map(ItemBox::new)
31 .collect::<Vec<_>>()
32 .into_boxed_slice();
33 Self { items }
34 }
35
36 pub(crate) fn get(&self, item: &'static Item) -> &ItemBox {
37 &self.items[item.index()]
38 }
39
40 pub(crate) fn get_mut(&mut self, item: &'static Item) -> &mut ItemBox {
41 &mut self.items[item.index()]
42 }
43
44 fn items_ptr(&self) -> NonNull<ItemBox> {
45 NonNull::new(self.items.as_ptr().cast_mut())
46 .expect("scope-local registry must contain the accessed item")
47 }
48}
49
50impl Default for Scope {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56static GLOBAL_SCOPE: OnceLock<Scope> = OnceLock::new();
57static GLOBAL_SCOPE_STATE: AtomicUsize = AtomicUsize::new(GlobalScopeState::Uninitialized as usize);
58
59#[derive(Clone, Copy, Eq, PartialEq)]
60#[repr(usize)]
61enum GlobalScopeState {
62 Uninitialized,
63 Ready,
64}
65
66struct GlobalInitialization {
67 owner_context: usize,
68 published: bool,
69}
70
71impl GlobalInitialization {
72 fn begin(owner_context: usize) -> Self {
73 Self {
74 owner_context,
75 published: false,
76 }
77 }
78
79 fn publish(mut self, scope: Scope) {
80 GLOBAL_SCOPE.call_once(|| scope);
81 GLOBAL_SCOPE_STATE.store(GlobalScopeState::Ready as usize, Ordering::Release);
82 self.published = true;
83 }
84}
85
86impl Drop for GlobalInitialization {
87 fn drop(&mut self) {
88 if !self.published {
89 let _ = GLOBAL_SCOPE_STATE.compare_exchange(
90 self.owner_context,
91 GlobalScopeState::Uninitialized as usize,
92 Ordering::Release,
93 Ordering::Relaxed,
94 );
95 }
96 }
97}
98
99#[ax_percpu::def_percpu]
100pub(crate) static ACTIVE_SCOPE_PTR: usize = 0;
101
102pub struct ActiveScope;
104
105impl ActiveScope {
106 pub unsafe fn set(scope: &Scope) {
114 let _guard = PreemptGuard::new();
115 unsafe {
118 ax_percpu::with_cpu_pin(|pin| Self::set_pinned(scope, pin))
119 .expect("scope-local access requires an installed CPU area")
120 };
121 }
122
123 pub unsafe fn set_pinned(scope: &Scope, pin: &CpuPin<'_>) {
129 ACTIVE_SCOPE_PTR.write_current(pin, scope.items_ptr().addr().get());
130 }
131
132 pub fn set_global() {
134 let _guard = PreemptGuard::new();
135 unsafe {
138 ax_percpu::with_cpu_pin(Self::set_global_pinned)
139 .expect("scope-local access requires an installed CPU area")
140 };
141 }
142
143 pub fn set_global_pinned(pin: &CpuPin<'_>) {
145 ACTIVE_SCOPE_PTR.write_current(pin, 0);
146 }
147
148 pub fn is_global() -> bool {
150 let _guard = PreemptGuard::new();
151 unsafe {
153 ax_percpu::with_cpu_pin(Self::is_global_pinned)
154 .expect("scope-local access requires an installed CPU area")
155 }
156 }
157
158 pub fn is_global_pinned(pin: &CpuPin<'_>) -> bool {
160 ACTIVE_SCOPE_PTR.read_current(pin) == 0
161 }
162
163 pub(crate) fn with_item<R>(
164 item: &'static Item,
165 pin: &CpuPin<'_>,
166 operation: impl for<'access> FnOnce(&'access ItemBox) -> R,
167 ) -> R {
168 Self::try_with_item(item, pin, operation).expect(
169 "scope-local global scope is not initialized; use LocalItem::with before pinned access",
170 )
171 }
172
173 pub(crate) fn try_with_item<R>(
174 item: &'static Item,
175 pin: &CpuPin<'_>,
176 operation: impl for<'access> FnOnce(&'access ItemBox) -> R,
177 ) -> Option<R> {
178 let ptr = ACTIVE_SCOPE_PTR.read_current(pin);
179 let items = if ptr == 0 {
180 GLOBAL_SCOPE.get()?.items_ptr()
181 } else {
182 NonNull::new(ptr as *mut ItemBox)?
183 };
184 let index = item.index();
185 Some(operation(unsafe { items.add(index).as_ref() }))
186 }
187
188 pub(crate) fn initialize_global() {
189 let owner_context = current_context_identity();
190 loop {
191 match GLOBAL_SCOPE_STATE.load(Ordering::Acquire) {
192 state if state == GlobalScopeState::Ready as usize => return,
193 state if state == owner_context => {
194 panic!("scope-local global scope initialization is already in progress")
195 }
196 state if state == GlobalScopeState::Uninitialized as usize => {
197 if GLOBAL_SCOPE_STATE
198 .compare_exchange(
199 GlobalScopeState::Uninitialized as usize,
200 owner_context,
201 Ordering::AcqRel,
202 Ordering::Acquire,
203 )
204 .is_ok()
205 {
206 let initialization = GlobalInitialization::begin(owner_context);
207 initialization.publish(Scope::new());
208 return;
209 }
210 }
211 _ => core::hint::spin_loop(),
212 }
213 }
214 }
215}
216
217fn current_context_identity() -> usize {
218 let _guard = PreemptGuard::new();
219 let context = unsafe {
223 ax_percpu::with_cpu_pin(|pin| {
224 cpu_local::current_context(pin)
225 .expect("scope-local current context must be valid")
226 .as_ptr() as usize
227 })
228 .expect("scope-local access requires an installed CPU area")
229 };
230 assert!(
231 context > GlobalScopeState::Ready as usize,
232 "scope-local initialization requires a valid current context"
233 );
234 context
235}