]> git.lizzy.rs Git - rust.git/blob - src/shims/tls.rs
rustup for import changes
[rust.git] / src / shims / tls.rs
1 //! Implement thread-local storage.
2
3 use std::collections::BTreeMap;
4
5 use log::trace;
6
7 use rustc_middle::ty;
8 use rustc_target::abi::{LayoutOf, Size, HasDataLayout};
9
10 use crate::{HelpersEvalContextExt, InterpResult, MPlaceTy, Scalar, StackPopCleanup, Tag};
11
12 pub type TlsKey = u128;
13
14 #[derive(Copy, Clone, Debug)]
15 pub struct TlsEntry<'tcx> {
16     /// The data for this key. None is used to represent NULL.
17     /// (We normalize this early to avoid having to do a NULL-ptr-test each time we access the data.)
18     /// Will eventually become a map from thread IDs to `Scalar`s, if we ever support more than one thread.
19     data: Option<Scalar<Tag>>,
20     dtor: Option<ty::Instance<'tcx>>,
21 }
22
23 #[derive(Debug)]
24 pub struct TlsData<'tcx> {
25     /// The Key to use for the next thread-local allocation.
26     next_key: TlsKey,
27
28     /// pthreads-style thread-local storage.
29     keys: BTreeMap<TlsKey, TlsEntry<'tcx>>,
30
31     /// A single global dtor (that's how things work on macOS) with a data argument.
32     global_dtor: Option<(ty::Instance<'tcx>, Scalar<Tag>)>,
33
34     /// Whether we are in the "destruct" phase, during which some operations are UB.
35     dtors_running: bool,
36 }
37
38 impl<'tcx> Default for TlsData<'tcx> {
39     fn default() -> Self {
40         TlsData {
41             next_key: 1, // start with 1 as we must not use 0 on Windows
42             keys: Default::default(),
43             global_dtor: None,
44             dtors_running: false,
45         }
46     }
47 }
48
49 impl<'tcx> TlsData<'tcx> {
50     /// Generate a new TLS key with the given destructor.
51     /// `max_size` determines the integer size the key has to fit in.
52     pub fn create_tls_key(&mut self, dtor: Option<ty::Instance<'tcx>>, max_size: Size) -> InterpResult<'tcx, TlsKey> {
53         let new_key = self.next_key;
54         self.next_key += 1;
55         self.keys.insert(new_key, TlsEntry { data: None, dtor }).unwrap_none();
56         trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor);
57
58         if max_size.bits() < 128 && new_key >= (1u128 << max_size.bits() as u128) {
59             throw_unsup_format!("we ran out of TLS key space");
60         }
61         Ok(new_key)
62     }
63
64     pub fn delete_tls_key(&mut self, key: TlsKey) -> InterpResult<'tcx> {
65         match self.keys.remove(&key) {
66             Some(_) => {
67                 trace!("TLS key {} removed", key);
68                 Ok(())
69             }
70             None => throw_ub_format!("removing a non-existig TLS key: {}", key),
71         }
72     }
73
74     pub fn load_tls(
75         &self,
76         key: TlsKey,
77         cx: &impl HasDataLayout,
78     ) -> InterpResult<'tcx, Scalar<Tag>> {
79         match self.keys.get(&key) {
80             Some(&TlsEntry { data, .. }) => {
81                 trace!("TLS key {} loaded: {:?}", key, data);
82                 Ok(data.unwrap_or_else(|| Scalar::null_ptr(cx).into()))
83             }
84             None => throw_ub_format!("loading from a non-existing TLS key: {}", key),
85         }
86     }
87
88     pub fn store_tls(&mut self, key: TlsKey, new_data: Option<Scalar<Tag>>) -> InterpResult<'tcx> {
89         match self.keys.get_mut(&key) {
90             Some(&mut TlsEntry { ref mut data, .. }) => {
91                 trace!("TLS key {} stored: {:?}", key, new_data);
92                 *data = new_data;
93                 Ok(())
94             }
95             None => throw_ub_format!("storing to a non-existing TLS key: {}", key),
96         }
97     }
98
99     pub fn set_global_dtor(&mut self, dtor: ty::Instance<'tcx>, data: Scalar<Tag>) -> InterpResult<'tcx> {
100         if self.dtors_running {
101             // UB, according to libstd docs.
102             throw_ub_format!("setting global destructor while destructors are already running");
103         }
104         if self.global_dtor.is_some() {
105             throw_unsup_format!("setting more than one global destructor is not supported");
106         }
107
108         self.global_dtor = Some((dtor, data));
109         Ok(())
110     }
111
112     /// Returns a dtor, its argument and its index, if one is supposed to run.
113     /// `key` is the last dtors that was run; we return the *next* one after that.
114     ///
115     /// An optional destructor function may be associated with each key value.
116     /// At thread exit, if a key value has a non-NULL destructor pointer,
117     /// and the thread has a non-NULL value associated with that key,
118     /// the value of the key is set to NULL, and then the function pointed
119     /// to is called with the previously associated value as its sole argument.
120     /// The order of destructor calls is unspecified if more than one destructor
121     /// exists for a thread when it exits.
122     ///
123     /// If, after all the destructors have been called for all non-NULL values
124     /// with associated destructors, there are still some non-NULL values with
125     /// associated destructors, then the process is repeated.
126     /// If, after at least {PTHREAD_DESTRUCTOR_ITERATIONS} iterations of destructor
127     /// calls for outstanding non-NULL values, there are still some non-NULL values
128     /// with associated destructors, implementations may stop calling destructors,
129     /// or they may continue calling destructors until no non-NULL values with
130     /// associated destructors exist, even though this might result in an infinite loop.
131     fn fetch_tls_dtor(
132         &mut self,
133         key: Option<TlsKey>,
134     ) -> Option<(ty::Instance<'tcx>, Scalar<Tag>, TlsKey)> {
135         use std::collections::Bound::*;
136
137         let thread_local = &mut self.keys;
138         let start = match key {
139             Some(key) => Excluded(key),
140             None => Unbounded,
141         };
142         for (&key, &mut TlsEntry { ref mut data, dtor }) in
143             thread_local.range_mut((start, Unbounded))
144         {
145             if let Some(data_scalar) = *data {
146                 if let Some(dtor) = dtor {
147                     let ret = Some((dtor, data_scalar, key));
148                     *data = None;
149                     return ret;
150                 }
151             }
152         }
153         None
154     }
155 }
156
157 impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
158 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
159     fn run_tls_dtors(&mut self) -> InterpResult<'tcx> {
160         let this = self.eval_context_mut();
161         assert!(!this.machine.tls.dtors_running, "running TLS dtors twice");
162         this.machine.tls.dtors_running = true;
163
164         if this.tcx.sess.target.target.target_os == "windows" {
165             // Windows has a special magic linker section that is run on certain events.
166             // Instead of searching for that section and supporting arbitrary hooks in there
167             // (that would be basically https://github.com/rust-lang/miri/issues/450),
168             // we specifically look up the static in libstd that we know is placed
169             // in that section.
170             let thread_callback = this.eval_path_scalar(&["std", "sys", "windows", "thread_local", "p_thread_callback"])?;
171             let thread_callback = this.memory.get_fn(thread_callback.not_undef()?)?.as_instance()?;
172
173             // The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
174             let reason = this.eval_path_scalar(&["std", "sys", "windows", "c", "DLL_PROCESS_DETACH"])?;
175             let ret_place = MPlaceTy::dangling(this.layout_of(this.tcx.mk_unit())?, this).into();
176             this.call_function(
177                 thread_callback,
178                 &[Scalar::null_ptr(this).into(), reason.into(), Scalar::null_ptr(this).into()],
179                 Some(ret_place),
180                 StackPopCleanup::None { cleanup: true },
181             )?;
182
183             // step until out of stackframes
184             this.run()?;
185
186             // Windows doesn't have other destructors.
187             return Ok(());
188         }
189
190         // The macOS global dtor runs "before any TLS slots get freed", so do that first.
191         if let Some((instance, data)) = this.machine.tls.global_dtor {
192             trace!("Running global dtor {:?} on {:?}", instance, data);
193
194             let ret_place = MPlaceTy::dangling(this.layout_of(this.tcx.mk_unit())?, this).into();
195             this.call_function(
196                 instance,
197                 &[data.into()],
198                 Some(ret_place),
199                 StackPopCleanup::None { cleanup: true },
200             )?;
201
202             // step until out of stackframes
203             this.run()?;
204         }
205
206         // Now run the "keyed" destructors.
207         let mut dtor = this.machine.tls.fetch_tls_dtor(None);
208         while let Some((instance, ptr, key)) = dtor {
209             trace!("Running TLS dtor {:?} on {:?}", instance, ptr);
210             assert!(!this.is_null(ptr).unwrap(), "data can't be NULL when dtor is called!");
211
212             let ret_place = MPlaceTy::dangling(this.layout_of(this.tcx.mk_unit())?, this).into();
213             this.call_function(
214                 instance,
215                 &[ptr.into()],
216                 Some(ret_place),
217                 StackPopCleanup::None { cleanup: true },
218             )?;
219
220             // step until out of stackframes
221             this.run()?;
222
223             // Fetch next dtor after `key`.
224             dtor = match this.machine.tls.fetch_tls_dtor(Some(key)) {
225                 dtor @ Some(_) => dtor,
226                 // We ran each dtor once, start over from the beginning.
227                 None => this.machine.tls.fetch_tls_dtor(None),
228             };
229         }
230         Ok(())
231     }
232 }