Skip to content

Commit feb1883

Browse files
author
Vytautas Astrauskas
committed
Unify TLS dtors; move stepping outside.
1 parent 8240ed2 commit feb1883

File tree

5 files changed

+110
-49
lines changed

5 files changed

+110
-49
lines changed

src/eval.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,20 +211,15 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
211211
assert!(ecx.step()?, "a terminated thread was scheduled for execution");
212212
}
213213
SchedulingAction::ExecuteDtors => {
214-
ecx.run_tls_dtors_for_active_thread()?;
214+
ecx.schedule_tls_dtors_for_active_thread()?;
215215
}
216216
SchedulingAction::Stop => {
217217
break;
218218
}
219219
}
220220
ecx.process_diagnostics();
221221
}
222-
// Read the return code pointer *before* we run TLS destructors, to assert
223-
// that it was written to by the time that `start` lang item returned.
224222
let return_code = ecx.read_scalar(ret_place.into())?.not_undef()?.to_machine_isize(&ecx)?;
225-
// Run Windows destructors. (We do not support concurrency on Windows
226-
// yet, so we run the destructor of the main thread separately.)
227-
ecx.run_windows_tls_dtors()?;
228223
Ok(return_code)
229224
})();
230225

src/shims/tls.rs

Lines changed: 69 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ pub struct TlsData<'tcx> {
3838

3939
/// Whether we are in the "destruct" phase, during which some operations are UB.
4040
dtors_running: HashSet<ThreadId>,
41+
42+
/// The last TlsKey used to retrieve a TLS destructor.
43+
last_dtor_key: BTreeMap<ThreadId, TlsKey>,
4144
}
4245

4346
impl<'tcx> Default for TlsData<'tcx> {
@@ -47,6 +50,7 @@ impl<'tcx> Default for TlsData<'tcx> {
4750
keys: Default::default(),
4851
global_dtors: Default::default(),
4952
dtors_running: Default::default(),
53+
last_dtor_key: Default::default(),
5054
}
5155
}
5256
}
@@ -187,21 +191,15 @@ impl<'tcx> TlsData<'tcx> {
187191
}
188192
}
189193

190-
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
191-
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
192-
193-
/// Run TLS destructors for the main thread on Windows. The implementation
194-
/// assumes that we do not support concurrency on Windows yet.
195-
///
196-
/// Note: on non-Windows OS this function is a no-op.
197-
fn run_windows_tls_dtors(&mut self) -> InterpResult<'tcx> {
194+
impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
195+
trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
196+
/// Schedule TLS destructors for the main thread on Windows. The
197+
/// implementation assumes that we do not support concurrency on Windows
198+
/// yet.
199+
fn schedule_windows_tls_dtors(&mut self) -> InterpResult<'tcx> {
198200
let this = self.eval_context_mut();
199-
if this.tcx.sess.target.target.target_os != "windows" {
200-
return Ok(());
201-
}
202201
let active_thread = this.get_active_thread()?;
203202
assert_eq!(this.get_total_thread_count()?, 1, "concurrency on Windows not supported");
204-
assert!(!this.machine.tls.dtors_running.contains(&active_thread), "running TLS dtors twice");
205203
this.machine.tls.dtors_running.insert(active_thread);
206204
// Windows has a special magic linker section that is run on certain events.
207205
// Instead of searching for that section and supporting arbitrary hooks in there
@@ -221,30 +219,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
221219
StackPopCleanup::None { cleanup: true },
222220
)?;
223221

224-
// step until out of stackframes
225-
this.run()?;
226-
227-
// Windows doesn't have other destructors.
222+
this.enable_thread(active_thread)?;
228223
Ok(())
229224
}
230225

231-
/// Run TLS destructors for the active thread.
226+
/// Schedule the MacOS global dtor to be executed.
232227
///
233-
/// Note: on Windows OS this function is a no-op because we do not support
234-
/// concurrency on Windows yet.
235-
///
236-
/// FIXME: we do not support yet deallocation of thread local statics.
237-
fn run_tls_dtors_for_active_thread(&mut self) -> InterpResult<'tcx> {
228+
/// Note: It is safe to call this function also on other Unixes.
229+
fn schedule_macos_global_tls_dtors(&mut self) -> InterpResult<'tcx> {
238230
let this = self.eval_context_mut();
239-
if this.tcx.sess.target.target.target_os == "windows" {
240-
return Ok(());
241-
}
242231
let thread_id = this.get_active_thread()?;
243-
assert!(!this.machine.tls.dtors_running.contains(&thread_id), "running TLS dtors twice");
244-
this.machine.tls.dtors_running.insert(thread_id);
245-
246232
// The macOS global dtor runs "before any TLS slots get freed", so do that first.
247-
if let Some(&(instance, data)) = this.machine.tls.global_dtors.get(&thread_id) {
233+
if let Some((instance, data)) = this.machine.tls.global_dtors.remove(&thread_id) {
248234
trace!("Running global dtor {:?} on {:?} at {:?}", instance, data, thread_id);
249235

250236
let ret_place = MPlaceTy::dangling(this.machine.layouts.unit, this).into();
@@ -255,14 +241,33 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
255241
StackPopCleanup::None { cleanup: true },
256242
)?;
257243

258-
// step until out of stackframes
259-
this.run()?;
244+
// Enable the thread so that it steps through the destructor which
245+
// we just scheduled. Since we deleted the destructor, it is
246+
// guaranteed that we will schedule it again. The `dtors_running`
247+
// flag will prevent the code from adding the destructor again.
248+
this.enable_thread(thread_id)?;
260249
}
250+
Ok(())
251+
}
252+
253+
/// Schedule a pthread TLS destructor.
254+
fn schedule_pthread_tls_dtors(&mut self) -> InterpResult<'tcx> {
255+
let this = self.eval_context_mut();
256+
let active_thread = this.get_active_thread()?;
261257

