Skip to content

Commit 5af5f81

Browse files
committed
Auto merge of rust-lang#122494 - joboet:simplify_key_tls, r=m-ou-se
Simplify key-based thread locals This PR simplifies key-based thread-locals by: * unifying the macro expansion of `const` and non-`const` initializers * reducing the amount of code in the expansion * simply reallocating on recursive initialization instead of going through `LazyKeyInner` * replacing `catch_unwind` with the shared `abort_on_dtor_unwind` It does not change the initialization behaviour described in rust-lang#110897.
2 parents a365890 + 30b5a70 commit 5af5f81

File tree

4 files changed

+63
-189
lines changed

4 files changed

+63
-189
lines changed

std/src/sys/mod.rs

-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ pub mod cmath;
99
pub mod os_str;
1010
pub mod path;
1111
pub mod sync;
12-
#[allow(dead_code)]
13-
#[allow(unused_imports)]
1412
pub mod thread_local;
1513

1614
// FIXME(117276): remove this, move feature implementations into individual

std/src/sys/thread_local/fast_local/lazy.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::cell::UnsafeCell;
22
use crate::hint::unreachable_unchecked;
3-
use crate::mem::forget;
43
use crate::ptr;
54
use crate::sys::thread_local::abort_on_dtor_unwind;
65
use crate::sys::thread_local_dtor::register_dtor;

std/src/sys/thread_local/mod.rs

+3-84
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")]
2+
#![cfg_attr(test, allow(unused))]
23

34
// There are three thread-local implementations: "static", "fast", "OS".
45
// The "OS" thread local key type is accessed via platform-specific API calls and is slow, while the
@@ -24,94 +25,12 @@ cfg_if::cfg_if! {
2425
}
2526
}
2627

