osiris/sync/once.rs
1#![allow(dead_code)]
2
3use core::cell::UnsafeCell;
4use core::mem::MaybeUninit;
5use core::sync::atomic::AtomicU8;
6use core::sync::atomic::Ordering;
7
8/// A synchronization primitive that can be used to block a thread until a value is ready.
9/// The procedure is as follows:
10/// 1. The Caller calls step(NOT_READY) to indicate that it is about to start the initialization process.
11/// 2. The Caller initializes the value.
12/// 3. The Caller calls step(IN_TRANSIT) to indicate that the value is ready.
13/// If step 1 fails, the value is already being initialized and the Caller must wait until is() returns true.
14pub struct Ready {
15 ready: AtomicU8,
16}
17
18impl Ready {
19 const READY: u8 = 2;
20 const IN_TRANSIT: u8 = 1;
21 const NOT_READY: u8 = 0;
22
23 /// Initializes a new Ready.
24 pub const fn new() -> Self {
25 Self {
26 ready: AtomicU8::new(0),
27 }
28 }
29
30 /// Move the Ready to the next state, if it is in state `from`.
31 pub fn step(&self, from: u8) -> bool {
32 self.forward(from, from + 1)
33 }
34
35 /// Move the Ready to state `to` if it is in state `from`.
36 fn forward(&self, _from: u8, _to: u8) -> bool {
37 self.ready
38 .compare_exchange(_from, _to, Ordering::AcqRel, Ordering::Acquire)
39 .is_ok()
40 }
41
42 /// Returns true if the value is ready.
43 pub fn is(&self) -> bool {
44 self.ready.load(Ordering::Acquire) == Self::READY
45 }
46}
47
48/// A synchronization primitive that represents a value that is initialized at most once.
49pub struct OnceCell<T> {
50 value: UnsafeCell<MaybeUninit<T>>,
51 init: Ready,
52}
53
54/// Safety:
55/// 1. A `value` is only written to atomically and once.
56/// 2. A `value` is only readable from after the initialization process is finished.
57/// 3. A `init` is only written and read from atomically.
58unsafe impl<T> Sync for OnceCell<T> {}
59
60impl<T> OnceCell<T> {
61 /// Initializes a new OnceCell.
62 pub const fn new() -> Self {
63 Self {
64 value: UnsafeCell::new(MaybeUninit::uninit()),
65 init: Ready::new(),
66 }
67 }
68
69 /// Returns a reference to the value if it is initialized.
70 pub fn get(&self) -> Option<&T> {
71 if self.init.is() {
72 // Safety:
73 // 1. By contract, is the value initialized if init.is() returns true.
74 // 2. No writes are allowed to the value after the initialization process is finished.
75 Some(unsafe { self.get_unchecked() })
76 } else {
77 None
78 }
79 }
80
81 /// Sets the value if it is not already initialized, and returns a reference to the value.
82 pub fn set_or_get(&self, value: T) -> &T {
83 if let Some(value) = self.set(value) {
84 value
85 } else {
86 // If we reach this point, initialization is already in progress.
87 while !self.init.is() {
88 hal::asm::nop!();
89 }
90 // Safety:
91 // 1. By contract, is the value initialized if init.is() returns true.
92 // 2. No writes are allowed to the value after the initialization process is finished.
93 unsafe { self.get_unchecked() }
94 }
95 }
96
97 /// Sets the value if it is not already initialized, and returns a reference to the value.
98 pub fn do_or_get<F>(&self, f: F) -> &T
99 where
100 F: FnOnce() -> T,
101 {
102 self.set_or_get(f())
103 }
104
105 /// Sets the value if it is not already initialized, returns a reference to the value if it was not set previously.
106 pub fn set(&self, value: T) -> Option<&T> {
107 if self.init.is() {
108 return None;
109 }
110
111 if self.init.step(Ready::NOT_READY) {
112 // Safety: We are now in the IN_TRANSIT state, so we are the only ones that can write to the value.
113 // We are also the only ones that can read from the value.
114 unsafe {
115 self.value.get().write(MaybeUninit::new(value));
116 }
117
118 if self.init.step(Ready::IN_TRANSIT) {
119 // Safety: We are now in the READY state, so no writes can happen to the value.
120 // 1. It is safe to create a immutable reference to the value.
121 // 2. We initialized the value, so it is safe to return a reference to it.
122 return Some(unsafe { self.get_unchecked() });
123 }
124
125 // By contract, only the thread that started the initialization process can finish it.
126 unreachable!();
127 }
128
129 None
130 }
131
132 /// Returns a reference to the value, unchecked.
133 ///
134 /// # Safety
135 /// Preconditions: The value must be initialized.
136 /// Postconditions: The value is returned.
137 unsafe fn get_unchecked(&self) -> &T {
138 unsafe { (*self.value.get()).assume_init_ref() }
139 }
140}