Skip to content

Commit b0dd0a8

Browse files
committed
Auto merge of #3739 - joboet:macos_tls_dtors, r=RalfJung
Implement support for multiple TLS destructors on macOS I want to get rid of [this `#[cfg]` block](https://github.com/rust-lang/rust/blob/98dcbae5c9ac615d5acfbf42d42b19a77cb9ec11/library/std/src/thread/mod.rs#L195-L211) in `std`, but it is currently required for miri, as it does not support multiple macOS TLS destructors. This is not true for the platform itself, however, as can be observed in the [implementation](https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/dyld/DyldRuntimeState.cpp#L2239).
2 parents 2888707 + 22b6295 commit b0dd0a8

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)