Skip to content

Commit 2dbed74

Browse files
committed
move interpreter loop into thread manager; they are pretty tightly coupled anyway
1 parent 5d9e97d commit 2dbed74

File tree

3 files changed

+76
-78
lines changed

3 files changed

+76
-78
lines changed

src/concurrency/thread.rs

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::shims::tls;
2121
use crate::*;
2222

2323
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24-
pub enum SchedulingAction {
24+
enum SchedulingAction {
2525
/// Execute step on the active thread.
2626
ExecuteStep,
2727
/// Execute a timeout callback.
@@ -450,8 +450,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
450450
}
451451

452452
/// Get a mutable borrow of the currently active thread.
453-
/// (Private for a bit of protection.)
454-
fn active_thread_mut(&mut self) -> &mut Thread<'mir, 'tcx> {
453+
pub fn active_thread_mut(&mut self) -> &mut Thread<'mir, 'tcx> {
455454
&mut self.threads[self.active_thread]
456455
}
457456

@@ -718,6 +717,51 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
718717
}
719718
}
720719

720+
impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {}
721+
trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
722+
/// Execute a timeout callback on the callback's thread.
723+
#[inline]
724+
fn run_timeout_callback(&mut self) -> InterpResult<'tcx> {
725+
let this = self.eval_context_mut();
726+
let (thread, callback) = if let Some((thread, callback)) =
727+
this.machine.threads.get_ready_callback(&this.machine.clock)
728+
{
729+
(thread, callback)
730+
} else {
731+
// get_ready_callback can return None if the computer's clock
732+
// was shifted after calling the scheduler and before the call
733+
// to get_ready_callback (see issue
734+
// https://github.com/rust-lang/miri/issues/1763). In this case,
735+
// just do nothing, which effectively just returns to the
736+
// scheduler.
737+
return Ok(());
738+
};
739+
// This back-and-forth with `set_active_thread` is here because of two
740+
// design decisions:
741+
// 1. Make the caller and not the callback responsible for changing
742+
// thread.
743+
// 2. Make the scheduler the only place that can change the active
744+
// thread.
745+
let old_thread = this.set_active_thread(thread);
746+
callback.call(this)?;
747+
this.set_active_thread(old_thread);
748+
Ok(())
749+
}
750+
751+
#[inline]
752+
fn run_on_stack_empty(&mut self) -> InterpResult<'tcx, Poll<()>> {
753+
let this = self.eval_context_mut();
754+
let mut callback = this
755+
.active_thread_mut()
756+
.on_stack_empty
757+
.take()
758+
.expect("`on_stack_empty` not set up, or already running");
759+
let res = callback(this)?;
760+
this.active_thread_mut().on_stack_empty = Some(callback);
761+
Ok(res)
762+
}
763+
}
764+
721765
// Public interface to thread management.
722766
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
723767
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
@@ -961,61 +1005,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
9611005
this.machine.threads.unregister_timeout_callback_if_exists(thread);
9621006
}
9631007

964-
/// Execute a timeout callback on the callback's thread.
965-
#[inline]
966-
fn run_timeout_callback(&mut self) -> InterpResult<'tcx> {
1008+
/// Run the core interpreter loop. Returns only when an interrupt occurs (an error or program
1009+
/// termination).
1010+
fn run_threads(&mut self) -> InterpResult<'tcx, !> {
9671011
let this = self.eval_context_mut();
968-
let (thread, callback) = if let Some((thread, callback)) =
969-
this.machine.threads.get_ready_callback(&this.machine.clock)
970-
{
971-
(thread, callback)
972-
} else {
973-
// get_ready_callback can return None if the computer's clock
974-
// was shifted after calling the scheduler and before the call
975-
// to get_ready_callback (see issue
976-
// https://github.com/rust-lang/miri/issues/1763). In this case,
977-
// just do nothing, which effectively just returns to the
978-
// scheduler.
979-
return Ok(());
980-
};
981-
// This back-and-forth with `set_active_thread` is here because of two
982-
// design decisions:
983-
// 1. Make the caller and not the callback responsible for changing
984-
// thread.
985-
// 2. Make the scheduler the only place that can change the active
986-
// thread.
987-
let old_thread = this.set_active_thread(thread);
988-
callback.call(this)?;
989-
this.set_active_thread(old_thread);
990-
Ok(())
991-
}
992-
993-
#[inline]
994-
fn run_on_stack_empty(&mut self) -> InterpResult<'tcx, Poll<()>> {
995-
let this = self.eval_context_mut();
996-
let mut callback = this
997-
.active_thread_mut()
998-
.on_stack_empty
999-
.take()
1000-
.expect("`on_stack_empty` not set up, or already running");
1001-
let res = callback(this)?;
1002-
this.active_thread_mut().on_stack_empty = Some(callback);
1003-
Ok(res)
1004-
}
1005-
1006-
/// Decide which action to take next and on which thread.
1007-
#[inline]
1008-
fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> {
1009-
let this = self.eval_context_mut();
1010-
this.machine.threads.schedule(&this.machine.clock)
1012+
loop {
1013+
match this.machine.threads.schedule(&this.machine.clock)? {
1014+
SchedulingAction::ExecuteStep => {
1015+
if !this.step()? {
1016+
// See if this thread can do something else.
1017+
match this.run_on_stack_empty()? {
1018+
Poll::Pending => {} // keep going
1019+
Poll::Ready(()) => this.terminate_active_thread()?,
1020+
}
1021+
}
1022+
}
1023+
SchedulingAction::ExecuteTimeoutCallback => {
1024+
this.run_timeout_callback()?;
1025+
}
1026+
SchedulingAction::Sleep(duration) => {
1027+
this.machine.clock.sleep(duration);
1028+
}
1029+
}
1030+
}
10111031
}
10121032

