Skip to content

Commit c1cced8

Browse files
committed
std: optimize LazyLock size
1 parent 5bef91c commit c1cced8

File tree

6 files changed

+116
-17
lines changed

6 files changed

+116
-17
lines changed

library/std/src/sync/lazy_lock.rs

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1-
use crate::cell::Cell;
1+
use crate::mem::ManuallyDrop;
2+
3+
use crate::cell::UnsafeCell;
24
use crate::fmt;
35
use crate::ops::Deref;
46
use crate::panic::{RefUnwindSafe, UnwindSafe};
5-
use crate::sync::OnceLock;
7+
use crate::sync::Once;
8+
9+
use super::once::ExclusiveState;
10+
11+
// We use the state of a Once as discriminant value. Upon creation, the state is
12+
// "incomplete" and `f` contains the initialization closure. In the first call to
13+
// `call_once`, `f` is taken and run. If it succeeds, `value` is set and the state
14+
// is changed to "complete". If it panics, the Once is poisoned, so none of the
15+
// two fields is initialized.
16+
union Data<T, F> {
17+
value: ManuallyDrop<T>,
18+
f: ManuallyDrop<F>,
19+
}
620

721
/// A value which is initialized on the first access.
822
///
@@ -43,16 +57,17 @@ use crate::sync::OnceLock;
4357
/// ```
4458
#[unstable(feature = "once_cell", issue = "74465")]
4559
pub struct LazyLock<T, F = fn() -> T> {
46-
cell: OnceLock<T>,
47-
init: Cell<Option<F>>,
60+
once: Once,
61+
data: UnsafeCell<Data<T, F>>,
4862
}
63+
4964
impl<T, F: FnOnce() -> T> LazyLock<T, F> {
5065
/// Creates a new lazy value with the given initializing
5166
/// function.
5267
#[inline]
5368
#[unstable(feature = "once_cell", issue = "74465")]
5469
pub const fn new(f: F) -> LazyLock<T, F> {
55-
LazyLock { cell: OnceLock::new(), init: Cell::new(Some(f)) }
70+
LazyLock { once: Once::new(), data: UnsafeCell::new(Data { f: ManuallyDrop::new(f) }) }
5671
}
5772

5873
/// Forces the evaluation of this lazy value and
@@ -74,10 +89,43 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> {
7489
#[inline]
7590
#[unstable(feature = "once_cell", issue = "74465")]
7691
pub fn force(this: &LazyLock<T, F>) -> &T {
77-
this.cell.get_or_init(|| match this.init.take() {
78-
Some(f) => f(),
79-
None => panic!("Lazy instance has previously been poisoned"),
80-
})
92+
this.once.call_once(|| {
93+
// SAFETY: `call_once` only runs this closure once, ever.
94+
let data = unsafe { &mut *this.data.get() };
95+
let f = unsafe { ManuallyDrop::take(&mut data.f) };
96+
let value = f();
97+
data.value = ManuallyDrop::new(value);
98+
});
99+
100+
// SAFETY:
101+
// There are four possible scenarios:
102+
// * the closure was called and initialized `value`.
103+
// * the closure was called and panicked, so this point is never reached.
104+
// * the closure was not called, but a previous call initialized `value`.
105+
// * the closure was not called because the Once is poisoned, so this point
106+
// is never reached.
107+
// So `value` has definitely been initialized and will not be modified again.
108+
unsafe { &*(*this.data.get()).value }
109+
}
110+
}
111+
112+
impl<T, F> LazyLock<T, F> {
113+
/// Get the inner value if it has already been initialized.
114+
fn get(&self) -> Option<&T> {
115+
if self.once.is_completed() { Some(unsafe { &*(*self.data.get()).value }) } else { None }
116+
}
117+
}
118+
119+
#[unstable(feature = "once_cell", issue = "74465")]
120+
impl<T, F> Drop for LazyLock<T, F> {
121+
fn drop(&mut self) {
122+
match self.once.state() {
123+
ExclusiveState::Incomplete => unsafe { ManuallyDrop::drop(&mut self.data.get_mut().f) },
124+
ExclusiveState::Complete => unsafe {
125+
ManuallyDrop::drop(&mut self.data.get_mut().value)
126+
},
127+
ExclusiveState::Poisoned => {}
128+
}
81129
}
82130
}
83131

@@ -103,23 +151,25 @@ impl<T: Default> Default for LazyLock<T> {
103151
#[unstable(feature = "once_cell", issue = "74465")]
104152
impl<T: fmt::Debug, F> fmt::Debug for LazyLock<T, F> {
105153
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106-
f.debug_struct("Lazy").field("cell", &self.cell).finish_non_exhaustive()
154+
f.debug_tuple("LazyLock")
155+
.field(match self.get() {
156+
Some(v) => v,
157+
None => &"Uninit",
158+
})
159+
.finish()
107160
}
108161
}
109162

110163
// We never create a `&F` from a `&LazyLock<T, F>` so it is fine
111164
// to not impl `Sync` for `F`
112-
// we do create a `&mut Option<F>` in `force`, but this is
113-
// properly synchronized, so it only happens once
114-
// so it also does not contribute to this impl.
115165
#[unstable(feature = "once_cell", issue = "74465")]
116-
unsafe impl<T, F: Send> Sync for LazyLock<T, F> where OnceLock<T>: Sync {}
166+
unsafe impl<T: Sync + Send, F: Send> Sync for LazyLock<T, F> {}
117167
// auto-derived `Send` impl is OK.
118168

119169
#[unstable(feature = "once_cell", issue = "74465")]
120-
impl<T, F: UnwindSafe> RefUnwindSafe for LazyLock<T, F> where OnceLock<T>: RefUnwindSafe {}
170+
impl<T: RefUnwindSafe + UnwindSafe, F: UnwindSafe> RefUnwindSafe for LazyLock<T, F> {}
121171
#[unstable(feature = "once_cell", issue = "74465")]
122-
impl<T, F: UnwindSafe> UnwindSafe for LazyLock<T, F> where OnceLock<T>: UnwindSafe {}
172+
impl<T: UnwindSafe, F: UnwindSafe> UnwindSafe for LazyLock<T, F> {}
123173

124174
#[cfg(test)]
125175
mod tests;

library/std/src/sync/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ mod condvar;
186186
mod lazy_lock;
187187
mod mpmc;
188188
mod mutex;
189-
mod once;
189+
pub(crate) mod once;
190190
mod once_lock;
191191
mod poison;
192192
mod remutex;

library/std/src/sync/once.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ pub struct OnceState {
4343
pub(crate) inner: sys::OnceState,
4444
}
4545

46+
pub(crate) enum ExclusiveState {
47+
Incomplete,
48+
Poisoned,
49+
Complete,
50+
}
51+
4652
/// Initialization value for static [`Once`] values.
4753
///
4854
/// # Examples
@@ -248,6 +254,16 @@ impl Once {
248254
pub fn is_completed(&self) -> bool {
249255
self.inner.is_completed()
250256
}
257+
258+
/// Returns the current state of the `Once` instance.
259+
///
260+
/// Since this takes a mutable reference, no initialization can currently
261+
/// be running, so the state must be either "incomplete", "poisoned" or
262+
/// "complete".
263+
#[inline]
264+
pub(crate) fn state(&mut self) -> ExclusiveState {
265+
self.inner.state()
266+
}
251267
}
252268

253269
#[stable(feature = "std_debug", since = "1.16.0")]

library/std/src/sys/unsupported/once.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::cell::Cell;
22
use crate::sync as public;
3+
use crate::sync::once::ExclusiveState;
34

45
pub struct Once {
56
state: Cell<State>,
@@ -44,6 +45,16 @@ impl Once {
4445
self.state.get() == State::Complete
4546
}
4647

48+
#[inline]
49+
pub(crate) fn state(&mut self) -> ExclusiveState {
50+
match self.state.get() {
51+
State::Incomplete => ExclusiveState::Incomplete,
52+
State::Poisoned => ExclusiveState::Poisoned,
53+
State::Complete => ExclusiveState::Complete,
54+
_ => unreachable!("invalid Once state"),
55+
}
56+
}
57+
4758
#[cold]
4859
#[track_caller]
4960
pub fn call(&self, ignore_poisoning: bool, f: &mut impl FnMut(&public::OnceState)) {

library/std/src/sys_common/once/futex.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::sync::atomic::{
44
AtomicU32,
55
Ordering::{Acquire, Relaxed, Release},
66
};
7+
use crate::sync::once::ExclusiveState;
78
use crate::sys::futex::{futex_wait, futex_wake_all};
89

910
// On some platforms, the OS is very nice and handles the waiter queue for us.
@@ -78,6 +79,16 @@ impl Once {
7879
self.state.load(Acquire) == COMPLETE
7980
}
8081

82+
#[inline]
83+
pub(crate) fn state(&mut self) -> ExclusiveState {
84+
match *self.state.get_mut() {
85+
INCOMPLETE => ExclusiveState::Incomplete,
86+
POISONED => ExclusiveState::Poisoned,
87+
COMPLETE => ExclusiveState::Complete,
88+
_ => unreachable!("invalid Once state"),
89+
}
90+
}
91+
8192
// This uses FnMut to match the API of the generic implementation. As this
8293
// implementation is quite light-weight, it is generic over the closure and
8394
// so avoids the cost of dynamic dispatch.

library/std/src/sys_common/once/queue.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ use crate::fmt;
6060
use crate::ptr;
6161
use crate::sync as public;
6262
use crate::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
63+
use crate::sync::once::ExclusiveState;
6364
use crate::thread::{self, Thread};
6465

6566
type Masked = ();
@@ -121,6 +122,16 @@ impl Once {
121122
self.state_and_queue.load(Ordering::Acquire).addr() == COMPLETE
122123
}
123124

125+
#[inline]
126+
pub(crate) fn state(&mut self) -> ExclusiveState {
127+
match self.state_and_queue.get_mut().addr() {
128+
INCOMPLETE => ExclusiveState::Incomplete,
129+
POISONED => ExclusiveState::Poisoned,
130+
COMPLETE => ExclusiveState::Complete,
131+
_ => unreachable!("invalid Once state"),
132+
}
133+
}
134+
124135
// This is a non-generic function to reduce the monomorphization cost of
125136
// using `call_once` (this isn't exactly a trivial or small implementation).
126137
//

0 commit comments

Comments
 (0)