@@ -36,9 +36,9 @@ pub struct TlsData<'tcx> {
36
36
/// pthreads-style thread-local storage.
37
37
keys: BTreeMap<TlsKey, TlsEntry<'tcx>>,
38
38
39
- /// A single per thread destructor of the thread local storage (that's how
40
- /// things work on macOS) with a data argument .
41
- macos_thread_dtors: BTreeMap<ThreadId, (ty::Instance<'tcx>, Scalar)>,
39
+ /// On macOS, each thread holds a list of destructor functions with their
40
+ /// respective data arguments .
41
+ macos_thread_dtors: BTreeMap<ThreadId, Vec< (ty::Instance<'tcx>, Scalar)> >,
42
42
}
43
43
44
44
impl<'tcx> Default for TlsData<'tcx> {
@@ -119,26 +119,15 @@ impl<'tcx> TlsData<'tcx> {
119
119
}
120
120
}
121
121
122
- /// Set the thread wide destructor of the thread local storage for the given
123
- /// thread. This function is used to implement `_tlv_atexit` shim on MacOS.
124
- ///
125
- /// Thread wide dtors are available only on MacOS. There is one destructor
126
- /// per thread as can be guessed from the following comment in the
127
- /// [`_tlv_atexit`
128
- /// implementation](https://github.com/opensource-apple/dyld/blob/195030646877261f0c8c7ad8b001f52d6a26f514/src/threadLocalVariables.c#L389):
129
- ///
130
- /// NOTE: this does not need locks because it only operates on current thread data
131
- pub fn set_macos_thread_dtor(
122
+ /// Add a thread local storage destructor for the given thread. This function
123
+ /// is used to implement the `_tlv_atexit` shim on MacOS.
124
+ pub fn add_macos_thread_dtor(
132
125
&mut self,
133
126
thread: ThreadId,
134
127
dtor: ty::Instance<'tcx>,
135
128
data: Scalar,
136
129
) -> InterpResult<'tcx> {
137
- if self.macos_thread_dtors.insert(thread, (dtor, data)).is_some() {
138
- throw_unsup_format!(
139
- "setting more than one thread local storage destructor for the same thread is not supported"
140
- );
141
- }
130
+ self.macos_thread_dtors.entry(thread).or_default().push((dtor, data));
142
131
Ok(())
143
132
}
144
133
@@ -202,6 +191,10 @@ impl<'tcx> TlsData<'tcx> {
202
191
for TlsEntry { data, .. } in self.keys.values_mut() {
203
192
data.remove(&thread_id);
204
193
}
194
+
195
+ if let Some(dtors) = self.macos_thread_dtors.remove(&thread_id) {
196
+ assert!(dtors.is_empty(), "the destructors should have already been run");
197
+ }
205
198
}
206
199
}
207
200
@@ -212,7 +205,7 @@ impl VisitProvenance for TlsData<'_> {
212
205
for scalar in keys.values().flat_map(|v| v.data.values()) {
213
206
scalar.visit_provenance(visit);
214
207
}
215
- for (_, scalar) in macos_thread_dtors.values() {
208
+ for (_, scalar) in macos_thread_dtors.values().flatten() {
216
209
scalar.visit_provenance(visit);
217
210
}
218
211
}
@@ -225,6 +218,7 @@ pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>);
225
218
enum TlsDtorsStatePriv<'tcx> {
226
219
#[default]
227
220
Init,
221
+ MacOsDtors,
228
222
PthreadDtors(RunningDtorState),
229
223
/// For Windows Dtors, we store the list of functions that we still have to call.
230
224
/// These are functions from the magic `.CRT$XLB` linker section.
@@ -243,11 +237,10 @@ impl<'tcx> TlsDtorsState<'tcx> {
243
237
Init => {
244
238
match this.tcx.sess.target.os.as_ref() {
245
239
"macos" => {
246
- // The macOS thread wide destructor runs "before any TLS slots get
247
- // freed", so do that first.
248
- this.schedule_macos_tls_dtor()?;
249
- // When that destructor is done, go on with the pthread dtors.
250
- break 'new_state PthreadDtors(Default::default());
240
+ // macOS has a _tlv_atexit function that allows
241
+ // registering destructors without associated keys.
242
+ // These are run first.
243
+ break 'new_state MacOsDtors;
251
244
}
252
245
_ if this.target_os_is_unix() => {
253
246
// All other Unixes directly jump to running the pthread dtors.
@@ -266,6 +259,14 @@ impl<'tcx> TlsDtorsState<'tcx> {
266
259
}
267
260
}
268
261
}
262
+ MacOsDtors => {
263
+ match this.schedule_macos_tls_dtor()? {
264
+ Poll::Pending => return Ok(Poll::Pending),
265
+ // After all macOS destructors are run, the system switches
266
+ // to destroying the pthread destructors.
267
+ Poll::Ready(()) => break 'new_state PthreadDtors(Default::default()),
268
+ }
269
+ }
269
270
PthreadDtors(state) => {
270
271
match this.schedule_next_pthread_tls_dtor(state)? {
271
272
Poll::Pending => return Ok(Poll::Pending), // just keep going
@@ -328,12 +329,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
328
329
Ok(())
329
330
}
330
331
331
- /// Schedule the MacOS thread destructor of the thread local storage to be
332
- /// executed.
333
- fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx> {
332
+ /// Schedule the macOS thread local storage destructors to be executed.
333
+ fn schedule_macos_tls_dtor(&mut self) -> InterpResult<'tcx, Poll<()>> {
334
334
let this = self.eval_context_mut();
335
335
let thread_id = this.active_thread();
336
- if let Some((instance, data)) = this.machine.tls.macos_thread_dtors.remove(&thread_id) {
336
+ // macOS keeps track of TLS destructors in a stack. If a destructor
337
+ // registers another destructor, it will be run next.
338
+ // See https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/dyld/DyldRuntimeState.cpp#L2277
339
+ let dtor = this.machine.tls.macos_thread_dtors.get_mut(&thread_id).and_then(Vec::pop);
340
+ if let Some((instance, data)) = dtor {
337
341
trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id);
338
342
339
343
this.call_function(
@@ -343,8 +347,11 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
343
347
None,
344
348
StackPopCleanup::Root { cleanup: true },
345
349
)?;
350
+
351
+ return Ok(Poll::Pending);
346
352
}
347
- Ok(())
353
+
354
+ Ok(Poll::Ready(()))
348
355
}
349
356
350
357
/// Schedule a pthread TLS destructor. Returns `true` if found
0 commit comments