1 //! Implement thread-local storage.
3 use std::collections::btree_map::Entry as BTreeEntry;
4 use std::collections::BTreeMap;
10 use rustc_target::abi::{HasDataLayout, Size};
11 use rustc_target::spec::abi::Abi;
15 pub type TlsKey = u128;
17 #[derive(Clone, Debug)]
18 pub struct TlsEntry<'tcx> {
19 /// The data for this key. None is used to represent NULL.
20 /// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.)
21 data: BTreeMap<ThreadId, Scalar<Provenance>>,
22 dtor: Option<ty::Instance<'tcx>>,
25 #[derive(Default, Debug)]
26 struct RunningDtorState {
27 /// The last TlsKey used to retrieve a TLS destructor. `None` means that we
28 /// have not tried to retrieve a TLS destructor yet or that we already tried
30 last_key: Option<TlsKey>,
34 pub struct TlsData<'tcx> {
35 /// The Key to use for the next thread-local allocation.
38 /// pthreads-style thread-local storage.
39 keys: BTreeMap<TlsKey, TlsEntry<'tcx>>,
41 /// A single per thread destructor of the thread local storage (that's how
42 /// things work on macOS) with a data argument.
43 macos_thread_dtors: BTreeMap<ThreadId, (ty::Instance<'tcx>, Scalar<Provenance>)>,
46 impl<'tcx> Default for TlsData<'tcx> {
47 fn default() -> Self {
49 next_key: 1, // start with 1 as we must not use 0 on Windows
50 keys: Default::default(),
51 macos_thread_dtors: Default::default(),
56 impl<'tcx> TlsData<'tcx> {
57 /// Generate a new TLS key with the given destructor.
58 /// `max_size` determines the integer size the key has to fit in.
59 #[allow(clippy::integer_arithmetic)]
60 pub fn create_tls_key(
62 dtor: Option<ty::Instance<'tcx>>,
64 ) -> InterpResult<'tcx, TlsKey> {
65 let new_key = self.next_key;
67 self.keys.try_insert(new_key, TlsEntry { data: Default::default(), dtor }).unwrap();
68 trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor);
70 if max_size.bits() < 128 && new_key >= (1u128 << max_size.bits()) {
71 throw_unsup_format!("we ran out of TLS key space");
76 pub fn delete_tls_key(&mut self, key: TlsKey) -> InterpResult<'tcx> {
77 match self.keys.remove(&key) {
79 trace!("TLS key {} removed", key);
82 None => throw_ub_format!("removing a non-existig TLS key: {}", key),
90 cx: &impl HasDataLayout,
91 ) -> InterpResult<'tcx, Scalar<Provenance>> {
92 match self.keys.get(&key) {
93 Some(TlsEntry { data, .. }) => {
94 let value = data.get(&thread_id).copied();
95 trace!("TLS key {} for thread {:?} loaded: {:?}", key, thread_id, value);
96 Ok(value.unwrap_or_else(|| Scalar::null_ptr(cx)))
98 None => throw_ub_format!("loading from a non-existing TLS key: {}", key),
106 new_data: Scalar<Provenance>,
107 cx: &impl HasDataLayout,
108 ) -> InterpResult<'tcx> {
109 match self.keys.get_mut(&key) {
110 Some(TlsEntry { data, .. }) => {
111 if new_data.to_machine_usize(cx)? != 0 {
112 trace!("TLS key {} for thread {:?} stored: {:?}", key, thread_id, new_data);
113 data.insert(thread_id, new_data);
115 trace!("TLS key {} for thread {:?} removed", key, thread_id);
116 data.remove(&thread_id);
120 None => throw_ub_format!("storing to a non-existing TLS key: {}", key),
124 /// Set the thread wide destructor of the thread local storage for the given
125 /// thread. This function is used to implement `_tlv_atexit` shim on MacOS.
127 /// Thread wide dtors are available only on MacOS. There is one destructor
128 /// per thread as can be guessed from the following comment in the
130 /// implementation](https://github.com/opensource-apple/dyld/blob/195030646877261f0c8c7ad8b001f52d6a26f514/src/threadLocalVariables.c#L389):
132 /// NOTE: this does not need locks because it only operates on current thread data
133 pub fn set_macos_thread_dtor(
136 dtor: ty::Instance<'tcx>,
137 data: Scalar<Provenance>,
138 ) -> InterpResult<'tcx> {
139 if self.macos_thread_dtors.insert(thread, (dtor, data)).is_some() {
141 "setting more than one thread local storage destructor for the same thread is not supported"
147 /// Returns a dtor, its argument and its index, if one is supposed to run.
148 /// `key` is the last dtors that was run; we return the *next* one after that.
150 /// An optional destructor function may be associated with each key value.
151 /// At thread exit, if a key value has a non-NULL destructor pointer,
152 /// and the thread has a non-NULL value associated with that key,
153 /// the value of the key is set to NULL, and then the function pointed
154 /// to is called with the previously associated value as its sole argument.
155 /// **The order of destructor calls is unspecified if more than one destructor
156 /// exists for a thread when it exits.**
158 /// If, after all the destructors have been called for all non-NULL values
159 /// with associated destructors, there are still some non-NULL values with
160 /// associated destructors, then the process is repeated.
161 /// If, after at least {PTHREAD_DESTRUCTOR_ITERATIONS} iterations of destructor
162 /// calls for outstanding non-NULL values, there are still some non-NULL values
163 /// with associated destructors, implementations may stop calling destructors,
164 /// or they may continue calling destructors until no non-NULL values with
165 /// associated destructors exist, even though this might result in an infinite loop.
170 ) -> Option<(ty::Instance<'tcx>, Scalar<Provenance>, TlsKey)> {
171 use std::ops::Bound::*;
173 let thread_local = &mut self.keys;
174 let start = match key {
175 Some(key) => Excluded(key),
178 // We interpret the documentaion above (taken from POSIX) as saying that we need to iterate
179 // over all keys and run each destructor at least once before running any destructor a 2nd
180 // time. That's why we have `key` to indicate how far we got in the current iteration. If we
181 // return `None`, `schedule_next_pthread_tls_dtor` will re-try with `ket` set to `None` to
182 // start the next round.
183 // TODO: In the future, we might consider randomizing destructor order, but we still have to
184 // uphold this requirement.
185 for (&key, TlsEntry { data, dtor }) in thread_local.range_mut((start, Unbounded)) {
186 match data.entry(thread_id) {
187 BTreeEntry::Occupied(entry) => {
188 if let Some(dtor) = dtor {
189 // Set TLS data to NULL, and call dtor with old value.
190 let data_scalar = entry.remove();
191 let ret = Some((*dtor, data_scalar, key));
195 BTreeEntry::Vacant(_) => {}
201 /// Delete all TLS entries for the given thread. This function should be
202 /// called after all TLS destructors have already finished.
203 fn delete_all_thread_tls(&mut self, thread_id: ThreadId) {
204 for TlsEntry { data, .. } in self.keys.values_mut() {
205 data.remove(&thread_id);
210 impl VisitTags for TlsData<'_> {
211 fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
212 let TlsData { keys, macos_thread_dtors, next_key: _ } = self;
214 for scalar in keys.values().flat_map(|v| v.data.values()) {
215 scalar.visit_tags(visit);
217 for (_, scalar) in macos_thread_dtors.values() {
218 scalar.visit_tags(visit);
223 #[derive(Debug, Default)]
224 pub struct TlsDtorsState(TlsDtorsStatePriv);
226 #[derive(Debug, Default)]
227 enum TlsDtorsStatePriv {
230 PthreadDtors(RunningDtorState),
235 pub fn on_stack_empty<'tcx>(
237 this: &mut MiriInterpCx<'_, 'tcx>,
238 ) -> InterpResult<'tcx, Poll<()>> {
239 use TlsDtorsStatePriv::*;
242 match this.tcx.sess.target.os.as_ref() {
243 "linux" | "freebsd" | "android" => {
244 // Run the pthread dtors.
245 self.0 = PthreadDtors(Default::default());
248 // The macOS thread wide destructor runs "before any TLS slots get
249 // freed", so do that first.
250 this.schedule_macos_tls_dtor()?;
251 // When the stack is empty again, go on with the pthread dtors.
252 self.0 = PthreadDtors(Default::default());
255 // Run the special magic hook.
256 this.schedule_windows_tls_dtors()?;
257 // And move to the final state.
261 // No TLS dtor support.
262 // FIXME: should we do something on wasi?
267 PthreadDtors(state) => {
268 match this.schedule_next_pthread_tls_dtor(state)? {
269 Poll::Pending => {} // just keep going
270 Poll::Ready(()) => self.0 = Done,
274 this.machine.tls.delete_all_thread_tls(this.get_active_thread());
275 return Ok(Poll::Ready(()));
283 impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
284 trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
285 /// Schedule TLS destructors for Windows.
286 /// On windows, TLS destructors are managed by std.
287 fn schedule_windows_tls_dtors(&mut self) -> InterpResult<'tcx> {
288 let this = self.eval_context_mut();
290 // Windows has a special magic linker section that is run on certain events.
291 // Instead of searching for that section and supporting arbitrary hooks in there
292 // (that would be basically https://github.com/rust-lang/miri/issues/450),
293 // we specifically look up the static in libstd that we know is placed
295 if !this.have_module(&["std"]) {
296 // Looks like we are running in a `no_std` crate.
297 // That also means no TLS dtors callback to call.
300 let thread_callback =
301 this.eval_windows("thread_local_key", "p_thread_callback").to_pointer(this)?;
302 let thread_callback = this.get_ptr_fn(thread_callback)?.as_instance()?;
304 // FIXME: Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits
305 // but std treats both the same.
306 let reason = this.eval_windows("c", "DLL_THREAD_DETACH");
308 // The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
309 // FIXME: `h` should be a handle to the current module and what `pv` should be is unknown
310 // but both are ignored by std
313 Abi::System { unwind: false },
314 &[Scalar::null_ptr(this).into(), reason.into(), Scalar::null_ptr(this).into()],
316 StackPopCleanup::Root { cleanup: true },
321 /// Schedule the MacOS thread destructor of the thread local storage to be
323 fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx> {
324 let this = self.eval_context_mut();
325 let thread_id = this.get_active_thread();
326 if let Some((instance, data)) = this.machine.tls.macos_thread_dtors.remove(&thread_id) {
327 trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id);
331 Abi::C { unwind: false },
334 StackPopCleanup::Root { cleanup: true },
340 /// Schedule a pthread TLS destructor. Returns `true` if found
341 /// a destructor to schedule, and `false` otherwise.
342 fn schedule_next_pthread_tls_dtor(
344 state: &mut RunningDtorState,
345 ) -> InterpResult<'tcx, Poll<()>> {
346 let this = self.eval_context_mut();
347 let active_thread = this.get_active_thread();
349 // Fetch next dtor after `key`.
350 let dtor = match this.machine.tls.fetch_tls_dtor(state.last_key, active_thread) {
351 dtor @ Some(_) => dtor,
352 // We ran each dtor once, start over from the beginning.
353 None => this.machine.tls.fetch_tls_dtor(None, active_thread),
355 if let Some((instance, ptr, key)) = dtor {
356 state.last_key = Some(key);
357 trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread);
359 !ptr.to_machine_usize(this).unwrap() != 0,
360 "data can't be NULL when dtor is called!"
365 Abi::C { unwind: false },
368 StackPopCleanup::Root { cleanup: true },
371 return Ok(Poll::Pending);