Skip to content

Commit 22b6295

Browse files
committed
implement support for multiple TLS destructors on macOS
1 parent 521422a commit 22b6295

File tree

4 files changed

+87
-30
lines changed

4 files changed

+87
-30
lines changed

src/shims/tls.rs

+36-29
Original file line numberDiff line numberDiff line change
@@ -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

4444
impl<'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>);
225218
enum 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

src/shims/unix/macos/foreign_items.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
132132
let dtor = this.get_ptr_fn(dtor)?.as_instance()?;
133133
let data = this.read_scalar(data)?;
134134
let active_thread = this.active_thread();
135-
this.machine.tls.set_macos_thread_dtor(active_thread, dtor, data)?;
135+
this.machine.tls.add_macos_thread_dtor(active_thread, dtor, data)?;
136136
}
137137

138138
// Querying system information

tests/pass/tls/macos_tlv_atexit.rs

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//@only-target-darwin
2+
3+
use std::thread;
4+
5+
extern "C" {
6+
fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8);
7+
}
8+
9+
fn register<F>(f: F)
10+
where
11+
F: FnOnce() + 'static,
12+
{
13+
// This will receive the pointer passed into `_tlv_atexit`, which is the
14+
// original `f` but boxed up.
15+
unsafe extern "C" fn run<F>(ptr: *mut u8)
16+
where
17+
F: FnOnce() + 'static,
18+
{
19+
let f = unsafe { Box::from_raw(ptr as *mut F) };
20+
f()
21+
}
22+
23+
unsafe {
24+
_tlv_atexit(run::<F>, Box::into_raw(Box::new(f)) as *mut u8);
25+
}
26+
}
27+
28+
fn main() {
29+
thread::spawn(|| {
30+
register(|| println!("dtor 2"));
31+
register(|| println!("dtor 1"));
32+
println!("exiting thread");
33+
})
34+
.join()
35+
.unwrap();
36+
37+
println!("exiting main");
38+
register(|| println!("dtor 5"));
39+
register(|| {
40+
println!("registering dtor in dtor 3");
41+
register(|| println!("dtor 4"));
42+
});
43+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
exiting thread
2+
dtor 1
3+
dtor 2
4+
exiting main
5+
registering dtor in dtor 3
6+
dtor 4
7+
dtor 5

0 commit comments

Comments
 (0)