262-
assert!(this.has_terminated(thread_id)?, "running TLS dtors for non-terminated thread");
263-
let mut dtor = this.machine.tls.fetch_tls_dtor(None, thread_id);
264-
while let Some((instance, ptr, key)) = dtor {
265-
trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, thread_id);
258+
assert!(this.has_terminated(active_thread)?, "running TLS dtors for non-terminated thread");
259+
// Fetch next dtor after `key`.
260+
let last_key = this.machine.tls.last_dtor_key.get(&active_thread).cloned();
261+
let dtor = match this.machine.tls.fetch_tls_dtor(last_key, active_thread) {
262+
dtor @ Some(_) => dtor,
263+
// We ran each dtor once, start over from the beginning.
264+
None => {
265+
this.machine.tls.fetch_tls_dtor(None, active_thread)
266+
}
267+
};
268+
if let Some((instance, ptr, key)) = dtor {
269+
this.machine.tls.last_dtor_key.insert(active_thread, key);
270+
trace!("Running TLS dtor {:?} on {:?} at {:?}", instance, ptr, active_thread);
266271
assert!(!this.is_null(ptr).unwrap(), "Data can't be NULL when dtor is called!");
267272

268273
let ret_place = MPlaceTy::dangling(this.machine.layouts.unit, this).into();
@@ -273,15 +278,36 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
273278
StackPopCleanup::None { cleanup: true },
274279
)?;
275280

276-
// step until out of stackframes
277-
this.run()?;
281+
this.enable_thread(active_thread)?;
282+
return Ok(());
283+
}
284+
this.machine.tls.last_dtor_key.remove(&active_thread);
285+
286+
Ok(())
287+
}
288+
}
289+
290+
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
291+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
278292

279-
// Fetch next dtor after `key`.
280-
dtor = match this.machine.tls.fetch_tls_dtor(Some(key), thread_id) {
281-
dtor @ Some(_) => dtor,
282-
// We ran each dtor once, start over from the beginning.
283-
None => this.machine.tls.fetch_tls_dtor(None, thread_id),
284-
};
293+
/// Schedule an active thread's TLS destructor to run on the active thread.
294+
/// Note that this function does not run the destructors itself, it just
295+
/// schedules them one by one each time it is called.
296+
///
297+
/// FIXME: we do not support yet deallocation of thread local statics.
298+
fn schedule_tls_dtors_for_active_thread(&mut self) -> InterpResult<'tcx> {
299+
let this = self.eval_context_mut();
300+
let active_thread = this.get_active_thread()?;
301+
302+
if this.tcx.sess.target.target.target_os == "windows" {
303+
if !this.machine.tls.dtors_running.contains(&active_thread) {
304+
this.machine.tls.dtors_running.insert(active_thread);
305+
this.schedule_windows_tls_dtors()?;
306+
}
307+
} else {
308+
this.machine.tls.dtors_running.insert(active_thread);
309+
this.schedule_macos_global_tls_dtors()?;
310+
this.schedule_pthread_tls_dtors()?;
285311
}
286312

287313
Ok(())

src/thread.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
248248
self.threads[thread_id].state == ThreadState::Terminated
249249
}
250250

251+
/// Enable the thread for execution. The thread must be terminated.
252+
fn enable_thread(&mut self, thread_id: ThreadId) {
253+
assert!(self.has_terminated(thread_id));
254+
self.threads[thread_id].state = ThreadState::Enabled;
255+
}
256+
251257
/// Get the borrow of the currently active thread.
252258
fn active_thread_mut(&mut self) -> &mut Thread<'mir, 'tcx> {
253259
&mut self.threads[self.active_thread]
@@ -525,6 +531,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
525531
Ok(this.machine.threads.has_terminated(thread_id))
526532
}
527533

534+
#[inline]
535+
fn enable_thread(&mut self, thread_id: ThreadId) -> InterpResult<'tcx> {
536+
let this = self.eval_context_mut();
537+
this.machine.threads.enable_thread(thread_id);
538+
Ok(())
539+
}
540+
528541
#[inline]
529542
fn active_thread_stack(&self) -> &[Frame<'mir, 'tcx, Tag, FrameData<'tcx>>] {
530543
let this = self.eval_context_ref();
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//! Check that destructors of the thread locals are executed on all OSes.
2+
3+
use std::cell::RefCell;
4+
5+
struct TestCell {
6+
value: RefCell<u8>,
7+
}
8+
9+
impl Drop for TestCell {
10+
fn drop(&mut self) {
11+
eprintln!("Dropping: {}", self.value.borrow())
12+
}
13+
}
14+
15+
thread_local! {
16+
static A: TestCell = TestCell { value: RefCell::new(0) };
17+
}
18+
19+
fn main() {
20+
A.with(|f| {
21+
assert_eq!(*f.value.borrow(), 0);
22+
*f.value.borrow_mut() = 5;
23+
});
24+
eprintln!("Continue main.")
25+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Continue main.
2+
Dropping: 5

0 commit comments

Comments
 (0)