diff --git a/src/clock.rs b/src/clock.rs new file mode 100644 index 000000000000..faf2d7fda6c4 --- /dev/null +++ b/src/clock.rs @@ -0,0 +1,131 @@ +use std::sync::atomic::AtomicU64; +use std::time::{Duration, Instant as StdInstant}; + +use rustc_data_structures::sync::Ordering; + +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 struct Instant { + kind: InstantKind, +} + +#[derive(Debug)] +enum InstantKind { + Host(StdInstant), + Virtual { nanoseconds: u64 }, +} + +/// A monotone clock used for `Instant` simulation. +#[derive(Debug)] +pub struct Clock { + kind: ClockKind, +} + +#[derive(Debug)] +enum ClockKind { + 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 { + let kind = if communicate { + ClockKind::Host { time_anchor: StdInstant::now() } + } else { + ClockKind::Virtual { nanoseconds: 0.into() } + }; + + Self { kind } + } + + /// Get the current time relative to this clock. + pub fn get(&self) -> Duration { + match &self.kind { + ClockKind::Host { time_anchor } => + StdInstant::now().saturating_duration_since(*time_anchor), + ClockKind::Virtual { nanoseconds } => + Duration::from_nanos(nanoseconds.load(Ordering::Relaxed)), + } + } + + /// Let the time pass for a small interval. + pub fn tick(&self) { + match &self.kind { + ClockKind::Host { .. } => { + // Time will pass without us doing anything. + } + ClockKind::Virtual { nanoseconds } => { + nanoseconds.fetch_add(NANOSECOND_PER_BASIC_BLOCK, Ordering::Relaxed); + } + } + } + + /// Sleep for the desired duration. + pub fn sleep(&self, duration: Duration) { + match &self.kind { + ClockKind::Host { .. } => std::thread::sleep(duration), + ClockKind::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 { + match &self.kind { + ClockKind::Host { .. } => + StdInstant::now() + .checked_add(duration) + .map(|instant| Instant { kind: InstantKind::Host(instant) }), + ClockKind::Virtual { nanoseconds } => + nanoseconds + .load(Ordering::Relaxed) + .checked_add(duration.as_nanos().try_into().unwrap()) + .map(|nanoseconds| Instant { kind: InstantKind::Virtual { nanoseconds } }), + } + } + + /// Compute `start + duration` relative to this clock where `start` is the instant of time when + /// this clock was created. + pub fn get_time_absolute(&self, duration: Duration) -> Option { + match &self.kind { + ClockKind::Host { time_anchor } => + time_anchor + .checked_add(duration) + .map(|instant| Instant { kind: InstantKind::Host(instant) }), + ClockKind::Virtual { .. } => + Some(Instant { + kind: InstantKind::Virtual { + nanoseconds: duration.as_nanos().try_into().unwrap(), + }, + }), + } + } + + /// Returns the duration until the given instant. + pub fn duration_until(&self, instant: &Instant) -> Duration { + match (&instant.kind, &self.kind) { + (InstantKind::Host(instant), ClockKind::Host { .. }) => + instant.saturating_duration_since(StdInstant::now()), + ( + InstantKind::Virtual { nanoseconds }, + ClockKind::Virtual { nanoseconds: current_ns }, + ) => + Duration::from_nanos(nanoseconds.saturating_sub(current_ns.load(Ordering::Relaxed))), + _ => panic!(), + } + } +} diff --git a/src/concurrency/thread.rs b/src/concurrency/thread.rs index 8f1fabdddcf6..b39a67168480 100644 --- a/src/concurrency/thread.rs +++ b/src/concurrency/thread.rs @@ -16,7 +16,6 @@ use rustc_target::spec::abi::Abi; use crate::concurrency::data_race; use crate::concurrency::sync::SynchronizationState; -use crate::shims::time::{Clock, Instant}; use crate::*; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -188,6 +187,17 @@ 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, clock: &Clock) -> Duration { + match self { + Time::Monotonic(instant) => clock.duration_until(instant), + 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 @@ -489,7 +499,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { for thread in self.threads.indices() { match self.timeout_callbacks.entry(thread) { Entry::Occupied(entry) => - if clock.get_wait_time(&entry.get().call_time) == Duration::new(0, 0) { + if entry.get().call_time.get_wait_time(clock) == Duration::new(0, 0) { return Some((thread, entry.remove().callback)); }, Entry::Vacant(_) => {} @@ -573,7 +583,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> { // at the time of the call". // let potential_sleep_time = - self.timeout_callbacks.values().map(|info| clock.get_wait_time(&info.call_time)).min(); + self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time(clock)).min(); if potential_sleep_time == Some(Duration::new(0, 0)) { return Ok(SchedulingAction::ExecuteTimeoutCallback); } diff --git a/src/lib.rs b/src/lib.rs index 4fb6704165be..016ed01f4dac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; +mod clock; mod concurrency; mod diagnostics; mod eval; @@ -81,6 +82,7 @@ pub use crate::shims::time::EvalContextExt as _; pub use crate::shims::tls::{EvalContextExt as _, TlsData}; pub use crate::shims::EvalContextExt as _; +pub use crate::clock::{Clock, Instant}; pub use crate::concurrency::{ data_race::{ AtomicFenceOrd, AtomicReadOrd, AtomicRwOrd, AtomicWriteOrd, @@ -89,7 +91,7 @@ pub use crate::concurrency::{ sync::{CondvarId, EvalContextExt as SyncEvalContextExt, MutexId, RwLockId}, thread::{ EvalContextExt as ThreadsEvalContextExt, SchedulingAction, ThreadId, ThreadManager, - ThreadState, + ThreadState, Time, }, }; pub use crate::diagnostics::{ diff --git a/src/machine.rs b/src/machine.rs index 7a3b8732fca5..4cfedd3bdf42 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -27,7 +27,7 @@ use rustc_target::spec::abi::Abi; use crate::{ concurrency::{data_race, weak_memory}, - shims::{time::Clock, unix::FileHandler}, + shims::unix::FileHandler, *, }; diff --git a/src/shims/time.rs b/src/shims/time.rs index fa01b7b1caee..46164e478f82 100644 --- a/src/shims/time.rs +++ b/src/shims/time.rs @@ -1,145 +1,7 @@ -use std::sync::atomic::AtomicU64; -use std::time::{Duration, Instant as StdInstant, SystemTime}; +use std::time::{Duration, 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 struct Instant { - kind: InstantKind, -} - -#[derive(Debug)] -enum InstantKind { - Host(StdInstant), - Virtual { nanoseconds: u64 }, -} - -/// A monotone clock used for `Instant` simulation. -#[derive(Debug)] -pub struct Clock { - kind: ClockKind, -} - -#[derive(Debug)] -enum ClockKind { - 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 { - let kind = if communicate { - ClockKind::Host { time_anchor: StdInstant::now() } - } else { - ClockKind::Virtual { nanoseconds: 0.into() } - }; - - Self { kind } - } - - /// Get the current time relative to this clock. - pub fn get(&self) -> Duration { - match &self.kind { - ClockKind::Host { time_anchor } => - StdInstant::now().saturating_duration_since(*time_anchor), - ClockKind::Virtual { nanoseconds } => - Duration::from_nanos(nanoseconds.load(Ordering::Relaxed)), - } - } - - /// Let the time pass for a small interval. - pub fn tick(&self) { - match &self.kind { - ClockKind::Host { .. } => { - // Time will pass without us doing anything. - } - ClockKind::Virtual { nanoseconds } => { - nanoseconds.fetch_add(NANOSECOND_PER_BASIC_BLOCK, Ordering::Relaxed); - } - } - } - - /// Sleep for the desired duration. - pub fn sleep(&self, duration: Duration) { - match &self.kind { - ClockKind::Host { .. } => std::thread::sleep(duration), - ClockKind::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