Skip to main content

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}