organize clock arithmetic more like the stdlib

This commit is contained in:
Ralf Jung 2022-09-01 12:18:55 +02:00 committed by Christian Poveda
parent f5e2f73178
commit bc307b40ba
No known key found for this signature in database
GPG Key ID: 27525EF5E7420A50
5 changed files with 53 additions and 64 deletions

View File

@ -20,6 +20,32 @@ enum InstantKind {
Virtual { nanoseconds: u64 },
}
impl Instant {
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
match self.kind {
InstantKind::Host(instant) =>
instant.checked_add(duration).map(|i| Instant { kind: InstantKind::Host(i) }),
InstantKind::Virtual { nanoseconds } =>
u128::from(nanoseconds)
.checked_add(duration.as_nanos())
.and_then(|n| u64::try_from(n).ok())
.map(|nanoseconds| Instant { kind: InstantKind::Virtual { nanoseconds } }),
}
}
pub fn duration_since(&self, earlier: Instant) -> Duration {
match (&self.kind, earlier.kind) {
(InstantKind::Host(instant), InstantKind::Host(earlier)) =>
instant.duration_since(earlier),
(
InstantKind::Virtual { nanoseconds },
InstantKind::Virtual { nanoseconds: earlier },
) => Duration::from_nanos(nanoseconds.saturating_sub(earlier)),
_ => panic!("all `Instant` must be of the same kind"),
}
}
}
/// A monotone clock used for `Instant` simulation.
#[derive(Debug)]
pub struct Clock {
@ -50,16 +76,6 @@ pub fn new(communicate: bool) -> Self {
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 {
@ -67,7 +83,7 @@ pub fn tick(&self) {
// Time will pass without us doing anything.
}
ClockKind::Virtual { nanoseconds } => {
nanoseconds.fetch_add(NANOSECOND_PER_BASIC_BLOCK, Ordering::Relaxed);
nanoseconds.fetch_add(NANOSECOND_PER_BASIC_BLOCK, Ordering::SeqCst);
}
}
}
@ -78,54 +94,26 @@ pub fn sleep(&self, duration: Duration) {
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);
nanoseconds.fetch_add(duration.as_nanos().try_into().unwrap(), Ordering::SeqCst);
}
}
}
/// Compute `now + duration` relative to this clock.
pub fn get_time_relative(&self, duration: Duration) -> Option<Instant> {
/// Return the `anchor` instant, to convert between monotone instants and durations relative to the anchor.
pub fn anchor(&self) -> Instant {
match &self.kind {
ClockKind::Host { .. } =>
StdInstant::now()
.checked_add(duration)
.map(|instant| Instant { kind: InstantKind::Host(instant) }),
ClockKind::Host { time_anchor } => Instant { kind: InstantKind::Host(*time_anchor) },
ClockKind::Virtual { .. } => Instant { kind: InstantKind::Virtual { nanoseconds: 0 } },
}
}
pub fn now(&self) -> Instant {
match &self.kind {
ClockKind::Host { .. } => Instant { kind: InstantKind::Host(StdInstant::now()) },
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<Instant> {
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!(),
Instant {
kind: InstantKind::Virtual { nanoseconds: nanoseconds.load(Ordering::SeqCst) },
},
}
}
}

View File

@ -191,7 +191,7 @@ 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::Monotonic(instant) => instant.duration_since(clock.now()),
Time::RealTime(time) =>
time.duration_since(SystemTime::now()).unwrap_or(Duration::new(0, 0)),
}

View File

@ -41,7 +41,7 @@ fn clock_gettime(
this.check_no_isolation("`clock_gettime` with real time clocks")?;
system_time_to_duration(&SystemTime::now())?
} else if relative_clocks.contains(&clk_id) {
this.machine.clock.get()
this.machine.clock.now().duration_since(this.machine.clock.anchor())
} else {
let einval = this.eval_libc("EINVAL")?;
this.set_last_error(einval)?;
@ -125,7 +125,7 @@ fn QueryPerformanceCounter(
// QueryPerformanceCounter uses a hardware counter as its basis.
// Miri will emulate a counter with a resolution of 1 nanosecond.
let duration = this.machine.clock.get();
let duration = this.machine.clock.now().duration_since(this.machine.clock.anchor());
let qpc = i64::try_from(duration.as_nanos()).map_err(|_| {
err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported")
})?;
@ -164,7 +164,7 @@ fn mach_absolute_time(&self) -> InterpResult<'tcx, Scalar<Provenance>> {
// This returns a u64, with time units determined dynamically by `mach_timebase_info`.
// We return plain nanoseconds.
let duration = this.machine.clock.get();
let duration = this.machine.clock.now().duration_since(this.machine.clock.anchor());
let res = u64::try_from(duration.as_nanos()).map_err(|_| {
err_unsup_format!("programs running longer than 2^64 nanoseconds are not supported")
})?;
@ -207,9 +207,10 @@ fn nanosleep(
}
};
// If adding the duration overflows, let's just sleep for an hour. Waking up early is always acceptable.
let timeout_time = this.machine.clock.get_time_relative(duration).unwrap_or_else(|| {
this.machine.clock.get_time_relative(Duration::from_secs(3600)).unwrap()
});
let now = this.machine.clock.now();
let timeout_time = now
.checked_add(duration)
.unwrap_or_else(|| now.checked_add(Duration::from_secs(3600)).unwrap());
let active_thread = this.get_active_thread();
this.block_thread(active_thread);
@ -235,7 +236,7 @@ fn Sleep(&mut self, timeout: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let timeout_ms = this.read_scalar(timeout)?.to_u32()?;
let duration = Duration::from_millis(timeout_ms.into());
let timeout_time = this.machine.clock.get_time_relative(duration).unwrap();
let timeout_time = this.machine.clock.now().checked_add(duration).unwrap();
let active_thread = this.get_active_thread();
this.block_thread(active_thread);

View File

@ -106,14 +106,14 @@ pub fn futex<'tcx>(
if op & futex_realtime != 0 {
Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
} else {
Time::Monotonic(this.machine.clock.get_time_absolute(duration).unwrap())
Time::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap())
}
} else {
// FUTEX_WAIT uses a relative timestamp.
if op & futex_realtime != 0 {
Time::RealTime(SystemTime::now().checked_add(duration).unwrap())
} else {
Time::Monotonic(this.machine.clock.get_time_relative(duration).unwrap())
Time::Monotonic(this.machine.clock.now().checked_add(duration).unwrap())
}
})
};

View File

@ -840,7 +840,7 @@ fn pthread_cond_timedwait(
let timeout_time = if clock_id == this.eval_libc_i32("CLOCK_REALTIME")? {
Time::RealTime(SystemTime::UNIX_EPOCH.checked_add(duration).unwrap())
} else if clock_id == this.eval_libc_i32("CLOCK_MONOTONIC")? {
Time::Monotonic(this.machine.clock.get_time_absolute(duration).unwrap())
Time::Monotonic(this.machine.clock.anchor().checked_add(duration).unwrap())
} else {
throw_unsup_format!("unsupported clock id: {}", clock_id);
};