@@ -36,9 +36,9 @@ pub struct TlsData<'tcx> {
3636 /// pthreads-style thread-local storage.
3737 keys : BTreeMap < TlsKey , TlsEntry < ' tcx > > ,
3838
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 ) > > ,
4242}
4343
4444impl < ' tcx > Default for TlsData < ' tcx > {
@@ -119,26 +119,15 @@ impl<'tcx> TlsData<'tcx> {
119119 }
120120 }
121121
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 (
132125 & mut self ,
133126 thread : ThreadId ,
134127 dtor : ty:: Instance < ' tcx > ,
135128 data : Scalar ,
136129 ) -> 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) ) ;
142131 Ok ( ( ) )
143132 }
144133
@@ -202,6 +191,10 @@ impl<'tcx> TlsData<'tcx> {
202191 for TlsEntry { data, .. } in self . keys . values_mut ( ) {
203192 data. remove ( & thread_id) ;
204193 }
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+ }
205198 }
206199}
207200
@@ -212,7 +205,7 @@ impl VisitProvenance for TlsData<'_> {
212205 for scalar in keys. values ( ) . flat_map ( |v| v. data . values ( ) ) {
213206 scalar. visit_provenance ( visit) ;
214207 }
215- for ( _, scalar) in macos_thread_dtors. values ( ) {
208+ for ( _, scalar) in macos_thread_dtors. values ( ) . flatten ( ) {
216209 scalar. visit_provenance ( visit) ;
217210 }
218211 }
@@ -225,6 +218,7 @@ pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>);
225218enum TlsDtorsStatePriv < ' tcx > {
226219 #[ default]
227220 Init ,
221+ MacOsDtors ,
228222 PthreadDtors ( RunningDtorState ) ,
229223 /// For Windows Dtors, we store the list of functions that we still have to call.
230224 /// These are functions from the magic `.CRT$XLB` linker section.
@@ -243,11 +237,10 @@ impl<'tcx> TlsDtorsState<'tcx> {
243237 Init => {
244238 match this. tcx . sess . target . os . as_ref ( ) {
245239 "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 ;
251244 }
252245 _ if this. target_os_is_unix ( ) => {
253246 // All other Unixes directly jump to running the pthread dtors.
@@ -266,6 +259,14 @@ impl<'tcx> TlsDtorsState<'tcx> {
266259 }
267260 }
268261 }
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+ }
269270 PthreadDtors ( state) => {
270271 match this. schedule_next_pthread_tls_dtor ( state) ? {
271272 Poll :: Pending => return Ok ( Poll :: Pending ) , // just keep going
@@ -328,12 +329,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
328329 Ok ( ( ) )
329330 }
330331
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 < ( ) > > {
334334 let this = self . eval_context_mut ( ) ;
335335 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 {
337341 trace ! ( "Running macos dtor {:?} on {:?} at {:?}" , instance, data, thread_id) ;
338342
339343 this. call_function (
@@ -343,8 +347,11 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
343347 None ,
344348 StackPopCleanup :: Root { cleanup : true } ,
345349 ) ?;
350+
351+ return Ok ( Poll :: Pending ) ;
346352 }
347- Ok ( ( ) )
353+
354+ Ok ( Poll :: Ready ( ( ) ) )
348355 }
349356
350357 /// Schedule a pthread TLS destructor. Returns `true` if found
0 commit comments