@@ -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,8 @@ impl<'tcx> TlsData<'tcx> {
202
191
for TlsEntry { data, .. } in self . keys . values_mut ( ) {
203
192
data. remove ( & thread_id) ;
204
193
}
194
+
195
+ self . macos_thread_dtors . remove ( & thread_id) ;
205
196
}
206
197
}
207
198
@@ -212,7 +203,7 @@ impl VisitProvenance for TlsData<'_> {
212
203
for scalar in keys. values ( ) . flat_map ( |v| v. data . values ( ) ) {
213
204
scalar. visit_provenance ( visit) ;
214
205
}
215
- for ( _, scalar) in macos_thread_dtors. values ( ) {
206
+ for ( _, scalar) in macos_thread_dtors. values ( ) . flatten ( ) {
216
207
scalar. visit_provenance ( visit) ;
217
208
}
218
209
}
@@ -225,6 +216,7 @@ pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>);
225
216
enum TlsDtorsStatePriv < ' tcx > {
226
217
#[ default]
227
218
Init ,
219
+ MacOsDtors ,
228
220
PthreadDtors ( RunningDtorState ) ,
229
221
/// For Windows Dtors, we store the list of functions that we still have to call.
230
222
/// These are functions from the magic `.CRT$XLB` linker section.
@@ -243,11 +235,10 @@ impl<'tcx> TlsDtorsState<'tcx> {
243
235
Init => {
244
236
match this. tcx . sess . target . os . as_ref ( ) {
245
237
"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 ( ) ) ;
238
+ // macOS has a _tlv_atexit function that allows
239
+ // registering destructors without associated keys.
240
+ // These are run first.
241
+ break ' new_state MacOsDtors ;
251
242
}
252
243
_ if this. target_os_is_unix ( ) => {
253
244
// All other Unixes directly jump to running the pthread dtors.
@@ -266,6 +257,14 @@ impl<'tcx> TlsDtorsState<'tcx> {
266
257
}
267
258
}
268
259
}
260
+ MacOsDtors => {
261
+ match this. schedule_macos_tls_dtor ( ) ? {
262
+ Poll :: Pending => return Ok ( Poll :: Pending ) ,
263
+ // After all macOS destructors are run, the system switches
264
+ // to destroying the pthread destructors.
265
+ Poll :: Ready ( ( ) ) => break ' new_state PthreadDtors ( Default :: default ( ) ) ,
266
+ }
267
+ }
269
268
PthreadDtors ( state) => {
270
269
match this. schedule_next_pthread_tls_dtor ( state) ? {
271
270
Poll :: Pending => return Ok ( Poll :: Pending ) , // just keep going
@@ -328,12 +327,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
328
327
Ok ( ( ) )
329
328
}
330
329
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 > {
330
+ /// Schedule the macOS thread local storage destructors to be executed.
331
+ fn schedule_macos_tls_dtor ( & mut self ) -> InterpResult < ' tcx , Poll < ( ) > > {
334
332
let this = self . eval_context_mut ( ) ;
335
333
let thread_id = this. active_thread ( ) ;
336
- if let Some ( ( instance, data) ) = this. machine . tls . macos_thread_dtors . remove ( & thread_id) {
334
+ // macOS keeps track of TLS destructors in a stack. If a destructor
335
+ // registers another destructor, it will be run next.
336
+ // See https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/dyld/DyldRuntimeState.cpp#L2277
337
+ let dtor = this. machine . tls . macos_thread_dtors . get_mut ( & thread_id) . and_then ( Vec :: pop) ;
338
+ if let Some ( ( instance, data) ) = dtor {
337
339
trace ! ( "Running macos dtor {:?} on {:?} at {:?}" , instance, data, thread_id) ;
338
340
339
341
this. call_function (
@@ -343,8 +345,11 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
343
345
None ,
344
346
StackPopCleanup :: Root { cleanup : true } ,
345
347
) ?;
348
+
349
+ return Ok ( Poll :: Pending ) ;
346
350
}
347
- Ok ( ( ) )
351
+
352
+ Ok ( Poll :: Ready ( ( ) ) )
348
353
}
349
354
350
355
/// Schedule a pthread TLS destructor. Returns `true` if found
0 commit comments