| 1 | //! Implementation of the `thread_local` macro. |
| 2 | //! |
| 3 | //! There are three different thread-local implementations: |
| 4 | //! * Some targets lack threading support, and hence have only one thread, so |
| 5 | //! the TLS data is stored in a normal `static`. |
| 6 | //! * Some targets support TLS natively via the dynamic linker and C runtime. |
| 7 | //! * On some targets, the OS provides a library-based TLS implementation. The |
| 8 | //! TLS data is heap-allocated and referenced using a TLS key. |
| 9 | //! |
| 10 | //! Each implementation provides a macro which generates the `LocalKey` `const` |
| 11 | //! used to reference the TLS variable, along with the necessary helper structs |
| 12 | //! to track the initialization/destruction state of the variable. |
| 13 | //! |
| 14 | //! Additionally, this module contains abstractions for the OS interfaces used |
| 15 | //! for these implementations. |
| 16 | |
| 17 | #![cfg_attr (test, allow(unused))] |
| 18 | #![doc (hidden)] |
| 19 | #![forbid (unsafe_op_in_unsafe_fn)] |
| 20 | #![unstable ( |
| 21 | feature = "thread_local_internals" , |
| 22 | reason = "internal details of the thread_local macro" , |
| 23 | issue = "none" |
| 24 | )] |
| 25 | |
| 26 | cfg_if::cfg_if! { |
| 27 | if #[cfg(any( |
| 28 | all(target_family = "wasm" , not(target_feature = "atomics" )), |
| 29 | target_os = "uefi" , |
| 30 | target_os = "zkvm" , |
| 31 | target_os = "trusty" , |
| 32 | ))] { |
| 33 | mod no_threads; |
| 34 | pub use no_threads::{EagerStorage, LazyStorage, thread_local_inner}; |
| 35 | pub(crate) use no_threads::{LocalPointer, local_pointer}; |
| 36 | } else if #[cfg(target_thread_local)] { |
| 37 | mod native; |
| 38 | pub use native::{EagerStorage, LazyStorage, thread_local_inner}; |
| 39 | pub(crate) use native::{LocalPointer, local_pointer}; |
| 40 | } else { |
| 41 | mod os; |
| 42 | pub use os::{Storage, thread_local_inner}; |
| 43 | pub(crate) use os::{LocalPointer, local_pointer}; |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | /// The native TLS implementation needs a way to register destructors for its data. |
| 48 | /// This module contains platform-specific implementations of that register. |
| 49 | /// |
| 50 | /// It turns out however that most platforms don't have a way to register a |
| 51 | /// destructor for each variable. On these platforms, we keep track of the |
| 52 | /// destructors ourselves and register (through the [`guard`] module) only a |
| 53 | /// single callback that runs all of the destructors in the list. |
| 54 | #[cfg (all(target_thread_local, not(all(target_family = "wasm" , not(target_feature = "atomics" )))))] |
| 55 | pub(crate) mod destructors { |
| 56 | cfg_if::cfg_if! { |
| 57 | if #[cfg(any( |
| 58 | target_os = "linux" , |
| 59 | target_os = "android" , |
| 60 | target_os = "fuchsia" , |
| 61 | target_os = "redox" , |
| 62 | target_os = "hurd" , |
| 63 | target_os = "netbsd" , |
| 64 | target_os = "dragonfly" |
| 65 | ))] { |
| 66 | mod linux_like; |
| 67 | mod list; |
| 68 | pub(super) use linux_like::register; |
| 69 | pub(super) use list::run; |
| 70 | } else { |
| 71 | mod list; |
| 72 | pub(super) use list::register; |
| 73 | pub(crate) use list::run; |
| 74 | } |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | /// This module provides a way to schedule the execution of the destructor list |
| 79 | /// and the [runtime cleanup](crate::rt::thread_cleanup) function. Calling `enable` |
| 80 | /// should ensure that these functions are called at the right times. |
| 81 | pub(crate) mod guard { |
| 82 | cfg_if::cfg_if! { |
| 83 | if #[cfg(all(target_thread_local, target_vendor = "apple" ))] { |
| 84 | mod apple; |
| 85 | pub(crate) use apple::enable; |
| 86 | } else if #[cfg(target_os = "windows" )] { |
| 87 | mod windows; |
| 88 | pub(crate) use windows::enable; |
| 89 | } else if #[cfg(any( |
| 90 | all(target_family = "wasm" , not( |
| 91 | all(target_os = "wasi" , target_env = "p1" , target_feature = "atomics" ) |
| 92 | )), |
| 93 | target_os = "uefi" , |
| 94 | target_os = "zkvm" , |
| 95 | target_os = "trusty" , |
| 96 | ))] { |
| 97 | pub(crate) fn enable() { |
| 98 | // FIXME: Right now there is no concept of "thread exit" on |
| 99 | // wasm, but this is likely going to show up at some point in |
| 100 | // the form of an exported symbol that the wasm runtime is going |
| 101 | // to be expected to call. For now we just leak everything, but |
| 102 | // if such a function starts to exist it will probably need to |
| 103 | // iterate the destructor list with these functions: |
| 104 | #[cfg(all(target_family = "wasm" , target_feature = "atomics" ))] |
| 105 | #[allow(unused)] |
| 106 | use super::destructors::run; |
| 107 | #[allow(unused)] |
| 108 | use crate::rt::thread_cleanup; |
| 109 | } |
| 110 | } else if #[cfg(any( |
| 111 | target_os = "hermit" , |
| 112 | target_os = "xous" , |
| 113 | ))] { |
| 114 | // `std` is the only runtime, so it just calls the destructor functions |
| 115 | // itself when the time comes. |
| 116 | pub(crate) fn enable() {} |
| 117 | } else if #[cfg(target_os = "solid_asp3" )] { |
| 118 | mod solid; |
| 119 | pub(crate) use solid::enable; |
| 120 | } else { |
| 121 | mod key; |
| 122 | pub(crate) use key::enable; |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | /// `const`-creatable TLS keys. |
| 128 | /// |
| 129 | /// Most OSs without native TLS will provide a library-based way to create TLS |
| 130 | /// storage. For each TLS variable, we create a key, which can then be used to |
| 131 | /// reference an entry in a thread-local table. This then associates each key |
| 132 | /// with a pointer which we can get and set to store our data. |
| 133 | pub(crate) mod key { |
| 134 | cfg_if::cfg_if! { |
| 135 | if #[cfg(any( |
| 136 | all( |
| 137 | not(target_vendor = "apple" ), |
| 138 | not(target_family = "wasm" ), |
| 139 | target_family = "unix" , |
| 140 | ), |
| 141 | all(not(target_thread_local), target_vendor = "apple" ), |
| 142 | target_os = "teeos" , |
| 143 | all(target_os = "wasi" , target_env = "p1" , target_feature = "atomics" ), |
| 144 | ))] { |
| 145 | mod racy; |
| 146 | mod unix; |
| 147 | #[cfg (test)] |
| 148 | mod tests; |
| 149 | pub(super) use racy::LazyKey; |
| 150 | pub(super) use unix::{Key, set}; |
| 151 | #[cfg (any(not(target_thread_local), test))] |
| 152 | pub(super) use unix::get; |
| 153 | use unix::{create, destroy}; |
| 154 | } else if #[cfg(all(not(target_thread_local), target_os = "windows" ))] { |
| 155 | #[cfg(test)] |
| 156 | mod tests; |
| 157 | mod windows; |
| 158 | pub(super) use windows::{Key, LazyKey, get, run_dtors, set}; |
| 159 | } else if #[cfg(all(target_vendor = "fortanix" , target_env = "sgx" ))] { |
| 160 | mod racy; |
| 161 | mod sgx; |
| 162 | #[cfg(test)] |
| 163 | mod tests; |
| 164 | pub(super) use racy::LazyKey; |
| 165 | pub(super) use sgx::{Key, get, set}; |
| 166 | use sgx::{create, destroy}; |
| 167 | } else if #[cfg(target_os = "xous" )] { |
| 168 | mod racy; |
| 169 | #[cfg(test)] |
| 170 | mod tests; |
| 171 | mod xous; |
| 172 | pub(super) use racy::LazyKey; |
| 173 | pub(crate) use xous::destroy_tls; |
| 174 | pub(super) use xous::{Key, get, set}; |
| 175 | use xous::{create, destroy}; |
| 176 | } |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /// Run a callback in a scenario which must not unwind (such as a `extern "C" |
| 181 | /// fn` declared in a user crate). If the callback unwinds anyway, then |
| 182 | /// `rtabort` with a message about thread local panicking on drop. |
| 183 | #[inline ] |
| 184 | #[allow (dead_code)] |
| 185 | fn abort_on_dtor_unwind(f: impl FnOnce()) { |
| 186 | // Using a guard like this is lower cost. |
| 187 | let guard: DtorUnwindGuard = DtorUnwindGuard; |
| 188 | f(); |
| 189 | core::mem::forget(guard); |
| 190 | |
| 191 | struct DtorUnwindGuard; |
| 192 | impl Drop for DtorUnwindGuard { |
| 193 | #[inline ] |
| 194 | fn drop(&mut self) { |
| 195 | // This is not terribly descriptive, but it doesn't need to be as we'll |
| 196 | // already have printed a panic message at this point. |
| 197 | rtabort!("thread local panicked on drop" ); |
| 198 | } |
| 199 | } |
| 200 | } |
| 201 | |