27-
// Not used by the fast-local TLS anymore.
28-
// FIXME(#110897): remove this.
29-
#[allow(unused)]
30-
mod lazy {
31-
use crate::cell::UnsafeCell;
32-
use crate::hint;
33-
use crate::mem;
34-
35-
pub struct LazyKeyInner<T> {
36-
inner: UnsafeCell<Option<T>>,
37-
}
38-
39-
impl<T> LazyKeyInner<T> {
40-
pub const fn new() -> LazyKeyInner<T> {
41-
LazyKeyInner { inner: UnsafeCell::new(None) }
42-
}
43-
44-
pub unsafe fn get(&self) -> Option<&'static T> {
45-
// SAFETY: The caller must ensure no reference is ever handed out to
46-
// the inner cell nor mutable reference to the Option<T> inside said
47-
// cell. This make it safe to hand a reference, though the lifetime
48-
// of 'static is itself unsafe, making the get method unsafe.
49-
unsafe { (*self.inner.get()).as_ref() }
50-
}
51-
52-
/// The caller must ensure that no reference is active: this method
53-
/// needs unique access.
54-
pub unsafe fn initialize<F: FnOnce() -> T>(&self, init: F) -> &'static T {
55-
// Execute the initialization up front, *then* move it into our slot,
56-
// just in case initialization fails.
57-
let value = init();
58-
let ptr = self.inner.get();
59-
60-
// SAFETY:
61-
//
62-
// note that this can in theory just be `*ptr = Some(value)`, but due to
63-
// the compiler will currently codegen that pattern with something like:
64-
//
65-
// ptr::drop_in_place(ptr)
66-
// ptr::write(ptr, Some(value))
67-
//
68-
// Due to this pattern it's possible for the destructor of the value in
69-
// `ptr` (e.g., if this is being recursively initialized) to re-access
70-
// TLS, in which case there will be a `&` and `&mut` pointer to the same
71-
// value (an aliasing violation). To avoid setting the "I'm running a
72-
// destructor" flag we just use `mem::replace` which should sequence the
73-
// operations a little differently and make this safe to call.
74-
//
75-
// The precondition also ensures that we are the only one accessing
76-
// `self` at the moment so replacing is fine.
77-
unsafe {
78-
let _ = mem::replace(&mut *ptr, Some(value));
79-
}
80-
81-
// SAFETY: With the call to `mem::replace` it is guaranteed there is
82-
// a `Some` behind `ptr`, not a `None` so `unreachable_unchecked`
83-
// will never be reached.
84-
unsafe {
85-
// After storing `Some` we want to get a reference to the contents of
86-
// what we just stored. While we could use `unwrap` here and it should
87-
// always work it empirically doesn't seem to always get optimized away,
88-
// which means that using something like `try_with` can pull in
89-
// panicking code and cause a large size bloat.
90-
match *ptr {
91-
Some(ref x) => x,
92-
None => hint::unreachable_unchecked(),
93-
}
94-
}
95-
}
96-
97-
/// Watch out: unsynchronized internal mutability!
98-
///
99-
/// # Safety
100-
/// Causes UB if any reference to the value is used after this.
101-
#[allow(unused)]
102-
pub(crate) unsafe fn take(&self) -> Option<T> {
103-
let mutable: *mut _ = UnsafeCell::get(&self.inner);
104-
// SAFETY: That's the caller's problem.
105-
unsafe { mutable.replace(None) }
106-
}
107-
}
108-
}
109-
11028
/// Run a callback in a scenario which must not unwind (such as a `extern "C"
11129
/// fn` declared in a user crate). If the callback unwinds anyway, then
11230
/// `rtabort` with a message about thread local panicking on drop.
11331
#[inline]
114-
pub fn abort_on_dtor_unwind(f: impl FnOnce()) {
32+
#[allow(dead_code)]
33+
fn abort_on_dtor_unwind(f: impl FnOnce()) {
11534
// Using a guard like this is lower cost.
11635
let guard = DtorUnwindGuard;
11736
f();

std/src/sys/thread_local/os_local.rs

+60-102
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use super::lazy::LazyKeyInner;
1+
use super::abort_on_dtor_unwind;
22
use crate::cell::Cell;
3-
use crate::sys_common::thread_local_key::StaticKey as OsStaticKey;
4-
use crate::{fmt, marker, panic, ptr};
3+
use crate::marker::PhantomData;
4+
use crate::ptr;
5+
use crate::sys_common::thread_local_key::StaticKey as OsKey;
56

67
#[doc(hidden)]
78
#[allow_internal_unstable(thread_local_internals)]
@@ -10,38 +11,9 @@ use crate::{fmt, marker, panic, ptr};
1011
#[rustc_macro_transparency = "semitransparent"]
1112
pub macro thread_local_inner {
1213
// used to generate the `LocalKey` value for const-initialized thread locals
13-
(@key $t:ty, const $init:expr) => {{
14-
#[inline]
15-
#[deny(unsafe_op_in_unsafe_fn)]
16-
unsafe fn __getit(
17-
_init: $crate::option::Option<&mut $crate::option::Option<$t>>,
18-
) -> $crate::option::Option<&'static $t> {
19-
const INIT_EXPR: $t = $init;
20-
21-
// On platforms without `#[thread_local]` we fall back to the
22-
// same implementation as below for os thread locals.
23-
#[inline]
24-
const fn __init() -> $t { INIT_EXPR }
25-
static __KEY: $crate::thread::local_impl::Key<$t> =
26-
$crate::thread::local_impl::Key::new();
27-
unsafe {
28-
__KEY.get(move || {
29-
if let $crate::option::Option::Some(init) = _init {
30-
if let $crate::option::Option::Some(value) = init.take() {
31-
return value;
32-
} else if $crate::cfg!(debug_assertions) {
33-
$crate::unreachable!("missing initial value");
34-
}
35-
}
36-
__init()
37-
})
38-
}
39-
}
40-
41-
unsafe {
42-
$crate::thread::LocalKey::new(__getit)
43-
}
44-
}},
14+
(@key $t:ty, const $init:expr) => {
15+
$crate::thread::local_impl::thread_local_inner!(@key $t, { const INIT_EXPR: $t = $init; INIT_EXPR })
16+
},
4517

4618
// used to generate the `LocalKey` value for `thread_local!`
4719
(@key $t:ty, $init:expr) => {
@@ -55,20 +27,11 @@ pub macro thread_local_inner {
5527
unsafe fn __getit(
5628
init: $crate::option::Option<&mut $crate::option::Option<$t>>,
5729
) -> $crate::option::Option<&'static $t> {
58-
static __KEY: $crate::thread::local_impl::Key<$t> =
59-
$crate::thread::local_impl::Key::new();
30+
use $crate::thread::local_impl::Key;
6031

32+
static __KEY: Key<$t> = Key::new();
6133
unsafe {
62-
__KEY.get(move || {
63-
if let $crate::option::Option::Some(init) = init {
64-
if let $crate::option::Option::Some(value) = init.take() {
65-
return value;
66-
} else if $crate::cfg!(debug_assertions) {
67-
$crate::unreachable!("missing default value");
68-
}
69-
}
70-
__init()
71-
})
34+
__KEY.get(init, __init)
7235
}
7336
}
7437

@@ -85,78 +48,78 @@ pub macro thread_local_inner {
8548

8649
/// Use a regular global static to store this key; the state provided will then be
8750
/// thread-local.
51+
#[allow(missing_debug_implementations)]
8852
pub struct Key<T> {
89-
// OS-TLS key that we'll use to key off.
90-
os: OsStaticKey,
91-
marker: marker::PhantomData<Cell<T>>,
92-
}
93-
94-
impl<T> fmt::Debug for Key<T> {
95-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96-
f.debug_struct("Key").finish_non_exhaustive()
97-
}
53+
os: OsKey,
54+
marker: PhantomData<Cell<T>>,
9855
}
9956

10057
unsafe impl<T> Sync for Key<T> {}
10158

10259
struct Value<T: 'static> {
103-
inner: LazyKeyInner<T>,
60+
value: T,
10461
key: &'static Key<T>,
10562
}
10663

10764
impl<T: 'static> Key<T> {
10865
#[rustc_const_unstable(feature = "thread_local_internals", issue = "none")]
10966
pub const fn new() -> Key<T> {
110-
Key { os: OsStaticKey::new(Some(destroy_value::<T>)), marker: marker::PhantomData }
67+
Key { os: OsKey::new(Some(destroy_value::<T>)), marker: PhantomData }
11168
}
11269

113-
/// It is a requirement for the caller to ensure that no mutable
114-
/// reference is active when this method is called.
115-
pub unsafe fn get(&'static self, init: impl FnOnce() -> T) -> Option<&'static T> {
116-
// SAFETY: See the documentation for this method.
70+
/// Get the value associated with this key, initializating it if necessary.
71+
///
72+
/// # Safety
73+
/// * the returned reference must not be used after recursive initialization
74+
/// or thread destruction occurs.
75+
pub unsafe fn get(
76+
&'static self,
77+
i: Option<&mut Option<T>>,
78+
f: impl FnOnce() -> T,
79+
) -> Option<&'static T> {
80+
// SAFETY: (FIXME: get should actually be safe)
11781
let ptr = unsafe { self.os.get() as *mut Value<T> };
11882
if ptr.addr() > 1 {
11983
// SAFETY: the check ensured the pointer is safe (its destructor
12084
// is not running) + it is coming from a trusted source (self).
121-
if let Some(ref value) = unsafe { (*ptr).inner.get() } {
122-
return Some(value);
123-
}
85+
unsafe { Some(&(*ptr).value) }
86+
} else {
87+
// SAFETY: At this point we are sure we have no value and so
88+
// initializing (or trying to) is safe.
89+
unsafe { self.try_initialize(ptr, i, f) }
12490
}
125-
// SAFETY: At this point we are sure we have no value and so
126-
// initializing (or trying to) is safe.
127-
unsafe { self.try_initialize(init) }
12891
}
12992

130-
// `try_initialize` is only called once per os thread local variable,
131-
// except in corner cases where thread_local dtors reference other
132-
// thread_local's, or it is being recursively initialized.
133-
unsafe fn try_initialize(&'static self, init: impl FnOnce() -> T) -> Option<&'static T> {
134-
// SAFETY: No mutable references are ever handed out meaning getting
135-
// the value is ok.
136-
let ptr = unsafe { self.os.get() as *mut Value<T> };
93+
unsafe fn try_initialize(
94+
&'static self,
95+
ptr: *mut Value<T>,
96+
i: Option<&mut Option<T>>,
97+
f: impl FnOnce() -> T,
98+
) -> Option<&'static T> {
13799
if ptr.addr() == 1 {
138100
// destructor is running
139101
return None;
140102
}
141103

142-
let ptr = if ptr.is_null() {
143-
// If the lookup returned null, we haven't initialized our own
144-
// local copy, so do that now.
145-
let ptr = Box::into_raw(Box::new(Value { inner: LazyKeyInner::new(), key: self }));
146-
// SAFETY: At this point we are sure there is no value inside
147-
// ptr so setting it will not affect anyone else.
148-
unsafe {
149-
self.os.set(ptr as *mut u8);
150-
}
151-
ptr
152-
} else {
153-
// recursive initialization
154-
ptr
155-
};
104+
let value = i.and_then(Option::take).unwrap_or_else(f);
105+
let ptr = Box::into_raw(Box::new(Value { value, key: self }));
106+
// SAFETY: (FIXME: get should actually be safe)
107+
let old = unsafe { self.os.get() as *mut Value<T> };
108+
// SAFETY: `ptr` is a correct pointer that can be destroyed by the key destructor.
109+
unsafe {
110+
self.os.set(ptr as *mut u8);
111+
}
112+
if !old.is_null() {
113+
// If the variable was recursively initialized, drop the old value.
114+
// SAFETY: We cannot be inside a `LocalKey::with` scope, as the
115+
// initializer has already returned and the next scope only starts
116+
// after we return the pointer. Therefore, there can be no references
117+
// to the old value.
118+
drop(unsafe { Box::from_raw(old) });
119+
}
156120

157-
// SAFETY: ptr has been ensured as non-NUL just above an so can be
158-
// dereferenced safely.
159-
unsafe { Some((*ptr).inner.initialize(init)) }
121+
// SAFETY: We just created this value above.
122+
unsafe { Some(&(*ptr).value) }
160123
}
161124
}
162125

@@ -170,16 +133,11 @@ unsafe extern "C" fn destroy_value<T: 'static>(ptr: *mut u8) {
170133
//
171134
// Note that to prevent an infinite loop we reset it back to null right
172135
// before we return from the destructor ourselves.
173-
//
174-
// Wrap the call in a catch to ensure unwinding is caught in the event
175-
// a panic takes place in a destructor.
176-
if let Err(_) = panic::catch_unwind(|| unsafe {
177-
let ptr = Box::from_raw(ptr as *mut Value<T>);
136+
abort_on_dtor_unwind(|| {
137+
let ptr = unsafe { Box::from_raw(ptr as *mut Value<T>) };
178138
let key = ptr.key;
179-
key.os.set(ptr::without_provenance_mut(1));
139+
unsafe { key.os.set(ptr::without_provenance_mut(1)) };
180140
drop(ptr);
181-
key.os.set(ptr::null_mut());
182-
}) {
183-
rtabort!("thread local panicked on drop");
184-
}
141+
unsafe { key.os.set(ptr::null_mut()) };
142+
});
185143
}

0 commit comments

Comments
 (0)