10131033
/// Handles thread termination of the active thread: wakes up threads joining on this one,
10141034
/// and deallocated thread-local statics.
10151035
///
10161036
/// This is called by the eval loop when a thread's on_stack_empty returns `Ready`.
10171037
#[inline]
1018-
fn thread_terminated(&mut self) -> InterpResult<'tcx> {
1038+
fn terminate_active_thread(&mut self) -> InterpResult<'tcx> {
10191039
let this = self.eval_context_mut();
10201040
let thread = this.active_thread_mut();
10211041
assert!(thread.stack.is_empty(), "only threads with an empty stack can be terminated");

src/eval.rs

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,9 @@ impl MainThreadState {
229229
this.machine.layouts.isize,
230230
);
231231
let exit_code = this.read_scalar(&ret_place.into())?.to_machine_isize(this)?;
232-
// Need to call `thread_terminated` ourselves since we are not going to
233-
// return to the scheduler loop.
234-
this.thread_terminated()?;
232+
// Need to call this ourselves since we are not going to return to the scheduler
233+
// loop, and we want the main thread TLS to not show up as memory leaks.
234+
this.terminate_active_thread()?;
235235
// Stop interpreter loop.
236236
throw_machine_stop!(TerminationInfo::Exit { code: exit_code, leak_check: true });
237237
}
@@ -416,28 +416,8 @@ pub fn eval_entry<'tcx>(
416416
};
417417

418418
// Perform the main execution.
419-
let res: thread::Result<InterpResult<'_, !>> = panic::catch_unwind(AssertUnwindSafe(|| {
420-
// Main loop. Goes on forever until an interrupt is triggered (represented as `InterpError`).
421-
loop {
422-
match ecx.schedule()? {
423-
SchedulingAction::ExecuteStep => {
424-
if !ecx.step()? {
425-
// See if this thread can do something else.
426-
match ecx.run_on_stack_empty()? {
427-
Poll::Pending => {} // keep going
428-
Poll::Ready(()) => ecx.thread_terminated()?,
429-
}
430-
}
431-
}
432-
SchedulingAction::ExecuteTimeoutCallback => {
433-
ecx.run_timeout_callback()?;
434-
}
435-
SchedulingAction::Sleep(duration) => {
436-
ecx.machine.clock.sleep(duration);
437-
}
438-
}
439-
}
440-
}));
419+
let res: thread::Result<InterpResult<'_, !>> =
420+
panic::catch_unwind(AssertUnwindSafe(|| ecx.run_threads()));
441421
let res = res.unwrap_or_else(|panic_payload| {
442422
ecx.handle_ice();
443423
panic::resume_unwind(panic_payload)

src/lib.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,7 @@ pub use crate::concurrency::{
8989
data_race::{AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, EvalContextExt as _},
9090
init_once::{EvalContextExt as _, InitOnceId},
9191
sync::{CondvarId, EvalContextExt as _, MutexId, RwLockId, SyncId},
92-
thread::{
93-
EvalContextExt as _, SchedulingAction, StackEmptyCallback, ThreadId, ThreadManager, Time,
94-
},
92+
thread::{EvalContextExt as _, StackEmptyCallback, ThreadId, ThreadManager, Time},
9593
};
9694
pub use crate::diagnostics::{
9795
report_error, EvalContextExt as _, NonHaltingDiagnostic, TerminationInfo,

0 commit comments

Comments
 (0)