Skip to content

Commit de25371

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

File tree

4 files changed

+73
-30
lines changed

4 files changed

+73
-30
lines changed

src/shims/tls.rs

Lines changed: 34 additions & 29 deletions
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,8 @@ impl<'tcx> TlsData<'tcx> {
202191
for TlsEntry { data, .. } in self.keys.values_mut() {
203192
data.remove(&thread_id);
204193
}
194+
195+
self.macos_thread_dtors.remove(&thread_id);
205196
}
206197
}
207198

@@ -212,7 +203,7 @@ impl VisitProvenance for TlsData<'_> {
212203
for scalar in keys.values().flat_map(|v| v.data.values()) {
213204
scalar.visit_provenance(visit);
214205
}
215-
for (_, scalar) in macos_thread_dtors.values() {
206+
for (_, scalar) in macos_thread_dtors.values().flatten() {
216207
scalar.visit_provenance(visit);
217208
}
218209
}
@@ -225,6 +216,7 @@ pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>);
225216
enum TlsDtorsStatePriv<'tcx> {
226217
#[default]
227218
Init,
219+
MacOsDtors,
228220
PthreadDtors(RunningDtorState),
229221
/// For Windows Dtors, we store the list of functions that we still have to call.
230222
/// These are functions from the magic `.CRT$XLB` linker section.
@@ -243,11 +235,10 @@ impl<'tcx> TlsDtorsState<'tcx> {
243235
Init => {
244236
match this.tcx.sess.target.os.as_ref() {
245237
"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;
251242
}
252243
_ if this.target_os_is_unix() => {
253244
// All other Unixes directly jump to running the pthread dtors.
@@ -266,6 +257,14 @@ impl<'tcx> TlsDtorsState<'tcx> {
266257
}
267258
}
268259
}
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+
}
269268
PthreadDtors(state) => {
270269
match this.schedule_next_pthread_tls_dtor(state)? {
271270
Poll::Pending => return Ok(Poll::Pending), // just keep going
@@ -328,12 +327,15 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
328327
Ok(())
329328
}
330329

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<()>> {
334332
let this = self.eval_context_mut();
335333
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 {
337339
trace!("Running macos dtor {:?} on {:?} at {:?}", instance, data, thread_id);
338340

339341
this.call_function(
@@ -343,8 +345,11 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
343345
None,
344346
StackPopCleanup::Root { cleanup: true },
345347
)?;
348+
349+
return Ok(Poll::Pending);
346350
}
347-
Ok(())
351+
352+
Ok(Poll::Ready(()))
348353
}
349354

350355
/// Schedule a pthread TLS destructor. Returns `true` if found

src/shims/unix/macos/foreign_items.rs

Lines changed: 1 addition & 1 deletion
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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//@only-target-darwin
2+
3+
use std::ptr;
4+
use std::thread;
5+
6+
extern "C" {
7+
fn _tlv_atexit(dtor: unsafe extern "C" fn(*mut u8), arg: *mut u8);
8+
}
9+
10+
fn register(dtor: extern "C" fn(*mut u8)) {
11+
unsafe {
12+
_tlv_atexit(dtor, ptr::null_mut());
13+
}
14+
}
15+
16+
extern "C" fn register_another(_: *mut u8) {
17+
println!("registering another dtor");
18+
register(run);
19+
}
20+
21+
extern "C" fn run(_: *mut u8) {
22+
println!("dtor was called");
23+
}
24+
25+
fn main() {
26+
thread::spawn(|| {
27+
register(run);
28+
register(run);
29+
})
30+
.join()
31+
.unwrap();
32+
33+
register(register_another);
34+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dtor was called
2+
dtor was called
3+
registering another dtor
4+
dtor was called

0 commit comments

Comments
 (0)