Skip to content

Commit 2f1fa12

Browse files
committed
Auto merge of #2506 - pvdrz:a-really-bad-clock, r=saethlin
Make `sleep` work with isolation enabled Implement a virtual monotone clock that can be used to track time while isolation is enabled. This virtual clock keeps an internal nanoseconds counter that will be increased by a fixed amount at the end of every basic block. When a process sleeps, this clock will return immediately and increase the counter by the interval the process was supposed to sleep. Making miri execution faster than native code :trollface:. cc `@RalfJung` `@saethlin` `@JakobDegen`
2 parents 7e66a9f + c834637 commit 2f1fa12

File tree

9 files changed

+204
-53
lines changed

9 files changed

+204
-53
lines changed

src/clock.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use std::sync::atomic::{AtomicU64, Ordering};
2+
use std::time::{Duration, Instant as StdInstant};
3+
4+
/// When using a virtual clock, this defines how many nanoseconds we pretend are passing for each
5+
/// basic block.
6+
const NANOSECONDS_PER_BASIC_BLOCK: u64 = 10;
7+
8+
#[derive(Debug)]
9+
pub struct Instant {
10+
kind: InstantKind,
11+
}
12+
13+
#[derive(Debug)]
14+
enum InstantKind {
15+
Host(StdInstant),
16+
Virtual { nanoseconds: u64 },
17+
}
18+
19+
impl Instant {
20+
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
21+
match self.kind {
22+
InstantKind::Host(instant) =>
23+
instant.checked_add(duration).map(|i| Instant { kind: InstantKind::Host(i) }),
24+
InstantKind::Virtual { nanoseconds } =>
25+
u128::from(nanoseconds)
26+
.checked_add(duration.as_nanos())
27+
.and_then(|n| u64::try_from(n).ok())
28+
.map(|nanoseconds| Instant { kind: InstantKind::Virtual { nanoseconds } }),
29+
}
30+
}
31+
32+
pub fn duration_since(&self, earlier: Instant) -> Duration {
33+
match (&self.kind, earlier.kind) {
34+
(InstantKind::Host(instant), InstantKind::Host(earlier)) =>
35+
instant.duration_since(earlier),
36+
(
37+
InstantKind::Virtual { nanoseconds },
38+
InstantKind::Virtual { nanoseconds: earlier },
39+
) => Duration::from_nanos(nanoseconds.saturating_sub(earlier)),
40+
_ => panic!("all `Instant` must be of the same kind"),
41+
}
42+
}
43+
}
44+
45+
/// A monotone clock used for `Instant` simulation.
46+
#[derive(Debug)]
47+
pub struct Clock {
48+
kind: ClockKind,
49+
}
50+
51+
#[derive(Debug)]
52+
enum ClockKind {
53+
Host {
54+
/// The "time anchor" for this machine's monotone clock.
55+
time_anchor: StdInstant,
56+
},
57+
Virtual {
58+
/// The "current virtual time".
59+
nanoseconds: AtomicU64,
60+
},
61+
}
62+
63+
impl Clock {
64+
/// Create a new clock based on the availability of communication with the host.
65+
pub fn new(communicate: bool) -> Self {
66+
let kind = if communicate {
67+
ClockKind::Host { time_anchor: StdInstant::now() }
68+
} else {
69+
ClockKind::Virtual { nanoseconds: 0.into() }
70+
};
71+
72+
Self { kind }
73+
}
74+
75+
/// Let the time pass for a small interval.
76+
pub fn tick(&self) {
77+
match &self.kind {
78+
ClockKind::Host { .. } => {
79+
// Time will pass without us doing anything.
80+
}
81+
ClockKind::Virtual { nanoseconds } => {
82+
nanoseconds.fetch_add(NANOSECONDS_PER_BASIC_BLOCK, Ordering::SeqCst);
83+
}
84+
}
85+
}
86+
87+
/// Sleep for the desired duration.
88+
pub fn sleep(&self, duration: Duration) {
89+
match &self.kind {
90+
ClockKind::Host { .. } => std::thread::sleep(duration),
91+
ClockKind::Virtual { nanoseconds } => {
92+
// Just pretend that we have slept for some time.
93+
nanoseconds.fetch_add(duration.as_nanos().try_into().unwrap(), Ordering::SeqCst);
94+
}
95+
}
96+
}
97+
98+
/// Return the `anchor` instant, to convert between monotone instants and durations relative to the anchor.
99+
pub fn anchor(&self) -> Instant {
100+
match &self.kind {
101+
ClockKind::Host { time_anchor } => Instant { kind: InstantKind::Host(*time_anchor) },
102+
ClockKind::Virtual { .. } => Instant { kind: InstantKind::Virtual { nanoseconds: 0 } },
103+
}
104+
}
105+
106+
pub fn now(&self) -> Instant {
107+
match &self.kind {
108+
ClockKind::Host { .. } => Instant { kind: InstantKind::Host(StdInstant::now()) },
109+
ClockKind::Virtual { nanoseconds } =>
110+
Instant {
111+
kind: InstantKind::Virtual { nanoseconds: nanoseconds.load(Ordering::SeqCst) },
112+
},
113+
}
114+
}
115+
}

