Skip to main content

ostd/sync/
once.rs

1use vstd::{
2    atomic_with_ghost,
3    cell::pcell::{PCell, PointsTo},
4    modes::tracked_static_ref,
5    prelude::*,
6};
7
8use super::AtomicDataWithOwner;
9
10verus! {
11
12pub const UNINIT: u64 = 0;
13
14pub const OCCUPIED: u64 = 1;
15
16pub const INITED: u64 = 2;
17
18/// A tracked state of a¸ [`Once`] that can be used to ensure that the cell is
19/// initialized before accessing its value.
20pub tracked enum OnceState<V: 'static> {
21    /// The cell is uninitialized.
22    Uninit(PointsTo<Option<V>>),
23    /// The cell is occupied meaning it is being *written*.
24    Occupied,
25    /// The cell is initialized with a value and extended with
26    /// static lifetime.
27    Init(&'static PointsTo<Option<V>>),
28}
29
30/// A [`Predicate`] is something you're gonna preserve during the lifetime
31/// of any synchronization primitives like [`Once`].
32pub trait Predicate<V> {
33    spec fn inv(self, v: V) -> bool;
34}
35
36/// A trivial predicate that holds for any value.
37/// Use with [`OnceImpl`] when no invariant is needed.
38pub struct TrivialPred;
39
40impl<V> Predicate<V> for TrivialPred {
41    open spec fn inv(self, v: V) -> bool {
42        true
43    }
44}
45
46struct_with_invariants! {
47/// A synchronization primitive which can nominally be written to only once.
48///
49/// This type is a thread-safe [`Once`], and can be used in statics.
50/// In many simple cases, you can use [`LazyLock<T, F>`] instead to get the benefits of this type
51/// with less effort: `LazyLock<T, F>` "looks like" `&T` because it initializes with `F` on deref!
52/// Where OnceLock shines is when LazyLock is too simple to support a given case, as LazyLock
53/// doesn't allow additional inputs to its function after you call [`LazyLock::new(|| ...)`].
54///
55/// A `OnceLock` can be thought of as a safe abstraction over uninitialized data that becomes
56/// initialized once written.
57///
58/// # Examples
59///
60/// ```rust
61/// static MY_ONCE: Once<i32> = Once::new();
62///
63/// let value = MY_ONCE.get();
64/// assert(value.is_some());   // unsatisfied precondition, as MY_ONCE is uninitialized.
65/// ```
66#[verifier::reject_recursive_types(V)]
67pub struct OnceImpl<V: 'static, F: Predicate<V>> {
68    cell: PCell<Option<V>>,
69    state: vstd::atomic_ghost::AtomicU64<_, OnceState<V>, _>,
70    f: Ghost<F>,
71}
72
73#[verifier::type_invariant]
74pub closed spec fn wf(&self) -> bool {
75    invariant on state with (cell, f) is (v: u64, g: OnceState<V>) {
76        match g {
77            OnceState::Uninit(points_to) => {
78                &&& v == UNINIT
79                &&& points_to.id() == cell.id()
80                &&& points_to.value() is None
81            }
82            OnceState::Occupied => {
83                &&& v == OCCUPIED
84            }
85            OnceState::Init(points_to) => {
86                &&& v == INITED
87                &&& points_to.id() == cell.id()
88                &&& points_to.value() is Some 
89                &&& f@.inv(points_to.value()->Some_0)
90            }
91        }
92    }
93}
94
95}
96
97#[verifier::external]
98unsafe impl<V, F: Predicate<V>> Send for OnceImpl<V, F> {
99
100}
101
102#[verifier::external]
103unsafe impl<V, F: Predicate<V>> Sync for OnceImpl<V, F> {
104
105}
106
107impl<V, F: Predicate<V>> OnceImpl<V, F> {
108    pub closed spec fn inv(&self) -> F {
109        self.f@
110    }
111
112    /// Creates a new uninitialized [`Once`].
113    pub const fn new(Ghost(f): Ghost<F>) -> (r: Self)
114        ensures
115            r.wf(),
116            r.inv() == f,
117    {
118        let (cell, Tracked(points_to)) = PCell::new(None);
119        let tracked state = OnceState::Uninit(points_to);
120        let state = vstd::atomic_ghost::AtomicU64::new(
121            Ghost((cell, Ghost(f))),
122            UNINIT,
123            Tracked(state),
124        );
125
126        Self { cell, state, f: Ghost(f) }
127    }
128
129    /// Initializes the [`Once`] with the given value `v`.
130    pub fn init(&self, v: V)
131        requires
132            self.inv().inv(v),
133            self.wf(),
134    {
135        let cur_state =
136            atomic_with_ghost! {
137            &self.state => load(); ghost g => {}
138        };
139
140        if cur_state != UNINIT {
141            return ;
142        } else {
143            let tracked mut points_to = None;
144            let res =
145                atomic_with_ghost! {
146                &self.state => compare_exchange(UNINIT, OCCUPIED);
147                returning res; ghost g => {
148                    g = match g {
149                        OnceState::Uninit(points_to_inner) => {
150                            points_to = Some(points_to_inner);
151                            OnceState::Occupied
152                        }
153                        _ => {
154                            // If we are not in Uninit state, we cannot do anything.
155                            g
156                        }
157                    }
158                }
159            };
160
161            if !res.is_err() {
162                let tracked mut points_to = points_to.tracked_unwrap();
163                self.cell.replace(Tracked(&mut points_to), Some(v));
164                // Extending the permission to static because `OnceLock` is
165                // often shared among threads and we want to ensure that
166                // the value is accessible globally.
167                let tracked static_points_to = tracked_static_ref(points_to);
168                // let tracked _ = self.inst.borrow().do_deposit(points_to, points_to, &mut token);
169                atomic_with_ghost! {
170                    &self.state => store(INITED); ghost g => {
171                        g = OnceState::Init(static_points_to);
172                    }
173                }
174                return ;
175            } else {
176                // wait or abort.
177                return ;
178            }
179        }
180    }
181
182    /// Try to get the value stored in the [`Once`]. A [`Option::Some`]
183    /// is returned if the cell is initialized; otherwise, [`Option::None`]
184    /// is returned.
185    pub fn get<'a>(&'a self) -> (r: Option<&'a V>)
186        requires
187            self.wf(),
188        ensures
189            self.wf(),
190            r matches Some(res) ==> self.inv().inv(*res),
191    {
192        let tracked mut points_to = None;
193        let res =
194            atomic_with_ghost! {
195            &self.state => load(); ghost g => {
196                match g {
197                    OnceState::Init(points_to_opt) => {
198                        points_to = Some(points_to_opt);
199                    }
200                    _ => {}
201                }
202            }
203        };
204
205        if res == INITED {
206            let tracked points_to = points_to.tracked_unwrap();
207            let tracked static_points_to = tracked_static_ref(points_to);
208
209            self.cell.borrow(Tracked(static_points_to)).as_ref()
210        } else {
211            None
212        }
213    }
214}
215
216/// A `Once` that combines some data with a permission to access it.
217///
218/// This type alias automatically lifts the target value `V` into
219/// a wrapper [`AtomicDataWithOwner<V, Own>`] where `Own` is the
220/// permission type so that we can reason about non-trivial runtime
221/// properties in verification.
222pub type Once<V, Own, F> = OnceImpl<AtomicDataWithOwner<V, Own>, F>;
223
224} // verus!