1use std::collections::BTreeMap;
4use std::collections::btree_map::Entry as BTreeEntry;
5use std::task::Poll;
6
7use rustc_abi::{ExternAbi, HasDataLayout, Size};
8use rustc_middle::ty;
9use rustc_span::Span;
10use rustc_target::spec::Os;
11
12use crate::*;
13
14pub type TlsKey = u128;
15
16#[derive(Clone, Debug)]
17pub struct TlsEntry<'tcx> {
18 data: BTreeMap<ThreadId, Scalar>,
21 dtor: Option<(ty::Instance<'tcx>, Span)>,
22}
23
24#[derive(Default, Debug)]
25struct RunningDtorState {
26 last_key: Option<TlsKey>,
30}
31
32#[derive(Debug)]
33pub struct TlsData<'tcx> {
34 next_key: TlsKey,
36
37 keys: BTreeMap<TlsKey, TlsEntry<'tcx>>,
39
40 macos_thread_dtors: BTreeMap<ThreadId, Vec<(ty::Instance<'tcx>, Scalar, Span)>>,
43}
44
45impl<'tcx> Default for TlsData<'tcx> {
46 fn default() -> Self {
47 TlsData {
48 next_key: 1, keys: Default::default(),
50 macos_thread_dtors: Default::default(),
51 }
52 }
53}
54
55impl<'tcx> TlsData<'tcx> {
56 #[expect(clippy::arithmetic_side_effects)]
59 pub fn create_tls_key(
60 &mut self,
61 dtor: Option<(ty::Instance<'tcx>, Span)>,
62 max_size: Size,
63 ) -> InterpResult<'tcx, TlsKey> {
64 let new_key = self.next_key;
65 self.next_key += 1;
66 self.keys.try_insert(new_key, TlsEntry { data: Default::default(), dtor }).unwrap();
67 trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor);
68
69 if max_size.bits() < 128 && new_key >= (1u128 << max_size.bits()) {
70 throw_unsup_format!("we ran out of TLS key space");
71 }
72 interp_ok(new_key)
73 }
74
75 pub fn delete_tls_key(&mut self, key: TlsKey) -> InterpResult<'tcx> {
76 match self.keys.remove(&key) {
77 Some(_) => {
78 trace!("TLS key {} removed", key);
79 interp_ok(())
80 }
81 None => throw_ub_format!("removing a nonexistent TLS key: {}", key),
82 }
83 }
84
85 pub fn load_tls(
86 &self,
87 key: TlsKey,
88 thread_id: ThreadId,
89 cx: &impl HasDataLayout,
90 ) -> InterpResult<'tcx, Scalar> {
91 match self.keys.get(&key) {
92 Some(TlsEntry { data, .. }) => {
93 let value = data.get(&thread_id).copied();
94 trace!("TLS key {} for thread {:?} loaded: {:?}", key, thread_id, value);
95 interp_ok(value.unwrap_or_else(|| Scalar::null_ptr(cx)))
96 }
97 None => throw_ub_format!("loading from a non-existing TLS key: {}", key),
98 }
99 }
100
101 pub fn store_tls(
102 &mut self,
103 key: TlsKey,
104 thread_id: ThreadId,
105 new_data: Scalar,
106 cx: &impl HasDataLayout,
107 ) -> InterpResult<'tcx> {
108 match self.keys.get_mut(&key) {
109 Some(TlsEntry { data, .. }) => {
110 if new_data.to_target_usize(cx)? != 0 {
111 trace!("TLS key {} for thread {:?} stored: {:?}", key, thread_id, new_data);
112 data.insert(thread_id, new_data);
113 } else {
114 trace!("TLS key {} for thread {:?} removed", key, thread_id);
115 data.remove(&thread_id);
116 }
117 interp_ok(())
118 }
119 None => throw_ub_format!("storing to a non-existing TLS key: {}", key),
120 }
121 }
122
123 pub fn add_macos_thread_dtor(
126 &mut self,
127 thread: ThreadId,
128 dtor: ty::Instance<'tcx>,
129 data: Scalar,
130 span: Span,
131 ) -> InterpResult<'tcx> {
132 self.macos_thread_dtors.entry(thread).or_default().push((dtor, data, span));
133 interp_ok(())
134 }
135
136 fn fetch_tls_dtor(
156 &mut self,
157 key: Option<TlsKey>,
158 thread_id: ThreadId,
159 ) -> Option<(ty::Instance<'tcx>, Scalar, TlsKey, Span)> {
160 use std::ops::Bound::*;
161
162 let thread_local = &mut self.keys;
163 let start = match key {
164 Some(key) => Excluded(key),
165 None => Unbounded,
166 };
167 for (&key, TlsEntry { data, dtor }) in thread_local.range_mut((start, Unbounded)) {
175 match data.entry(thread_id) {
176 BTreeEntry::Occupied(entry) => {
177 if let Some((dtor, span)) = dtor {
178 let data_scalar = entry.remove();
180 return Some((*dtor, data_scalar, key, *span));
181 }
182 }
183 BTreeEntry::Vacant(_) => {}
184 }
185 }
186 None
187 }
188
189 fn delete_all_thread_tls(&mut self, thread_id: ThreadId) {
192 for TlsEntry { data, .. } in self.keys.values_mut() {
193 data.remove(&thread_id);
194 }
195
196 if let Some(dtors) = self.macos_thread_dtors.remove(&thread_id) {
197 assert!(dtors.is_empty(), "the destructors should have already been run");
198 }
199 }
200}
201
202impl VisitProvenance for TlsData<'_> {
203 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
204 let TlsData { keys, macos_thread_dtors, next_key: _ } = self;
205
206 for scalar in keys.values().flat_map(|v| v.data.values()) {
207 scalar.visit_provenance(visit);
208 }
209 for (_, scalar, _) in macos_thread_dtors.values().flatten() {
210 scalar.visit_provenance(visit);
211 }
212 }
213}
214
215#[derive(Debug, Default)]
216pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>);
217
218#[derive(Debug, Default)]
219enum TlsDtorsStatePriv<'tcx> {
220 #[default]
221 Init,
222 MacOsDtors,
223 PthreadDtors(RunningDtorState),
224 WindowsDtors(Vec<(ImmTy<'tcx>, Span)>),
227 Done,
228}
229
230impl<'tcx> TlsDtorsState<'tcx> {
231 pub fn on_stack_empty(
232 &mut self,
233 this: &mut MiriInterpCx<'tcx>,
234 ) -> InterpResult<'tcx, Poll<()>> {
235 use TlsDtorsStatePriv::*;
236 let new_state = 'new_state: {
237 match &mut self.0 {
238 Init => {
239 match this.tcx.sess.target.os {
240 Os::MacOs => {
241 break 'new_state MacOsDtors;
245 }
246 _ if this.target_os_is_unix() => {
247 break 'new_state PthreadDtors(Default::default());
249 }
250 Os::Windows => {
251 let dtors = this.lookup_windows_tls_dtors()?;
253 break 'new_state WindowsDtors(dtors);
255 }
256 _ => {
257 break 'new_state Done;
259 }
260 }
261 }
262 MacOsDtors => {
263 match this.schedule_macos_tls_dtor()? {
264 Poll::Pending => return interp_ok(Poll::Pending),
265 Poll::Ready(()) => break 'new_state PthreadDtors(Default::default()),
268 }
269 }
270 PthreadDtors(state) => {
271 match this.schedule_next_pthread_tls_dtor(state)? {
272 Poll::Pending => return interp_ok(Poll::Pending), Poll::Ready(()) => break 'new_state Done,
274 }
275 }
276 WindowsDtors(dtors) => {
277 if let Some((dtor, span)) = dtors.pop() {
278 this.schedule_windows_tls_dtor(dtor, span)?;
279 return interp_ok(Poll::Pending); } else {
281 break 'new_state Done;
283 }
284 }
285 Done => {
286 this.machine.tls.delete_all_thread_tls(this.active_thread());
287 return interp_ok(Poll::Ready(()));
288 }
289 }
290 };
291
292 self.0 = new_state;
293 interp_ok(Poll::Pending)
294 }
295}
296
297impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
298trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
299 fn lookup_windows_tls_dtors(&mut self) -> InterpResult<'tcx, Vec<(ImmTy<'tcx>, Span)>> {
302 let this = self.eval_context_mut();
303
304 interp_ok(this.lookup_link_section(|section| section == ".CRT$XLB")?)
307 }
308
309 fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx>, span: Span) -> InterpResult<'tcx> {
310 let this = self.eval_context_mut();
311
312 let dtor = dtor.to_scalar().to_pointer(this)?;
313 let thread_callback = this.get_ptr_fn(dtor)?.as_instance()?;
314
315 let reason = this.eval_windows("c", "DLL_THREAD_DETACH");
318 let null_ptr =
319 ImmTy::from_scalar(Scalar::null_ptr(this), this.machine.layouts.const_raw_ptr);
320
321 this.call_thread_root_function(
325 thread_callback,
326 ExternAbi::System { unwind: false },
327 &[null_ptr.clone(), ImmTy::from_scalar(reason, this.machine.layouts.u32), null_ptr],
328 None,
329 span,
330 )?;
331 interp_ok(())
332 }
333
334 fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx, Poll<()>> {
336 let this = self.eval_context_mut();
337 let thread_id = this.active_thread();
338 let dtor = this.machine.tls.macos_thread_dtors.get_mut(&thread_id).and_then(Vec::pop);
342 if let Some((instance, data, span)) = dtor {
343 trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id);
344
345 this.call_thread_root_function(
346 instance,
347 ExternAbi::C { unwind: false },
348 &[ImmTy::from_scalar(data, this.machine.layouts.mut_raw_ptr)],
349 None,
350 span,
351 )?;
352
353 return interp_ok(Poll::Pending);
354 }
355
356 interp_ok(Poll::Ready(()))
357 }
358
359 fn schedule_next_pthread_tls_dtor(
362 &mut self,
363 state: &mut RunningDtorState,
364 ) -> InterpResult<'tcx, Poll<()>> {
365 let this = self.eval_context_mut();
366 let active_thread = this.active_thread();
367
368 let dtor = match this.machine.tls.fetch_tls_dtor(state.last_key, active_thread) {
370 dtor @ Some(_) => dtor,
371 None => this.machine.tls.fetch_tls_dtor(None, active_thread),
373 };
374 if let Some((instance, ptr, key, span)) = dtor {
375 state.last_key = Some(key);
376 trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread);
377 assert!(
378 ptr.to_target_usize(this).unwrap() != 0,
379 "data can't be NULL when dtor is called!"
380 );
381
382 this.call_thread_root_function(
383 instance,
384 ExternAbi::C { unwind: false },
385 &[ImmTy::from_scalar(ptr, this.machine.layouts.mut_raw_ptr)],
386 None,
387 span,
388 )?;
389
390 return interp_ok(Poll::Pending);
391 }
392
393 interp_ok(Poll::Ready(()))
394 }
395}