src/concurrency/thread.rs

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::cell::RefCell;
44
use std::collections::hash_map::Entry;
55
use std::num::TryFromIntError;
6-
use std::time::{Duration, Instant, SystemTime};
6+
use std::time::{Duration, SystemTime};
77

88
use log::trace;
99

@@ -189,9 +189,9 @@ pub enum Time {
189189

190190
impl Time {
191191
/// How long do we have to wait from now until the specified time?
192-
fn get_wait_time(&self) -> Duration {
192+
fn get_wait_time(&self, clock: &Clock) -> Duration {
193193
match self {
194-
Time::Monotonic(instant) => instant.saturating_duration_since(Instant::now()),
194+
Time::Monotonic(instant) => instant.duration_since(clock.now()),
195195
Time::RealTime(time) =>
196196
time.duration_since(SystemTime::now()).unwrap_or(Duration::new(0, 0)),
197197
}
@@ -490,13 +490,16 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
490490
}
491491

492492
/// Get a callback that is ready to be called.
493-
fn get_ready_callback(&mut self) -> Option<(ThreadId, TimeoutCallback<'mir, 'tcx>)> {
493+
fn get_ready_callback(
494+
&mut self,
495+
clock: &Clock,
496+
) -> Option<(ThreadId, TimeoutCallback<'mir, 'tcx>)> {
494497
// We iterate over all threads in the order of their indices because
495498
// this allows us to have a deterministic scheduler.
496499
for thread in self.threads.indices() {
497500
match self.timeout_callbacks.entry(thread) {
498501
Entry::Occupied(entry) =>
499-
if entry.get().call_time.get_wait_time() == Duration::new(0, 0) {
502+
if entry.get().call_time.get_wait_time(clock) == Duration::new(0, 0) {
500503
return Some((thread, entry.remove().callback));
501504
},
502505
Entry::Vacant(_) => {}
@@ -553,7 +556,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
553556
/// used in stateless model checkers such as Loom: run the active thread as
554557
/// long as we can and switch only when we have to (the active thread was
555558
/// blocked, terminated, or has explicitly asked to be preempted).
556-
fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> {
559+
fn schedule(&mut self, clock: &Clock) -> InterpResult<'tcx, SchedulingAction> {
557560
// Check whether the thread has **just** terminated (`check_terminated`
558561
// checks whether the thread has popped all its stack and if yes, sets
559562
// the thread state to terminated).
@@ -580,7 +583,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
580583
// at the time of the call".
581584
// <https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_timedwait.html>
582585
let potential_sleep_time =
583-
self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time()).min();
586+
self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time(clock)).min();
584587
if potential_sleep_time == Some(Duration::new(0, 0)) {
585588
return Ok(SchedulingAction::ExecuteTimeoutCallback);
586589
}
@@ -615,7 +618,8 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
615618
// All threads are currently blocked, but we have unexecuted
616619
// timeout_callbacks, which may unblock some of the threads. Hence,
617620
// sleep until the first callback.
618-
std::thread::sleep(sleep_time);
621+
622+
clock.sleep(sleep_time);
619623
Ok(SchedulingAction::ExecuteTimeoutCallback)
620624
} else {
621625
throw_machine_stop!(TerminationInfo::Deadlock);
@@ -865,6 +869,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
865869
callback: TimeoutCallback<'mir, 'tcx>,
866870
) {
867871
let this = self.eval_context_mut();
872+
if !this.machine.communicate() && matches!(call_time, Time::RealTime(..)) {
873+
panic!("cannot have `RealTime` callback with isolation enabled!")
874+
}
868875
this.machine.threads.register_timeout_callback(thread, call_time, callback);
869876
}
870877

@@ -878,18 +885,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
878885
#[inline]
879886
fn run_timeout_callback(&mut self) -> InterpResult<'tcx> {
880887
let this = self.eval_context_mut();
881-
let (thread, callback) =
882-
if let Some((thread, callback)) = this.machine.threads.get_ready_callback() {
883-
(thread, callback)
884-
} else {
885-
// get_ready_callback can return None if the computer's clock
886-
// was shifted after calling the scheduler and before the call
887-
// to get_ready_callback (see issue
888-
// https://github.com/rust-lang/miri/issues/1763). In this case,
889-
// just do nothing, which effectively just returns to the
890-
// scheduler.
891-
return Ok(());
892-
};
888+
let (thread, callback) = if let Some((thread, callback)) =
889+
this.machine.threads.get_ready_callback(&this.machine.clock)
890+
{
891+
(thread, callback)
892+
} else {
893+
// get_ready_callback can return None if the computer's clock
894+
// was shifted after calling the scheduler and before the call
895+
// to get_ready_callback (see issue
896+
// https://github.com/rust-lang/miri/issues/1763). In this case,
897+
// just do nothing, which effectively just returns to the
898+
// scheduler.
899+
return Ok(());
900+
};
893901
// This back-and-forth with `set_active_thread` is here because of two
894902
// design decisions:
895903
// 1. Make the caller and not the callback responsible for changing
@@ -906,7 +914,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
906914
#[inline]
907915
fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> {
908916
let this = self.eval_context_mut();
909-
this.machine.threads.schedule()
917+
this.machine.threads.schedule(&this.machine.clock)
910918
}
911919

912920
/// Handles thread termination of the active thread: wakes up threads joining on this one,

src/eval.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -359,11 +359,6 @@ pub fn eval_entry<'tcx>(
359359
assert!(ecx.step()?, "a terminated thread was scheduled for execution");
360360
}
361361
SchedulingAction::ExecuteTimeoutCallback => {
362-
assert!(
363-
ecx.machine.communicate(),
364-
"scheduler callbacks require disabled isolation, but the code \
365-
that created the callback did not check it"
366-
);
367362
ecx.run_timeout_callback()?;
368363
}
369364
SchedulingAction::ExecuteDtors => {

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ extern crate rustc_session;
5151
extern crate rustc_span;
5252
extern crate rustc_target;
5353

54+
mod clock;
5455
mod concurrency;
5556
mod diagnostics;
5657
mod eval;
@@ -81,6 +82,7 @@ pub use crate::shims::time::EvalContextExt as _;
8182
pub use crate::shims::tls::{EvalContextExt as _, TlsData};
8283
pub use crate::shims::EvalContextExt as _;
8384

85+
pub use crate::clock::{Clock, Instant};
8486
pub use crate::concurrency::{
8587
data_race::{
8688
AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd,
@@ -89,7 +91,7 @@ pub use crate::concurrency::{
8991
sync::{CondvarId, EvalContextExt as SyncEvalContextExt, MutexId, RwLockId},
9092
thread::{
9193
EvalContextExt as ThreadsEvalContextExt, SchedulingAction, ThreadId, ThreadManager,
92-
ThreadState,
94+
ThreadState, Time,
9395
},
9496
};
9597
pub use crate::diagnostics::{

src/machine.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
use std::borrow::Cow;
55
use std::cell::RefCell;
66
use std::fmt;
7-
use std::time::Instant;
87

98
use rand::rngs::StdRng;
109
use rand::SeedableRng;
@@ -327,8 +326,8 @@ pub struct Evaluator<'mir, 'tcx> {
327326
/// The table of directory descriptors.
328327
pub(crate) dir_handler: shims::unix::DirHandler,
329328

330-
/// The "time anchor" for this machine's monotone clock (for `Instant` simulation).
331-
pub(crate) time_anchor: Instant,
329+
/// This machine's monotone clock.
330+
pub(crate) clock: Clock,
332331

333332
/// The set of threads.
334333
pub(crate) threads: ThreadManager<'mir, 'tcx>,
@@ -434,7 +433,6 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
434433
enforce_abi: config.check_abi,
435434
file_handler: FileHandler::new(config.mute_stdout_stderr),
436435
dir_handler: Default::default(),
437-
time_anchor: Instant::now(),
438436
layouts,
439437
threads: ThreadManager::default(),
440438
static_roots: Vec::new(),
@@ -454,6 +452,7 @@ impl<'mir, 'tcx> Evaluator<'mir, 'tcx> {
454452
preemption_rate: config.preemption_rate,
455453
report_progress: config.report_progress,
456454
basic_block_count: 0,
455+
clock: Clock::new(config.isolated_op == IsolatedOp::Allow),
457456
external_so_lib: config.external_so_file.as_ref().map(|lib_file_path| {
458457
// Check if host target == the session target.
459458
if env!("TARGET") != target_triple {
@@ -1036,6 +1035,10 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
10361035

10371036
// These are our preemption points.
10381037
ecx.maybe_preempt_active_thread();
1038+
1039+
// Make sure some time passes.
1040+
ecx.machine.clock.tick();
1041+
10391042
Ok(())
10401043
}
10411044

0 commit comments

Comments
 (0)