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 | |