scope_local/item.rs
1use core::{
2 alloc::Layout,
3 marker::PhantomData,
4 ops::{Deref, DerefMut},
5 ptr::{NonNull, addr_of},
6};
7
8use ax_percpu::CpuPin;
9use ax_sync::PreemptGuard;
10
11use crate::scope::{ActiveScope, Scope, ScopeCellReadGuard, ScopeCellWriteGuard, ScopeItemLease};
12
13#[doc(hidden)]
14pub struct Item {
15 pub(crate) layout: Layout,
16 pub(crate) init: fn(NonNull<()>),
17 pub(crate) drop: fn(NonNull<()>),
18}
19
20pub(crate) struct Registry;
21
22impl Deref for Registry {
23 type Target = [Item];
24
25 fn deref(&self) -> &Self::Target {
26 unsafe extern "Rust" {
27 static __start_scope_local: Item;
28 static __stop_scope_local: Item;
29 }
30 let start = addr_of!(__start_scope_local) as usize;
31 let len = (addr_of!(__stop_scope_local) as usize - start) / core::mem::size_of::<Item>();
32 unsafe { core::slice::from_raw_parts(start as *const Item, len) }
33 }
34}
35
36impl Item {
37 /// Creates one type-erased registry descriptor.
38 ///
39 /// # Safety
40 ///
41 /// `init` must initialize exactly one valid `T` at the supplied aligned
42 /// address, and `drop` must drop exactly that value without deallocating
43 /// its storage. The descriptor must only be paired with `LocalItem<T>`.
44 #[doc(hidden)]
45 pub const unsafe fn new<T: Send + Sync + 'static>(
46 init: fn(NonNull<()>),
47 drop: fn(NonNull<()>),
48 ) -> Self {
49 Self {
50 layout: Layout::new::<T>(),
51 init,
52 drop,
53 }
54 }
55
56 #[inline]
57 pub(crate) fn index(&'static self) -> usize {
58 unsafe { (self as *const Item).offset_from_unsigned(Registry.as_ptr()) }
59 }
60}
61
62/// A scope-local item.
63pub struct LocalItem<T> {
64 item: &'static Item,
65 _p: PhantomData<T>,
66}
67
68impl<T: Send + Sync + 'static> LocalItem<T> {
69 #[doc(hidden)]
70 #[inline]
71 /// # Safety
72 ///
73 /// `item` must have been created for exactly `T` and its initializer and
74 /// destructor must obey [`Item::new`]'s contract.
75 pub const unsafe fn new(item: &'static Item) -> Self {
76 Self {
77 item,
78 _p: PhantomData,
79 }
80 }
81
82 /// Runs `operation` with the value selected by the current active scope.
83 ///
84 /// The higher-ranked closure prevents a reference into per-CPU-selected
85 /// storage from escaping after preemption is re-enabled. The first global
86 /// access initializes the global scope before entering the pinned access.
87 /// Concurrent first access waits for that initialization to be published.
88 ///
89 /// This entry is intended for task context. Callers that already hold an
90 /// IRQ or preemption guard should use [`Self::with_pinned`] to avoid a
91 /// context transition on return. `operation` must not block, sleep, yield,
92 /// or retain another context-aware guard; clone an owned handle and perform
93 /// potentially blocking work after this method returns instead.
94 ///
95 /// ```compile_fail
96 /// use scope_local::scope_local;
97 ///
98 /// scope_local! {
99 /// static VALUE: usize = 1;
100 /// }
101 ///
102 /// let escaped: &'static usize = VALUE.with(|value| value);
103 /// ```
104 pub fn with<R>(&self, operation: impl for<'access> FnOnce(&'access T) -> R) -> R {
105 let mut operation = Some(operation);
106 loop {
107 let guard = PreemptGuard::new();
108 // SAFETY: `PreemptGuard` prevents migration for this complete access.
109 let result = unsafe {
110 ax_percpu::with_cpu_pin(|pin| {
111 ActiveScope::try_with_item(self.item, pin, |item| {
112 let operation = operation
113 .take()
114 .expect("scope-local operation must run at most once");
115 operation(item.as_ref())
116 })
117 })
118 }
119 .expect("scope-local access requires an installed CPU area");
120 drop(guard);
121
122 if let Some(result) = result {
123 return result;
124 }
125 ActiveScope::initialize_global();
126 }
127 }
128
129 /// Runs `operation` with the current value under an existing CPU pin.
130 ///
131 /// It never enters or leaves preemption state itself. The selected global
132 /// scope must already have been initialized by [`Self::with`]; explicit
133 /// [`Scope`] values are initialized eagerly. The caller remains responsible
134 /// for making `operation` valid in the context represented by `pin`.
135 pub fn with_pinned<'pin, R>(
136 &self,
137 pin: &CpuPin<'pin>,
138 operation: impl for<'access> FnOnce(&'access T) -> R,
139 ) -> R {
140 ActiveScope::with_item(self.item, pin, |item| operation(item.as_ref()))
141 }
142
143 /// Runs `operation` under an existing CPU pin without lazy initialization.
144 ///
145 /// Returns `None` when the global scope has not been initialized. This path
146 /// performs no allocation, lock acquisition, context transition, or user
147 /// callback other than `operation`, making it suitable for a caller holding
148 /// an IRQ-derived pin when that operation is itself hard-IRQ-safe.
149 pub fn try_with_pinned<'pin, R>(
150 &self,
151 pin: &CpuPin<'pin>,
152 operation: impl for<'access> FnOnce(&'access T) -> R,
153 ) -> Option<R> {
154 ActiveScope::try_with_item(self.item, pin, |item| operation(item.as_ref()))
155 }
156
157 /// Clones the value selected by the current active scope.
158 ///
159 /// This is the preferred entry for `Arc`-backed lock owners: the CPU pin is
160 /// released before the returned owner is locked or used by potentially
161 /// blocking code.
162 pub fn clone_current(&self) -> T
163 where
164 T: Clone,
165 {
166 self.with(Clone::clone)
167 }
168
169 /// Returns a reference to this item within the given scope.
170 pub fn scope<'scope>(&self, scope: &'scope Scope) -> ScopeItem<'scope, T> {
171 ScopeItem {
172 lease: scope.read_item(self.item),
173 _p: PhantomData,
174 }
175 }
176
177 /// Returns a mutable reference to this item within the given scope.
178 pub fn scope_mut<'scope>(&self, scope: &'scope mut Scope) -> ScopeItemMut<'scope, T> {
179 ScopeItemMut {
180 item: scope.get_mut_unlocked(self.item),
181 _p: PhantomData,
182 }
183 }
184
185 /// Returns the value selected through an existing [`ScopeCell`] read
186 /// capability.
187 ///
188 /// This path reuses the guard's shared count. It never recursively acquires
189 /// the underlying gate, so a writer that has already published upgrade
190 /// intent cannot deadlock the current reader.
191 ///
192 /// [`ScopeCell`]: crate::ScopeCell
193 pub fn scope_cell<'scope>(&self, scope: &'scope ScopeCellReadGuard<'_>) -> &'scope T {
194 scope.get(self.item).as_ref()
195 }
196
197 /// Returns mutable access to this item under a [`ScopeCell`] writer guard.
198 ///
199 /// Unlike [`Self::scope_mut`], this path never creates `&mut Scope`; the
200 /// guard authorizes slot-level interior mutation while other CPUs may still
201 /// retain the stable active-scope identity.
202 ///
203 /// [`ScopeCell`]: crate::ScopeCell
204 pub fn scope_cell_mut<'scope>(
205 &self,
206 scope: &'scope mut ScopeCellWriteGuard<'_>,
207 ) -> ScopeItemMut<'scope, T> {
208 ScopeItemMut {
209 item: scope.get_mut(self.item),
210 _p: PhantomData,
211 }
212 }
213}
214
215/// A reference to a scope-local item within a specific scope.
216///
217/// Created by [`LocalItem::scope`].
218pub struct ScopeItem<'scope, T> {
219 lease: ScopeItemLease<'scope>,
220 _p: PhantomData<T>,
221}
222
223impl<'scope, T> Deref for ScopeItem<'scope, T> {
224 type Target = T;
225
226 #[inline]
227 fn deref(&self) -> &Self::Target {
228 self.lease.item().as_ref()
229 }
230}
231
232/// A mutable reference to a scope-local item within a specific scope.
233///
234/// Created by [`LocalItem::scope_mut`].
235pub struct ScopeItemMut<'scope, T> {
236 item: &'scope mut crate::boxed::ItemBox,
237 _p: PhantomData<T>,
238}
239
240impl<'scope, T> Deref for ScopeItemMut<'scope, T> {
241 type Target = T;
242
243 #[inline]
244 fn deref(&self) -> &Self::Target {
245 self.item.as_ref()
246 }
247}
248
249impl<'scope, T> DerefMut for ScopeItemMut<'scope, T> {
250 #[inline]
251 fn deref_mut(&mut self) -> &mut Self::Target {
252 self.item.as_mut()
253 }
254}
255
256/// Define a scope-local item.
257///
258/// # Example
259///
260/// ```
261/// # use std::sync::atomic::AtomicUsize;
262/// # use scope_local::scope_local;
263/// scope_local! {
264/// /// An integer.
265/// pub static MY_I32: i32 = 42;
266/// /// An atomic integer.
267/// pub static MY_ATOMIC_USIZE: AtomicUsize = AtomicUsize::new(0);
268/// }
269/// ```
270#[macro_export]
271macro_rules! scope_local {
272 ( $( $(#[$attr:meta])* $vis:vis static $name:ident: $ty:ty = $default:expr; )+ ) => {
273 $(
274 $(#[$attr])*
275 $vis static $name: $crate::LocalItem<$ty> = {
276 #[unsafe(link_section = "scope_local")]
277 static ITEM: $crate::Item = unsafe {
278 $crate::Item::new::<$ty>(|ptr| {
279 let val: $ty = $default;
280 ptr.cast().write(val)
281 }, |ptr| {
282 ptr.cast::<$ty>().drop_in_place();
283 })
284 };
285
286 unsafe { $crate::LocalItem::new(&ITEM) }
287 };
288 )+
289 }
290}