diff --git a/src/concurrency/thread.rs b/src/concurrency/thread.rs index 19da0fc678a..8f1fabdddcf 100644 --- a/src/concurrency/thread.rs +++ b/src/concurrency/thread.rs @@ -3,7 +3,7 @@ use std::cell::RefCell; use std::collections::hash_map::Entry; use std::num::TryFromIntError; -use std::time::{Duration, Instant, SystemTime}; +use std::time::{Duration, SystemTime}; use log::trace; @@ -16,6 +16,7 @@ use crate::concurrency::data_race; use crate::concurrency::sync::SynchronizationState; +use crate::shims::time::{Clock, Instant}; use crate::*; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -187,17 +188,6 @@ pub enum Time { RealTime(SystemTime), } -impl Time { - /// How long do we have to wait from now until the specified time? - fn get_wait_time(&self) -> Duration { - match self { - Time::Monotonic(instant) => instant.saturating_duration_since(Instant::now()), - Time::RealTime(time) => - time.duration_since(SystemTime::now()).unwrap_or(Duration::new(0, 0)), - } - } -} - /// Callbacks are used to implement timeouts. For example, waiting on a /// conditional variable with a timeout creates a callback that is called after /// the specified time and unblocks the thread. If another thread signals on the @@ -490,13 +480,16 @@ fn unregister_timeout_callback_if_exists(&mut self, thread: ThreadId) { } /// Get a callback that is ready to be called. - fn get_ready_callback(&mut self) -> Option<(ThreadId, TimeoutCallback<'mir, 'tcx>)> { + fn get_ready_callback( + &mut self, + clock: &Clock, + ) -> Option<(ThreadId, TimeoutCallback<'mir, 'tcx>)> { // We iterate over all threads in the order of their indices because // this allows us to have a deterministic scheduler. for thread in self.threads.indices() { match self.timeout_callbacks.entry(thread) { Entry::Occupied(entry) => - if entry.get().call_time.get_wait_time() == Duration::new(0, 0) { + if clock.get_wait_time(&entry.get().call_time) == Duration::new(0, 0) { return Some((thread, entry.remove().callback)); }, Entry::Vacant(_) => {} @@ -553,7 +546,7 @@ fn thread_terminated( /// used in stateless model checkers such as Loom: run the active thread as /// long as we can and switch only when we have to (the active thread was /// blocked, terminated, or has explicitly asked to be preempted). - fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { + fn schedule(&mut self, clock: &Clock) -> InterpResult<'tcx, SchedulingAction> { // Check whether the thread has **just** terminated (`check_terminated` // checks whether the thread has popped all its stack and if yes, sets // the thread state to terminated). @@ -580,7 +573,7 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { // at the time of the call". // let potential_sleep_time = - self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time()).min(); + self.timeout_callbacks.values().map(|info| clock.get_wait_time(&info.call_time)).min(); if potential_sleep_time == Some(Duration::new(0, 0)) { return Ok(SchedulingAction::ExecuteTimeoutCallback); } @@ -615,7 +608,8 @@ fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { // All threads are currently blocked, but we have unexecuted // timeout_callbacks, which may unblock some of the threads. Hence, // sleep until the first callback. - std::thread::sleep(sleep_time); + + clock.sleep(sleep_time); Ok(SchedulingAction::ExecuteTimeoutCallback) } else { throw_machine_stop!(TerminationInfo::Deadlock); @@ -878,18 +872,19 @@ fn unregister_timeout_callback_if_exists(&mut self, thread: ThreadId) { #[inline] fn run_timeout_callback(&mut self) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let (thread, callback) = - if let Some((thread, callback)) = this.machine.threads.get_ready_callback() { - (thread, callback) - } else { - // get_ready_callback can return None if the computer's clock - // was shifted after calling the scheduler and before the call - // to get_ready_callback (see issue - // https://github.com/rust-lang/miri/issues/1763). In this case, - // just do nothing, which effectively just returns to the - // scheduler. - return Ok(()); - }; + let (thread, callback) = if let Some((thread, callback)) = + this.machine.threads.get_ready_callback(&this.machine.clock) + { + (thread, callback) + } else { + // get_ready_callback can return None if the computer's clock + // was shifted after calling the scheduler and before the call + // to get_ready_callback (see issue + // https://github.com/rust-lang/miri/issues/1763). In this case, + // just do nothing, which effectively just returns to the + // scheduler. + return Ok(()); + }; // This back-and-forth with `set_active_thread` is here because of two // design decisions: // 1. Make the caller and not the callback responsible for changing @@ -906,7 +901,7 @@ fn run_timeout_callback(&mut self) -> InterpResult<'tcx> { #[inline] fn schedule(&mut self) -> InterpResult<'tcx, SchedulingAction> { let this = self.eval_context_mut(); - this.machine.threads.schedule() + this.machine.threads.schedule(&this.machine.clock) } /// Handles thread termination of the active thread: wakes up threads joining on this one, diff --git a/src/eval.rs b/src/eval.rs index 8cdb2876f1a..e1ef7fa9817 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -359,11 +359,6 @@ pub fn eval_entry<'tcx>( assert!(ecx.step()?, "a terminated thread was scheduled for execution"); } SchedulingAction::ExecuteTimeoutCallback => { - assert!( - ecx.machine.communicate(), - "scheduler callbacks require disabled isolation, but the code \ - that created the callback did not check it" - ); ecx.run_timeout_callback()?; } SchedulingAction::ExecuteDtors => { diff --git a/src/machine.rs b/src/machine.rs index 60fe2a91adf..7a3b8732fca 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -4,7 +4,6 @@ use std::borrow::Cow; use std::cell::RefCell; use std::fmt; -use std::time::Instant; use rand::rngs::StdRng; use rand::SeedableRng; @@ -28,7 +27,7 @@ use crate::{ concurrency::{data_race, weak_memory}, - shims::unix::FileHandler, + shims::{time::Clock, unix::FileHandler}, *, }; @@ -327,8 +326,8 @@ pub struct Evaluator<'mir, 'tcx> { /// The table of directory descriptors. pub(crate) dir_handler: shims::unix::DirHandler, - /// The "time anchor" for this machine's monotone clock (for `Instant` simulation). - pub(crate) time_anchor: Instant, + /// This machine's monotone clock. + pub(crate) clock: Clock, /// The set of threads. pub(crate) threads: ThreadManager<'mir, 'tcx>, @@ -434,7 +433,6 @@ pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) enforce_abi: config.check_abi, file_handler: FileHandler::new(config.mute_stdout_stderr), dir_handler: Default::default(), - time_anchor: Instant::now(), layouts, threads: ThreadManager::default(), static_roots: Vec::new(), @@ -454,6 +452,7 @@ pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) preemption_rate: config.preemption_rate, report_progress: config.report_progress, basic_block_count: 0, + clock: Clock::new(config.isolated_op == IsolatedOp::Allow), external_so_lib: config.external_so_file.as_ref().map(|lib_file_path| { // Check if host target == the session target. if env!("TARGET") != target_triple { @@ -1036,6 +1035,9 @@ fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> // These are our preemption points. ecx.maybe_preempt_active_thread(); + + ecx.machine.clock.tick(); + Ok(()) } diff --git a/src/shims/time.rs b/src/shims/time.rs index a574a0612c4..77f46ac6c3d 100644 --- a/src/shims/time.rs +++ b/src/shims/time.rs @@ -1,8 +1,128 @@ -use std::time::{Duration, Instant, SystemTime}; +use std::sync::atomic::AtomicU64; +use std::time::{Duration, Instant as StdInstant, SystemTime}; + +use rustc_data_structures::sync::Ordering; use crate::concurrency::thread::Time; use crate::*; +/// When using a virtual clock, this defines how many nanoseconds do we pretend +/// are passing for each basic block. +const NANOSECOND_PER_BASIC_BLOCK: u64 = 10; + +#[derive(Debug)] +pub enum Instant { + Host(StdInstant), + Virtual { nanoseconds: u64 }, +} + +/// A monotone clock used for `Instant` simulation. +#[derive(Debug)] +pub enum Clock { + Host { + /// The "time anchor" for this machine's monotone clock. + time_anchor: StdInstant, + }, + Virtual { + /// The "current virtual time". + nanoseconds: AtomicU64, + }, +} + +impl Clock { + /// Create a new clock based on the availability of communication with the host. + pub fn new(communicate: bool) -> Self { + if communicate { + Self::Host { time_anchor: StdInstant::now() } + } else { + Self::Virtual { nanoseconds: 0.into() } + } + } + + /// Get the current time relative to this clock. + pub fn get(&self) -> Duration { + match self { + Self::Host { time_anchor } => StdInstant::now().saturating_duration_since(*time_anchor), + Self::Virtual { nanoseconds } => + Duration::from_nanos(nanoseconds.load(Ordering::Relaxed)), + } + } + + /// Let the time pass for a small interval. + pub fn tick(&self) { + match self { + Self::Host { .. } => { + // Time will pass without us doing anything. + } + Self::Virtual { nanoseconds } => { + nanoseconds.fetch_add(NANOSECOND_PER_BASIC_BLOCK, Ordering::Relaxed); + } + } + } + + /// Sleep for the desired duration. + pub fn sleep(&self, duration: Duration) { + match self { + Self::Host { .. } => std::thread::sleep(duration), + Self::Virtual { nanoseconds } => { + // Just pretend that we have slept for some time. + nanoseconds.fetch_add(duration.as_nanos().try_into().unwrap(), Ordering::Relaxed); + } + } + } + + /// Compute `now + duration` relative to this clock. + pub fn get_time_relative(&self, duration: Duration) -> Option