Auto merge of #123724 - joboet:static_tls, r=m-ou-se

Rewrite TLS on platforms without threads

The saga of #110897 continues!

r? `@m-ou-se` if you have time
This commit is contained in:
bors 2024-05-24 00:56:29 +00:00
commit 78dd504f2f
2 changed files with 76 additions and 70 deletions

View File

@ -10,7 +10,7 @@ cfg_if::cfg_if! {
#[doc(hidden)]
mod static_local;
#[doc(hidden)]
pub use static_local::{Key, thread_local_inner};
pub use static_local::{EagerStorage, LazyStorage, thread_local_inner};
} else if #[cfg(target_thread_local)] {
#[doc(hidden)]
mod fast_local;

View File

@ -1,5 +1,7 @@
use super::lazy::LazyKeyInner;
use crate::fmt;
//! On some targets like wasm there's no threads, so no need to generate
//! thread locals and we can instead just use plain statics!
use crate::cell::UnsafeCell;
#[doc(hidden)]
#[allow_internal_unstable(thread_local_internals)]
@ -9,22 +11,17 @@ use crate::fmt;
pub macro thread_local_inner {
// used to generate the `LocalKey` value for const-initialized thread locals
(@key $t:ty, const $init:expr) => {{
#[inline] // see comments below
const __INIT: $t = $init;
#[inline]
#[deny(unsafe_op_in_unsafe_fn)]
unsafe fn __getit(
_init: $crate::option::Option<&mut $crate::option::Option<$t>>,
) -> $crate::option::Option<&'static $t> {
const INIT_EXPR: $t = $init;
use $crate::thread::local_impl::EagerStorage;
// wasm without atomics maps directly to `static mut`, and dtors
// aren't implemented because thread dtors aren't really a thing
// on wasm right now
//
// FIXME(#84224) this should come after the `target_thread_local`
// block.
static mut VAL: $t = INIT_EXPR;
// SAFETY: we only ever create shared references, so there's no mutable aliasing.
unsafe { $crate::option::Option::Some(&*$crate::ptr::addr_of!(VAL)) }
static VAL: EagerStorage<$t> = EagerStorage { value: __INIT };
$crate::option::Option::Some(&VAL.value)
}
unsafe {
@ -33,74 +30,83 @@ pub macro thread_local_inner {
}},
// used to generate the `LocalKey` value for `thread_local!`
(@key $t:ty, $init:expr) => {
{
#[inline]
fn __init() -> $t { $init }
#[inline]
unsafe fn __getit(
init: $crate::option::Option<&mut $crate::option::Option<$t>>,
) -> $crate::option::Option<&'static $t> {
static __KEY: $crate::thread::local_impl::Key<$t> =
$crate::thread::local_impl::Key::new();
(@key $t:ty, $init:expr) => {{
#[inline]
fn __init() -> $t { $init }
unsafe {
__KEY.get(move || {
if let $crate::option::Option::Some(init) = init {
if let $crate::option::Option::Some(value) = init.take() {
return value;
} else if $crate::cfg!(debug_assertions) {
$crate::unreachable!("missing default value");
}
}
__init()
})
}
}
#[inline]
#[deny(unsafe_op_in_unsafe_fn)]
unsafe fn __getit(
init: $crate::option::Option<&mut $crate::option::Option<$t>>,
) -> $crate::option::Option<&'static $t> {
use $crate::thread::local_impl::LazyStorage;
unsafe {
$crate::thread::LocalKey::new(__getit)
}
static VAL: LazyStorage<$t> = LazyStorage::new();
unsafe { $crate::option::Option::Some(VAL.get(init, __init)) }
}
},
unsafe {
$crate::thread::LocalKey::new(__getit)
}
}},
($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => {
$(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> =
$crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
},
}
/// On some targets like wasm there's no threads, so no need to generate
/// thread locals and we can instead just use plain statics!
pub struct Key<T> {
inner: LazyKeyInner<T>,
#[allow(missing_debug_implementations)]
pub struct EagerStorage<T> {
pub value: T,
}
unsafe impl<T> Sync for Key<T> {}
// SAFETY: the target doesn't have threads.
unsafe impl<T> Sync for EagerStorage<T> {}
impl<T> fmt::Debug for Key<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Key").finish_non_exhaustive()
#[allow(missing_debug_implementations)]
pub struct LazyStorage<T> {
value: UnsafeCell<Option<T>>,
}
impl<T> LazyStorage<T> {
pub const fn new() -> LazyStorage<T> {
LazyStorage { value: UnsafeCell::new(None) }
}
/// Gets a reference to the contained value, initializing it if necessary.
///
/// # Safety
/// The returned reference may not be used after reentrant initialization has occurred.
#[inline]
pub unsafe fn get(
&'static self,
i: Option<&mut Option<T>>,
f: impl FnOnce() -> T,
) -> &'static T {
let value = unsafe { &*self.value.get() };
match value {
Some(v) => v,
None => self.initialize(i, f),
}
}
#[cold]
unsafe fn initialize(
&'static self,
i: Option<&mut Option<T>>,
f: impl FnOnce() -> T,
) -> &'static T {
let value = i.and_then(Option::take).unwrap_or_else(f);
// Destroy the old value, after updating the TLS variable as the
// destructor might reference it.
// FIXME(#110897): maybe panic on recursive initialization.
unsafe {
self.value.get().replace(Some(value));
}
// SAFETY: we just set this to `Some`.
unsafe { (*self.value.get()).as_ref().unwrap_unchecked() }
}
}
impl<T> Key<T> {
pub const fn new() -> Key<T> {
Key { inner: LazyKeyInner::new() }
}
pub unsafe fn get(&self, init: impl FnOnce() -> T) -> Option<&'static T> {
// SAFETY: The caller must ensure no reference is ever handed out to
// the inner cell nor mutable reference to the Option<T> inside said
// cell. This make it safe to hand a reference, though the lifetime
// of 'static is itself unsafe, making the get method unsafe.
let value = unsafe {
match self.inner.get() {
Some(ref value) => value,
None => self.inner.initialize(init),
}
};
Some(value)
}
}
// SAFETY: the target doesn't have threads.
unsafe impl<T> Sync for LazyStorage<T> {}