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_kernel_guard::NoPreempt;
9use ax_percpu::CpuPin;
10
11use crate::scope::{ActiveScope, Scope};
12
13#[doc(hidden)]
14pub struct Item {
15    pub layout: Layout,
16    pub init: fn(NonNull<()>),
17    pub 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 for `T`.
38    ///
39    /// # Safety
40    ///
41    /// `init` must initialize exactly one valid `T` at the supplied address,
42    /// and `drop` must destroy that value without deallocating its storage.
43    #[doc(hidden)]
44    pub const unsafe fn new<T: Send + Sync + 'static>(
45        init: fn(NonNull<()>),
46        drop: fn(NonNull<()>),
47    ) -> Self {
48        Self {
49            layout: Layout::new::<T>(),
50            init,
51            drop,
52        }
53    }
54
55    #[inline]
56    pub(crate) fn index(&'static self) -> usize {
57        unsafe { (self as *const Item).offset_from_unsigned(Registry.as_ptr()) }
58    }
59}
60
61/// A scope-local item.
62pub struct LocalItem<T> {
63    item: &'static Item,
64    _p: PhantomData<T>,
65}
66
67impl<T: Send + Sync + 'static> LocalItem<T> {
68    #[doc(hidden)]
69    #[inline]
70    /// # Safety
71    ///
72    /// `item` must have been constructed for exactly `T`.
73    pub const unsafe fn new(item: &'static Item) -> Self {
74        Self {
75            item,
76            _p: PhantomData,
77        }
78    }
79
80    /// Runs `operation` with the value selected by the active scope.
81    ///
82    /// The higher-ranked closure prevents a reference into per-CPU-selected
83    /// storage from escaping after preemption is re-enabled. The first global
84    /// access initializes the global scope before entering the pinned access.
85    /// Concurrent first access waits for that initialization to be published.
86    pub fn with<R>(&self, operation: impl for<'access> FnOnce(&'access T) -> R) -> R {
87        let mut operation = Some(operation);
88        loop {
89            let guard = NoPreempt::new();
90            // SAFETY: `NoPreempt` prevents migration for this complete access.
91            let result = unsafe {
92                ax_percpu::with_cpu_pin(|pin| {
93                    ActiveScope::try_with_item(self.item, pin, |item| {
94                        let operation = operation
95                            .take()
96                            .expect("scope-local operation must run at most once");
97                        operation(item.as_ref())
98                    })
99                })
100            }
101            .expect("scope-local access requires an installed CPU area");
102            drop(guard);
103
104            if let Some(result) = result {
105                return result;
106            }
107            ActiveScope::initialize_global();
108        }
109    }
110
111    /// Runs `operation` under an existing CPU pin without initialization.
112    ///
113    /// # Panics
114    ///
115    /// Panics if the selected global scope has not been initialized by
116    /// [`LocalItem::with`]. Explicit [`Scope`] values are initialized eagerly.
117    pub fn with_pinned<R>(
118        &self,
119        pin: &CpuPin<'_>,
120        operation: impl for<'access> FnOnce(&'access T) -> R,
121    ) -> R {
122        ActiveScope::with_item(self.item, pin, |item| operation(item.as_ref()))
123    }
124
125    /// Runs `operation` without lazy initialization under an existing pin.
126    ///
127    /// This returns `None` when the selected global scope has not yet been
128    /// initialized, allowing hard-IRQ callers to avoid allocation.
129    pub fn try_with_pinned<R>(
130        &self,
131        pin: &CpuPin<'_>,
132        operation: impl for<'access> FnOnce(&'access T) -> R,
133    ) -> Option<R> {
134        ActiveScope::try_with_item(self.item, pin, |item| operation(item.as_ref()))
135    }
136
137    /// Clones the selected value while keeping the CPU pin lifetime short.
138    pub fn clone_current(&self) -> T
139    where
140        T: Clone,
141    {
142        self.with(Clone::clone)
143    }
144
145    /// Returns a reference to this item within the given scope.
146    pub fn scope<'scope>(&self, scope: &'scope Scope) -> ScopeItem<'scope, T> {
147        ScopeItem {
148            item: self.item,
149            scope,
150            _p: PhantomData,
151        }
152    }
153
154    /// Returns a mutable reference to this item within the given scope.
155    pub fn scope_mut<'scope>(&self, scope: &'scope mut Scope) -> ScopeItemMut<'scope, T> {
156        ScopeItemMut {
157            item: self.item,
158            scope,
159            _p: PhantomData,
160        }
161    }
162}
163
164/// A reference to a scope-local item within a specific scope.
165///
166/// Created by [`LocalItem::scope`].
167pub struct ScopeItem<'scope, T> {
168    item: &'static Item,
169    scope: &'scope Scope,
170    _p: PhantomData<T>,
171}
172
173impl<'scope, T> Deref for ScopeItem<'scope, T> {
174    type Target = T;
175
176    #[inline]
177    fn deref(&self) -> &Self::Target {
178        self.scope.get(self.item).as_ref()
179    }
180}
181
182/// A mutable reference to a scope-local item within a specific scope.
183///
184/// Created by [`LocalItem::scope_mut`].
185pub struct ScopeItemMut<'scope, T> {
186    item: &'static Item,
187    scope: &'scope mut Scope,
188    _p: PhantomData<T>,
189}
190
191impl<'scope, T> Deref for ScopeItemMut<'scope, T> {
192    type Target = T;
193
194    #[inline]
195    fn deref(&self) -> &Self::Target {
196        self.scope.get(self.item).as_ref()
197    }
198}
199
200impl<'scope, T> DerefMut for ScopeItemMut<'scope, T> {
201    #[inline]
202    fn deref_mut(&mut self) -> &mut Self::Target {
203        self.scope.get_mut(self.item).as_mut()
204    }
205}
206
207/// Define a scope-local item.
208///
209/// # Example
210///
211/// ```
212/// # use std::sync::atomic::AtomicUsize;
213/// # use scope_local::scope_local;
214/// scope_local! {
215///     /// An integer.
216///     pub static MY_I32: i32 = 42;
217///     /// An atomic integer.
218///     pub static MY_ATOMIC_USIZE: AtomicUsize = AtomicUsize::new(0);
219/// }
220/// ```
221#[macro_export]
222macro_rules! scope_local {
223    ( $( $(#[$attr:meta])* $vis:vis static $name:ident: $ty:ty = $default:expr; )+ ) => {
224        $(
225            $(#[$attr])*
226            $vis static $name: $crate::LocalItem<$ty> = {
227                #[unsafe(link_section = "scope_local")]
228                static ITEM: $crate::Item = unsafe {
229                    $crate::Item::new::<$ty>(|ptr| {
230                        let val: $ty = $default;
231                        ptr.cast().write(val)
232                    }, |ptr| {
233                        ptr.cast::<$ty>().drop_in_place();
234                    })
235                };
236
237                unsafe { $crate::LocalItem::new(&ITEM) }
238            };
239        )+
240    }
241}