From a733d5a0793b10fef01320e0e1c96f1e07f13d08 Mon Sep 17 00:00:00 2001 From: joboet Date: Tue, 19 May 2026 11:40:29 +0200 Subject: [PATCH] std: store key-based TLS variables in a hashmap --- library/std/src/sys/thread_local/guard/key.rs | 30 -- library/std/src/sys/thread_local/key_based.rs | 367 ++++++++++++++++++ library/std/src/sys/thread_local/mod.rs | 13 +- library/std/src/sys/thread_local/os.rs | 271 ------------- library/std/tests/thread.rs | 4 +- 5 files changed, 377 insertions(+), 308 deletions(-) create mode 100644 library/std/src/sys/thread_local/key_based.rs delete mode 100644 library/std/src/sys/thread_local/os.rs diff --git a/library/std/src/sys/thread_local/guard/key.rs b/library/std/src/sys/thread_local/guard/key.rs index f91471419c1cc..19425365ab8f7 100644 --- a/library/std/src/sys/thread_local/guard/key.rs +++ b/library/std/src/sys/thread_local/guard/key.rs @@ -5,7 +5,6 @@ use crate::ptr; use crate::sys::thread_local::key::{LazyKey, set}; -#[cfg(target_thread_local)] pub fn enable() { use crate::sys::thread_local::destructors; @@ -29,32 +28,3 @@ pub fn enable() { } } } - -/// On platforms with key-based TLS, the system runs the destructors for us. -/// We still have to make sure that [`crate::rt::thread_cleanup`] is called, -/// however. This is done by deferring the execution of a TLS destructor to -/// the next round of destruction inside the TLS destructors. -#[cfg(not(target_thread_local))] -pub fn enable() { - const DEFER: *mut u8 = ptr::without_provenance_mut(1); - const RUN: *mut u8 = ptr::without_provenance_mut(2); - - static CLEANUP: LazyKey = LazyKey::new(Some(run)); - - unsafe { set(CLEANUP.force(), DEFER) } - - unsafe extern "C" fn run(state: *mut u8) { - if state == DEFER { - // Make sure that this function is run again in the next round of - // TLS destruction. If there is no further round, there will be leaks, - // but that's okay, `thread_cleanup` is not guaranteed to be called. - unsafe { set(CLEANUP.force(), RUN) } - } else { - debug_assert_eq!(state, RUN); - // If the state is still RUN in the next round of TLS destruction, - // it means that no other TLS destructors defined by this runtime - // have been run, as they would have set the state to DEFER. - crate::rt::thread_cleanup(); - } - } -} diff --git a/library/std/src/sys/thread_local/key_based.rs b/library/std/src/sys/thread_local/key_based.rs new file mode 100644 index 0000000000000..7de53bbd10040 --- /dev/null +++ b/library/std/src/sys/thread_local/key_based.rs @@ -0,0 +1,367 @@ +use hashbrown::HashTable; +use hashbrown::hash_table::Entry; + +use super::abort_on_dtor_unwind; +use super::key::{LazyKey, get, set}; +use crate::alloc::{Allocator, Layout, System, handle_alloc_error}; +use crate::cell::{Cell, RefCell}; +use crate::marker::PhantomData; +use crate::mem::{forget, needs_drop, replace, transmute}; +use crate::ptr::{self, NonNull, drop_in_place}; +use crate::rt::thread_cleanup; + +#[doc(hidden)] +#[allow_internal_unstable(thread_local_internals)] +#[allow_internal_unsafe] +#[unstable(feature = "thread_local_internals", issue = "none")] +#[rustc_macro_transparency = "semiopaque"] +pub macro thread_local_inner { + // NOTE: we cannot import `Key` or `LocalKey` with a `use` because that can shadow user + // provided type or type alias with a matching name. Please update the shadowing test in + // `tests/thread.rs` if these types are renamed. + + // used to generate the `LocalKey` value for `thread_local!`. + (@key $t:ty, $($(#[$($align_attr:tt)*])+)?, $init:expr) => {{ + #[inline] + fn __rust_std_internal_init_fn() -> $t { $init } + + unsafe { + $crate::thread::LocalKey::new(|__rust_std_internal_init| { + static __RUST_STD_INTERNAL_VAL: $crate::thread::local_impl::Key<$t> + = $crate::thread::local_impl::Key::new({ + $({ + // Ensure that attributes have valid syntax + // and that the proper feature gate is enabled + $(#[$($align_attr)*])+ + #[allow(unused)] + static DUMMY: () = (); + })? + + #[allow(unused_mut)] + let mut final_align = 1; + $($($crate::thread::local_impl::thread_local_inner!(@align final_align, $($align_attr)*);)+)? + final_align + }); + + __RUST_STD_INTERNAL_VAL.get(__rust_std_internal_init, __rust_std_internal_init_fn) + }) + } + }}, + + // process a single `rustc_align_static` attribute + (@align $final_align:ident, rustc_align_static($($align:tt)*) $(, $($attr_rest:tt)+)?) => { + let new_align: $crate::primitive::usize = $($align)*; + if new_align > $final_align { + $final_align = new_align; + } + + $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)? + }, + + // process a single `cfg_attr` attribute + // by translating it into a `cfg`ed block and recursing. + // https://doc.rust-lang.org/reference/conditional-compilation.html#railroad-ConfigurationPredicate + + (@align $final_align:ident, cfg_attr($cfg_pred:expr, $($cfg_rhs:tt)*) $(, $($attr_rest:tt)+)?) => { + #[cfg($cfg_pred)] + { + $crate::thread::local_impl::thread_local_inner!(@align $final_align, $($cfg_rhs)*); + } + + $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)? + }, +} + +// TLS keys are a *very* limited resource. Thus, to conserve them, this TLS +// implementation stores all variables in a per-thread map, with the key being +// the address of a `static` variable (these are unique even for immutable values). +// The map holds pointers to the individual variable's storage, as well as the +// metadata describing its memory layout. +// +// Additionally, we keep a list of the value destructors for types that need +// them and run them before deallocating the storage of values that do not need +// destruction, which matches the behaviour of the native TLS implementation. + +#[expect(missing_debug_implementations)] +pub struct Key { + // `Key` must not be zero-sized, since it has to have a unique address. + // Thus we can conserve space by also storing the requested alignment here + // (we store the power-of-two instead of the actual alignment to keep `Key` + // as small as possible). + align: u8, + _ty: PhantomData>, +} + +impl Key { + pub const fn new(align: usize) -> Key { + let Ok(layout) = Layout::new::().align_to(align) else { + panic!("invalid alignment for TLS variable"); + }; + Key { align: layout.align().ilog2() as u8, _ty: PhantomData } + } + + pub fn get(&'static self, i: Option<&mut Option>, f: impl FnOnce() -> T) -> *const T { + let key = <*const u8>::addr(&self.align); + if let Some(ptr) = find(key) { + // Either the value was initialized previously or has already been + // destroyed. Both scenarios are handled by the consumer of the + // pointer. + ptr.map_or(ptr::null_mut(), |p| p.as_ptr()).cast() + } else { + // The value has not been initialized on this thread yet. + + let val = i.and_then(Option::take).unwrap_or_else(f); + + // SAFETY: we ensure that `self.align` is sufficient for `T` in `new`. + let value = unsafe { Value::new(&self.align, val) }; + let dtor = if needs_drop::() { + // SAFETY: thin pointers are ABI compatible regardless of their type. + Some(unsafe { + transmute::(drop_in_place::) + }) + } else { + None + }; + + // Note that if `f` recursively initialized the variable, this will + // overwrite the existing value. We might want to change that in the + // future, but for now let's preserve that (sound) behaviour. + // (c.f. https://github.com/rust-lang/rust/issues/110897#issuecomment-1525705682 + // and the ensuing discussion) + unsafe { insert(value, dtor).map_or(ptr::null_mut(), |p| p.as_ptr()).cast() } + } + } +} + +unsafe impl Send for Key {} +unsafe impl Sync for Key {} + +struct Value { + key_align: &'static u8, + size: usize, + // A pointer to the value. If there is none, it means the value has been + // destroyed already. + ptr: Option>, +} + +impl Value { + /// Creates a new `Value` by allocating storage for `T` with alignment + /// matching the requested `key_align` and storing `val` in it. + /// + /// # Safety + /// `1 << key_align` must be higher than the minimum alignment of `T`. + unsafe fn new(key_align: &'static u8, val: T) -> Value { + debug_assert!((1 << key_align) >= align_of::()); + let mut value = Value { key_align, size: size_of::(), ptr: None }; + + let layout = value.layout(); + value.ptr = if layout.size() != 0 { + let Ok(ptr) = System.allocate(layout) else { handle_alloc_error(layout) }; + + // SAFETY: `ptr` points to valid memory with a layout sufficient + // to hold `T`. + unsafe { ptr.cast::().write(val) }; + Some(ptr.cast()) + } else { + // Forge a pointer with the requested alignment. Since `T` is zero- + // sized, there is nothing to store. But do avoid dropping `val`. + forget(val); + Some(layout.dangling_ptr()) + }; + + value + } + + fn layout(&self) -> Layout { + let align = 1 << self.key_align; + Layout::from_size_align(self.size, align).unwrap() + } + + fn key(&self) -> usize { + <*const u8>::addr(self.key_align) + } + + fn hash(&self) -> u64 { + // Its quite likely that all `Key`s are laid out right next to each + // other, in which case the lowest bits will have the highest variance. + // Since `HashTable` uses the lowest bits for its bucked index that + // makes the key good enough as a hash. If this turns out to result in + // too many conflicts, one might try incorporating the the page number + // into the top 7 bits (which are used in the hash table control bytes). + self.key() as u64 + } +} + +static STORAGE_KEY: LazyKey = LazyKey::new(Some(cleanup)); + +struct Storage { + vals: HashTable, + dtors: Vec<(usize, unsafe fn(*mut u8)), System>, +} + +/// Preinitialize the thread-local state. +/// +/// This also ensures that `thread_cleanup` is run, even if no other variables +/// are initialized. +#[cfg(not(any(target_os = "windows", target_os = "xous")))] +pub(crate) fn init() { + let key = STORAGE_KEY.force(); + if unsafe { get(key).is_null() } { + let (storage, _) = Box::into_raw_with_allocator(Box::new_in( + RefCell::new(Storage { vals: HashTable::new_in(System), dtors: Vec::new_in(System) }), + System, + )); + unsafe { set(key, storage.cast()) }; + } +} + +/// Finds the pointer associated with the specified key, if it has been +/// initialized already. +fn find(key: usize) -> Option>> { + let storage_key = STORAGE_KEY.force(); + // Retrieve the thread-local storage. If it has not been initialized yet, + // the value can't have been initialized either – except if thread-local + // storage has previously been destroyed. But its impossible to detect that + // without causing other issues (such as previously-uninitialized variables + // not being available in foreign thread-local destructors). This is + // irrelevant for nearly all users anyway, since there is no way in `std` to + // run code after the TLS destructors. + let storage = unsafe { get(storage_key).cast::>().as_ref()? }; + storage.borrow().vals.find(key as u64, |v| v.key() == key).map(|v| v.ptr) +} + +/// Inserts `value` and its corresponding destructor, potentially replacing a +/// previous value (and destroying it with `dtor` if applicable). +unsafe fn insert(value: Value, dtor: Option) -> Option> { + let ptr = value.ptr; + + let storage_key = STORAGE_KEY.force(); + let storage = unsafe { get(storage_key).cast::>().as_ref() }; + if let Some(storage) = storage { + let mut storage = storage.borrow_mut(); + match storage.vals.entry(value.hash(), |v| v.key() == value.key(), Value::hash) { + Entry::Occupied(mut existing) => { + let existing = existing.get_mut(); + debug_assert_eq!(existing.size, value.size); + + if let Some(old) = replace(&mut existing.ptr, ptr) { + let layout = existing.layout(); + // There was a value already in place. Overwrite it. + + // Release the borrow to allow access to other TLS variables + // in the destructor. + drop(storage); + + if let Some(dtor) = dtor { + unsafe { dtor(old.as_ptr()) }; + } + + if layout.size() != 0 { + unsafe { System.deallocate(old, layout) }; + } + } + } + Entry::Vacant(vacancy) => { + let key = value.key(); + vacancy.insert(value); + if let Some(dtor) = dtor { + storage.dtors.push((key, dtor)); + } + } + } + } else { + // If the thread-local storage has not been allocated yet, initialize it + // and take the opportunity to pre-insert the value before wrapping the + // storage in a `RefCell`. + let mut dtors = Vec::new_in(System); + if let Some(dtor) = dtor { + dtors.push((value.key(), dtor)); + } + + let mut vals = HashTable::new_in(System); + vals.insert_unique(value.hash(), value, Value::hash); + + let (storage, _) = Box::into_raw_with_allocator(Box::new_in( + RefCell::new(Storage { vals, dtors }), + System, + )); + unsafe { set(storage_key, storage.cast()) }; + } + + // Return the pointer to allow this function to be tail-called from `get`. + ptr +} + +unsafe extern "C" fn cleanup(storage: *mut u8) { + let storage_key = STORAGE_KEY.force(); + // Restore the pointer to the thread-local state so that destructors may + // access other TLS variables. + unsafe { set(storage_key, storage) }; + + let storage = storage as *mut RefCell; + let store = unsafe { &*(storage as *const RefCell) }; + let mut s = store.borrow_mut(); + while let Some((key, dtor)) = s.dtors.pop() { + let value = s.vals.find_mut(key as u64, |v| v.key() == key); + if let Some(value) = value { + // Mark the value as destroyed. + if let Some(ptr) = value.ptr.take() { + let layout = value.layout(); + // Release the borrow to allow access to TLS variables in the + // destructor. + drop(s); + + abort_on_dtor_unwind(|| unsafe { dtor(ptr.as_ptr()) }); + + if layout.size() != 0 { + unsafe { System.deallocate(ptr, layout) }; + } + + s = store.borrow_mut(); + } + } + } + + drop(s); + thread_cleanup(); + + // Unregister the thread-local state. + unsafe { set(storage_key, ptr::null_mut()) }; + let mut storage = unsafe { Box::from_raw_in(storage, System) }; + + // Deallocate the storage of all remaining variables. + for value in &mut storage.get_mut().vals { + if let Some(ptr) = value.ptr + && value.size != 0 + { + unsafe { System.deallocate(ptr, value.layout()) }; + } + } +} + +#[rustc_macro_transparency = "semiopaque"] +pub(crate) macro local_pointer { + () => {}, + ($vis:vis static $name:ident; $($rest:tt)*) => { + $vis static $name: $crate::sys::thread_local::LocalPointer = $crate::sys::thread_local::LocalPointer::__new(); + $crate::sys::thread_local::local_pointer! { $($rest)* } + }, +} + +pub(crate) struct LocalPointer { + key: LazyKey, +} + +impl LocalPointer { + pub const fn __new() -> LocalPointer { + LocalPointer { key: LazyKey::new(None) } + } + + pub fn get(&'static self) -> *mut () { + unsafe { get(self.key.force()) as *mut () } + } + + pub fn set(&'static self, p: *mut ()) { + unsafe { set(self.key.force(), p as *mut u8) } + } +} diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs index e88011aa22dad..64e9dbaf11932 100644 --- a/library/std/src/sys/thread_local/mod.rs +++ b/library/std/src/sys/thread_local/mod.rs @@ -41,9 +41,9 @@ cfg_select! { pub(crate) use native::{LocalPointer, local_pointer}; } _ => { - mod os; - pub use os::{Storage, thread_local_inner, value_align}; - pub(crate) use os::{LocalPointer, local_pointer}; + mod key_based; + pub use key_based::{Key, thread_local_inner}; + pub(crate) use key_based::{LocalPointer, local_pointer}; } } @@ -127,10 +127,13 @@ pub(crate) mod guard { mod solid; pub(crate) use solid::enable; } - _ => { + target_thread_local => { mod key; pub(crate) use key::enable; } + _ => { + pub(crate) use super::key_based::init as enable; + } } } @@ -166,7 +169,7 @@ pub(crate) mod key { #[cfg(test)] mod tests; mod windows; - pub(super) use windows::{Key, LazyKey, get, run_dtors, set}; + pub(super) use windows::{LazyKey, get, run_dtors, set}; } all(target_vendor = "fortanix", target_env = "sgx") => { mod racy; diff --git a/library/std/src/sys/thread_local/os.rs b/library/std/src/sys/thread_local/os.rs deleted file mode 100644 index e1d9d80c97b62..0000000000000 --- a/library/std/src/sys/thread_local/os.rs +++ /dev/null @@ -1,271 +0,0 @@ -use super::key::{Key, LazyKey, get, set}; -use super::{abort_on_dtor_unwind, guard}; -use crate::alloc::{self, GlobalAlloc, Layout, System}; -use crate::cell::Cell; -use crate::marker::PhantomData; -use crate::mem::ManuallyDrop; -use crate::ops::Deref; -use crate::panic::{AssertUnwindSafe, catch_unwind, resume_unwind}; -use crate::ptr::{self, NonNull}; - -#[doc(hidden)] -#[allow_internal_unstable(thread_local_internals)] -#[allow_internal_unsafe] -#[unstable(feature = "thread_local_internals", issue = "none")] -#[rustc_macro_transparency = "semiopaque"] -pub macro thread_local_inner { - // NOTE: we cannot import `Storage` or `LocalKey` with a `use` because that can shadow user - // provided type or type alias with a matching name. Please update the shadowing test in - // `tests/thread.rs` if these types are renamed. - - // used to generate the `LocalKey` value for `thread_local!`. - (@key $t:ty, $($(#[$($align_attr:tt)*])+)?, $init:expr) => {{ - #[inline] - fn __rust_std_internal_init_fn() -> $t { $init } - - // NOTE: this cannot import `LocalKey` or `Storage` with a `use` because that can shadow - // user provided type or type alias with a matching name. Please update the shadowing test - // in `tests/thread.rs` if these types are renamed. - unsafe { - $crate::thread::LocalKey::new(|__rust_std_internal_init| { - static __RUST_STD_INTERNAL_VAL: $crate::thread::local_impl::Storage<$t, { - $({ - // Ensure that attributes have valid syntax - // and that the proper feature gate is enabled - $(#[$($align_attr)*])+ - #[allow(unused)] - static DUMMY: () = (); - })? - - #[allow(unused_mut)] - let mut final_align = $crate::thread::local_impl::value_align::<$t>(); - $($($crate::thread::local_impl::thread_local_inner!(@align final_align, $($align_attr)*);)+)? - final_align - }> - = $crate::thread::local_impl::Storage::new(); - __RUST_STD_INTERNAL_VAL.get(__rust_std_internal_init, __rust_std_internal_init_fn) - }) - } - }}, - - // process a single `rustc_align_static` attribute - (@align $final_align:ident, rustc_align_static($($align:tt)*) $(, $($attr_rest:tt)+)?) => { - let new_align: $crate::primitive::usize = $($align)*; - if new_align > $final_align { - $final_align = new_align; - } - - $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)? - }, - - // process a single `cfg_attr` attribute - // by translating it into a `cfg`ed block and recursing. - // https://doc.rust-lang.org/reference/conditional-compilation.html#railroad-ConfigurationPredicate - - (@align $final_align:ident, cfg_attr($cfg_pred:expr, $($cfg_rhs:tt)*) $(, $($attr_rest:tt)+)?) => { - #[cfg($cfg_pred)] - { - $crate::thread::local_impl::thread_local_inner!(@align $final_align, $($cfg_rhs)*); - } - - $($crate::thread::local_impl::thread_local_inner!(@align $final_align, $($attr_rest)+);)? - }, -} - -/// Use a regular global static to store this key; the state provided will then be -/// thread-local. -/// INVARIANT: ALIGN must be a valid alignment, and no less than `value_align::`. -#[allow(missing_debug_implementations)] -pub struct Storage { - key: LazyKey, - marker: PhantomData>, -} - -unsafe impl Sync for Storage {} - -#[repr(C)] -struct Value { - // This field must be first, for correctness of `#[rustc_align_static]` - value: T, - // INVARIANT: if this value is stored under a TLS key, `key` must be that `key`. - key: Key, -} - -pub const fn value_align() -> usize { - crate::mem::align_of::>() -} - -/// Equivalent to `Box, System>`, but potentially over-aligned. -struct AlignedSystemBox { - ptr: NonNull>, -} - -impl AlignedSystemBox { - #[inline] - fn new(v: Value) -> Self { - let layout = Layout::new::>().align_to(ALIGN).unwrap(); - - // We use the System allocator here to avoid interfering with a potential - // Global allocator using thread-local storage. - let ptr: *mut Value = (unsafe { System.alloc(layout) }).cast(); - let Some(ptr) = NonNull::new(ptr) else { - alloc::handle_alloc_error(layout); - }; - unsafe { ptr.write(v) }; - Self { ptr } - } - - #[inline] - fn into_raw(b: Self) -> *mut Value { - let md = ManuallyDrop::new(b); - md.ptr.as_ptr() - } - - #[inline] - unsafe fn from_raw(ptr: *mut Value) -> Self { - Self { ptr: unsafe { NonNull::new_unchecked(ptr) } } - } -} - -impl Deref for AlignedSystemBox { - type Target = Value; - - #[inline] - fn deref(&self) -> &Self::Target { - unsafe { &*(self.ptr.as_ptr()) } - } -} - -impl Drop for AlignedSystemBox { - #[inline] - fn drop(&mut self) { - let layout = Layout::new::>().align_to(ALIGN).unwrap(); - - unsafe { - let unwind_result = catch_unwind(AssertUnwindSafe(|| self.ptr.drop_in_place())); - System.dealloc(self.ptr.as_ptr().cast(), layout); - if let Err(payload) = unwind_result { - resume_unwind(payload); - } - } - } -} - -impl Storage { - pub const fn new() -> Storage { - Storage { key: LazyKey::new(Some(destroy_value::)), marker: PhantomData } - } - - /// Gets a pointer to the TLS value, potentially initializing it with the - /// provided parameters. If the TLS variable has been destroyed, a null - /// pointer is returned. - /// - /// The resulting pointer may not be used after reentrant inialialization - /// or thread destruction has occurred. - pub fn get(&'static self, i: Option<&mut Option>, f: impl FnOnce() -> T) -> *const T { - let key = self.key.force(); - let ptr = unsafe { get(key) as *mut Value }; - if ptr.addr() > 1 { - // SAFETY: the check ensured the pointer is safe (its destructor - // is not running) + it is coming from a trusted source (self). - unsafe { &(*ptr).value } - } else { - // SAFETY: trivially correct. - unsafe { Self::try_initialize(key, ptr, i, f) } - } - } - - /// # Safety - /// * `key` must be the result of calling `self.key.force()` - /// * `ptr` must be the current value associated with `key`. - unsafe fn try_initialize( - key: Key, - ptr: *mut Value, - i: Option<&mut Option>, - f: impl FnOnce() -> T, - ) -> *const T { - if ptr.addr() == 1 { - // destructor is running - return ptr::null(); - } - - let value = AlignedSystemBox::::new(Value { - value: i.and_then(Option::take).unwrap_or_else(f), - key, - }); - let ptr = AlignedSystemBox::into_raw(value); - - // SAFETY: - // * key came from a `LazyKey` and is thus correct. - // * `ptr` is a correct pointer that can be destroyed by the key destructor. - // * the value is stored under the key that it contains. - let old = unsafe { - let old = get(key) as *mut Value; - set(key, ptr as *mut u8); - old - }; - - if !old.is_null() { - // If the variable was recursively initialized, drop the old value. - // SAFETY: We cannot be inside a `LocalKey::with` scope, as the - // initializer has already returned and the next scope only starts - // after we return the pointer. Therefore, there can be no references - // to the old value. - drop(unsafe { AlignedSystemBox::::from_raw(old) }); - } - - // SAFETY: We just created this value above. - unsafe { &(*ptr).value } - } -} - -unsafe extern "C" fn destroy_value(ptr: *mut u8) { - // SAFETY: - // - // The OS TLS ensures that this key contains a null value when this - // destructor starts to run. We set it back to a sentinel value of 1 to - // ensure that any future calls to `get` for this thread will return - // `None`. - // - // Note that to prevent an infinite loop we reset it back to null right - // before we return from the destructor ourselves. - abort_on_dtor_unwind(|| { - let ptr = unsafe { AlignedSystemBox::::from_raw(ptr as *mut Value) }; - let key = ptr.key; - // SAFETY: `key` is the TLS key `ptr` was stored under. - unsafe { set(key, ptr::without_provenance_mut(1)) }; - drop(ptr); - // SAFETY: `key` is the TLS key `ptr` was stored under. - unsafe { set(key, ptr::null_mut()) }; - // Make sure that the runtime cleanup will be performed - // after the next round of TLS destruction. - guard::enable(); - }); -} - -#[rustc_macro_transparency = "semiopaque"] -pub(crate) macro local_pointer { - () => {}, - ($vis:vis static $name:ident; $($rest:tt)*) => { - $vis static $name: $crate::sys::thread_local::LocalPointer = $crate::sys::thread_local::LocalPointer::__new(); - $crate::sys::thread_local::local_pointer! { $($rest)* } - }, -} - -pub(crate) struct LocalPointer { - key: LazyKey, -} - -impl LocalPointer { - pub const fn __new() -> LocalPointer { - LocalPointer { key: LazyKey::new(None) } - } - - pub fn get(&'static self) -> *mut () { - unsafe { get(self.key.force()) as *mut () } - } - - pub fn set(&'static self, p: *mut ()) { - unsafe { set(self.key.force(), p as *mut u8) } - } -} diff --git a/library/std/tests/thread.rs b/library/std/tests/thread.rs index ef13ce44d3180..89aa029d47e0a 100644 --- a/library/std/tests/thread.rs +++ b/library/std/tests/thread.rs @@ -63,14 +63,14 @@ fn thread_local_hygeiene() { #![allow(dead_code)] type LocalKey = (); - type Storage = (); + type Key = (); type LazyStorage = (); type EagerStorage = (); #[allow(non_camel_case_types)] type usize = (); thread_local! { static A: LocalKey = const { () }; - static B: Storage = const { () }; + static B: Key = const { () }; static C: LazyStorage = const { () }; static D: EagerStorage = const { () }; }