vasi_sync/
lazy_lock.rs

1use core::marker::PhantomData;
2use core::mem::MaybeUninit;
3
4use num_enum::{IntoPrimitive, TryFromPrimitive};
5
6use crate::sync::atomic;
7use crate::sync::{self, ConstPtr, UnsafeCell};
8
9/// For use with `LazyLock`.
10///
11/// This trait is implemented for `FnOnce` closures, so most users can just use
12/// that directly.  However in case they need to name a type in the `LazyLock`,
13/// they can create a type that implements this trait instead.
14pub trait Producer<T> {
15    fn initialize(self) -> T;
16}
17
18impl<F, T> Producer<T> for F
19where
20    F: FnOnce() -> T,
21{
22    fn initialize(self) -> T {
23        (self)()
24    }
25}
26
27#[derive(TryFromPrimitive, IntoPrimitive, Eq, PartialEq, Debug)]
28#[repr(u32)]
29enum InitState {
30    Uninitd,
31    Initd,
32    // A thread is currently initializing, and there are no threads
33    // asleep on the futex.
34    Initializing,
35    // A thread is currently initializing, and there *are* threads asleep on the
36    // futex. We use this extra state to avoid doing a `futex_wake` when there
37    // are no threads asleep on the futex.
38    InitializingWithSleepers,
39}
40
41/// Analogous to `std::sync::LazyLock`, but works on stable Rust, is no_std,
42/// only makes direct syscalls, etc.
43//
44// TODO: implement or derive `VirtualAddressSpaceIndependent`. The Derive
45// macro currently doesn't work, because the default for `Init` is *not*
46// `VirtualAddressSpaceIndependent`. We could implement the trait manually,
47// but this is error-prone, and we don't currently need it.
48// // #[cfg_attr(not(loom), derive(VirtualAddressSpaceIndependent))]
49//
50// If we *do* make this `VirtualAddressSpaceIndependent`, consider also making
51// it `repr(C)`. Left as `repr(rust)` for now to let the compiler choose the
52// most efficient field ordering based on the alignments of `T` and `Init`.
53pub struct LazyLock<T, Init = fn() -> T> {
54    value: UnsafeCell<MaybeUninit<T>>,
55    initializer: UnsafeCell<Option<Init>>,
56    init_state: atomic::AtomicU32,
57}
58// `T` must be `Sync`, since it will be shared across threads once initialized.
59// `Init` only needs to be `Send` since it'll only ever be accessed by the
60// single thread that performs the initialization.
61unsafe impl<T, Init> Sync for LazyLock<T, Init>
62where
63    T: Sync,
64    Init: Send,
65{
66}
67unsafe impl<T, Init> Send for LazyLock<T, Init>
68where
69    T: Send,
70    Init: Send,
71{
72}
73
74impl<T, Init> LazyLock<T, Init>
75where
76    Init: Producer<T>,
77{
78    // TODO: merge with `new` when loom's `UnsafeCell` supports const init.
79    #[cfg(not(loom))]
80    pub const fn const_new(initializer: Init) -> Self {
81        Self {
82            value: UnsafeCell::new(MaybeUninit::uninit()),
83            init_state: atomic::AtomicU32::new(InitState::Uninitd as u32),
84            initializer: UnsafeCell::new(Some(initializer)),
85        }
86    }
87
88    pub fn new(initializer: Init) -> Self {
89        Self {
90            value: UnsafeCell::new(MaybeUninit::uninit()),
91            init_state: atomic::AtomicU32::new(InitState::Uninitd.into()),
92            initializer: UnsafeCell::new(Some(initializer)),
93        }
94    }
95
96    fn init(&self, mut state: InitState) {
97        loop {
98            match state {
99                InitState::Uninitd => {
100                    match self.init_state.compare_exchange(
101                        InitState::Uninitd.into(),
102                        InitState::Initializing.into(),
103                        atomic::Ordering::Relaxed,
104                        // (Potentially) pairs with `Release` in `Initializing` case.
105                        atomic::Ordering::Acquire,
106                    ) {
107                        Ok(_) => {
108                            // We have the initialization lock.
109                            self.value.get_mut().with(|p: *mut MaybeUninit<T>| unsafe {
110                                let initializer =
111                                    self.initializer.get_mut().deref().take().unwrap();
112                                (*p).write(initializer.initialize());
113                            });
114                            // `Release` pairs with `Acquire` in `Self::force`
115                            // and/or this method's `Initializing` case.
116                            let prev = InitState::try_from(
117                                self.init_state
118                                    .swap(InitState::Initd.into(), atomic::Ordering::Release),
119                            )
120                            .unwrap();
121                            match prev {
122                                InitState::Initializing => {
123                                    // No sleepers; no need to do a futex wake
124                                }
125                                InitState::InitializingWithSleepers => {
126                                    // There are blocked threads; wake them.
127                                    sync::futex_wake_all(&self.init_state).unwrap();
128                                }
129                                other => panic!("Unexpected state {other:?}"),
130                            };
131                            return;
132                        }
133                        Err(v) => {
134                            // It changed out from under us; update and try again.
135                            state = v.try_into().unwrap();
136                        }
137                    }
138                }
139                InitState::Initializing => {
140                    state = match self.init_state.compare_exchange(
141                        InitState::Initializing.into(),
142                        InitState::InitializingWithSleepers.into(),
143                        atomic::Ordering::Relaxed,
144                        // Potentially pair with initialization `Release`.
145                        atomic::Ordering::Acquire,
146                    ) {
147                        Ok(_) => InitState::InitializingWithSleepers,
148                        Err(v) => v.try_into().unwrap(),
149                    }
150                }
151                InitState::InitializingWithSleepers => {
152                    match sync::futex_wait(
153                        &self.init_state,
154                        InitState::InitializingWithSleepers.into(),
155                    ) {
156                        Ok(_) | Err(rustix::io::Errno::INTR) | Err(rustix::io::Errno::AGAIN) => (),
157                        Err(e) => panic!("Unexpected error: {e:?}"),
158                    }
159                    // `Acquire` pairs with `Release` in `Uninitd` case.
160                    state = InitState::try_from(self.init_state.load(atomic::Ordering::Acquire))
161                        .unwrap();
162                }
163                InitState::Initd => {
164                    break;
165                }
166            }
167        }
168    }
169
170    /// Force initialization and return a reference object.
171    ///
172    /// std's LazyLock returns an actual reference here and implements Deref.
173    /// We can't because of our usage of loom's UnsafeCell, which doesn't support
174    /// getting a raw reference that's not borrowed from an intermediate temp object.
175    #[inline]
176    pub fn force(&self) -> Ref<T> {
177        // `Acquire` pairs with `Release` in `Uninitd` case of `init`.
178        let state = InitState::try_from(self.init_state.load(atomic::Ordering::Acquire)).unwrap();
179
180        // Having this check in this small `inline` function that avoids calling
181        // the larger initialization function in the common case substantially
182        // improves performance in the microbenchmark.
183        if state != InitState::Initd {
184            // Call the non-inlined slow path
185            self.init(state);
186        }
187
188        Ref {
189            val: self.value.get(),
190            _phantom: PhantomData,
191        }
192    }
193
194    /// Whether `self` is initialized yet.
195    #[inline]
196    pub fn initd(&self) -> bool {
197        InitState::try_from(self.init_state.load(atomic::Ordering::Relaxed)).unwrap()
198            == InitState::Initd
199    }
200}
201
202/// We only implement Deref outside of Loom, since Loom requires an intermediate
203/// object to catch invalid accesses to our internal `UnsafeCell`s.
204#[cfg(not(loom))]
205impl<T, Init> core::ops::Deref for LazyLock<T, Init>
206where
207    Init: Producer<T>,
208{
209    type Target = T;
210
211    fn deref(&self) -> &Self::Target {
212        self.force();
213        let ptr: *mut MaybeUninit<T> = self.value.untracked_get();
214        // SAFETY: Pointer is valid and no mutable refs exist after
215        // initialization.
216        let ptr = unsafe { ptr.as_ref().unwrap() };
217        // SAFETY: `force` ensured initialization.
218        unsafe { ptr.assume_init_ref() }
219    }
220}
221
222impl<T, Init> Drop for LazyLock<T, Init> {
223    fn drop(&mut self) {
224        // `Acquire` pairs with `Release` in `init`.
225        if InitState::try_from(self.init_state.load(atomic::Ordering::Acquire)).unwrap()
226            == InitState::Initd
227        {
228            unsafe { self.value.get_mut().deref().assume_init_drop() }
229        }
230    }
231}
232
233pub struct Ref<'a, T> {
234    val: ConstPtr<MaybeUninit<T>>,
235    _phantom: PhantomData<&'a T>,
236}
237
238impl<T> core::ops::Deref for Ref<'_, T> {
239    type Target = T;
240
241    fn deref(&self) -> &Self::Target {
242        // SAFETY: LazyLock enforces no more mutable references to this data
243        // before any instances of this type are constructed, and that val is
244        // initd.
245        unsafe { self.val.deref().assume_init_ref() }
246    }
247}