sleep_until: add clock_nanosleep support to Miri
The clock_nanosleep support is there to allow code using `sleep_until` to run under Miri. Therefore the implementation is minimal. - Only the clocks REALTIME and MONOTONIC are supported. The first is supported simply because it was trivial to add not because it was needed for sleep_until. - The only supported flag combinations are no flags or TIMER_ABSTIME only. If an unsupported flag combination or clock is passed in this throws unsupported.
This commit is contained in:
parent
f24ee2c9b1
commit
61cf174dce
5 changed files with 193 additions and 0 deletions
|
|
@ -362,6 +362,63 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
interp_ok(Scalar::from_i32(0))
|
||||
}
|
||||
|
||||
fn clock_nanosleep(
|
||||
&mut self,
|
||||
clock_id: &OpTy<'tcx>,
|
||||
flags: &OpTy<'tcx>,
|
||||
timespec: &OpTy<'tcx>,
|
||||
rem: &OpTy<'tcx>,
|
||||
) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
let clockid_t_size = this.libc_ty_layout("clockid_t").size;
|
||||
|
||||
let clock_id = this.read_scalar(clock_id)?.to_int(clockid_t_size)?;
|
||||
let timespec = this.deref_pointer_as(timespec, this.libc_ty_layout("timespec"))?;
|
||||
let flags = this.read_scalar(flags)?.to_i32()?;
|
||||
let _rem = this.read_pointer(rem)?; // Signal handlers are not supported, so rem will never be written to.
|
||||
|
||||
// The standard lib through sleep_until only needs CLOCK_MONOTONIC
|
||||
if clock_id != this.eval_libc("CLOCK_MONOTONIC").to_int(clockid_t_size)? {
|
||||
throw_unsup_format!("clock_nanosleep: only CLOCK_MONOTONIC is supported");
|
||||
}
|
||||
|
||||
let duration = match this.read_timespec(×pec)? {
|
||||
Some(duration) => duration,
|
||||
None => {
|
||||
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
|
||||
}
|
||||
};
|
||||
|
||||
let timeout_anchor = if flags == 0 {
|
||||
// No flags set, the timespec should be interperted as a duration
|
||||
// to sleep for
|
||||
TimeoutAnchor::Relative
|
||||
} else if flags == this.eval_libc_i32("TIMER_ABSTIME") {
|
||||
// Only flag TIMER_ABSTIME set, the timespec should be interperted as
|
||||
// an absolute time.
|
||||
TimeoutAnchor::Absolute
|
||||
} else {
|
||||
// The standard lib (through `sleep_until`) only needs TIMER_ABSTIME
|
||||
throw_unsup_format!(
|
||||
"`clock_nanosleep` unsupported flags {flags}, only no flags or \
|
||||
TIMER_ABSTIME is supported"
|
||||
);
|
||||
};
|
||||
|
||||
this.block_thread(
|
||||
BlockReason::Sleep,
|
||||
Some((TimeoutClock::Monotonic, timeout_anchor, duration)),
|
||||
callback!(
|
||||
@capture<'tcx> {}
|
||||
|_this, unblock: UnblockKind| {
|
||||
assert_eq!(unblock, UnblockKind::TimedOut);
|
||||
interp_ok(())
|
||||
}
|
||||
),
|
||||
);
|
||||
interp_ok(Scalar::from_i32(0))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn Sleep(&mut self, timeout: &OpTy<'tcx>) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
|
|
|
|||
|
|
@ -967,6 +967,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
let result = this.nanosleep(req, rem)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"clock_nanosleep" => {
|
||||
// Currently this function does not exist on all Unixes, e.g. on macOS.
|
||||
this.check_target_os(
|
||||
&["freebsd", "linux", "android", "solaris", "illumos"],
|
||||
link_name,
|
||||
)?;
|
||||
let [clock_id, flags, req, rem] =
|
||||
this.check_shim(abi, CanonAbi::C, link_name, args)?;
|
||||
let result = this.clock_nanosleep(clock_id, flags, req, rem)?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
"sched_getaffinity" => {
|
||||
// Currently this function does not exist on all Unixes, e.g. on macOS.
|
||||
this.check_target_os(&["linux", "freebsd", "android"], link_name)?;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//@ignore-target: windows # no libc time APIs on Windows
|
||||
//@compile-flags: -Zmiri-disable-isolation
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{env, mem, ptr};
|
||||
|
||||
fn main() {
|
||||
|
|
@ -20,6 +21,19 @@ fn main() {
|
|||
test_localtime_r_future_32b();
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
test_localtime_r_future_64b();
|
||||
|
||||
test_nanosleep();
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos"
|
||||
))]
|
||||
{
|
||||
test_clock_nanosleep::absolute();
|
||||
test_clock_nanosleep::relative();
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests whether clock support exists at all
|
||||
|
|
@ -315,3 +329,103 @@ fn test_localtime_r_multiple_calls_deduplication() {
|
|||
NUM_CALLS - 1
|
||||
);
|
||||
}
|
||||
|
||||
fn test_nanosleep() {
|
||||
let start_test_sleep = Instant::now();
|
||||
let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 };
|
||||
let remainder = ptr::null_mut::<libc::timespec>();
|
||||
let is_error = unsafe { libc::nanosleep(&duration_zero, remainder) };
|
||||
assert_eq!(is_error, 0);
|
||||
assert!(start_test_sleep.elapsed() < Duration::from_millis(10));
|
||||
|
||||
let start_test_sleep = Instant::now();
|
||||
let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 };
|
||||
let remainder = ptr::null_mut::<libc::timespec>();
|
||||
let is_error = unsafe { libc::nanosleep(&duration_100_millis, remainder) };
|
||||
assert_eq!(is_error, 0);
|
||||
assert!(start_test_sleep.elapsed() > Duration::from_millis(100));
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos"
|
||||
))]
|
||||
mod test_clock_nanosleep {
|
||||
use super::*;
|
||||
|
||||
/// Helper function used to create an instant in the future
|
||||
fn add_100_millis(mut ts: libc::timespec) -> libc::timespec {
|
||||
// While tv_nsec has type `c_long` tv_sec has type `time_t`. These might
|
||||
// end up as different types (for example: like i32 and i64).
|
||||
const SECOND: libc::c_long = 1_000_000_000;
|
||||
ts.tv_nsec += SECOND / 10;
|
||||
// If this pushes tv_nsec to SECOND or higher, we need to overflow to tv_sec.
|
||||
ts.tv_sec += (ts.tv_nsec / SECOND) as libc::time_t;
|
||||
ts.tv_nsec %= SECOND;
|
||||
ts
|
||||
}
|
||||
|
||||
/// Helper function to get the current time for testing relative sleeps
|
||||
fn timespec_now(clock: libc::clockid_t) -> libc::timespec {
|
||||
let mut timespec = mem::MaybeUninit::<libc::timespec>::uninit();
|
||||
let is_error = unsafe { libc::clock_gettime(clock, timespec.as_mut_ptr()) };
|
||||
assert_eq!(is_error, 0);
|
||||
unsafe { timespec.assume_init() }
|
||||
}
|
||||
|
||||
pub fn absolute() {
|
||||
let start_test_sleep = Instant::now();
|
||||
let before_start = libc::timespec { tv_sec: 0, tv_nsec: 0 };
|
||||
let remainder = ptr::null_mut::<libc::timespec>();
|
||||
let error = unsafe {
|
||||
// this will not sleep since unix time zero is in the past
|
||||
libc::clock_nanosleep(
|
||||
libc::CLOCK_MONOTONIC,
|
||||
libc::TIMER_ABSTIME,
|
||||
&before_start,
|
||||
remainder,
|
||||
)
|
||||
};
|
||||
assert_eq!(error, 0);
|
||||
assert!(start_test_sleep.elapsed() < Duration::from_millis(10));
|
||||
|
||||
let start_test_sleep = Instant::now();
|
||||
let hunderd_millis_after_start = add_100_millis(timespec_now(libc::CLOCK_MONOTONIC));
|
||||
let remainder = ptr::null_mut::<libc::timespec>();
|
||||
let error = unsafe {
|
||||
libc::clock_nanosleep(
|
||||
libc::CLOCK_MONOTONIC,
|
||||
libc::TIMER_ABSTIME,
|
||||
&hunderd_millis_after_start,
|
||||
remainder,
|
||||
)
|
||||
};
|
||||
assert_eq!(error, 0);
|
||||
assert!(start_test_sleep.elapsed() > Duration::from_millis(100));
|
||||
}
|
||||
|
||||
pub fn relative() {
|
||||
const NO_FLAGS: i32 = 0;
|
||||
|
||||
let start_test_sleep = Instant::now();
|
||||
let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 };
|
||||
let remainder = ptr::null_mut::<libc::timespec>();
|
||||
let error = unsafe {
|
||||
libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_zero, remainder)
|
||||
};
|
||||
assert_eq!(error, 0);
|
||||
assert!(start_test_sleep.elapsed() < Duration::from_millis(10));
|
||||
|
||||
let start_test_sleep = Instant::now();
|
||||
let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 };
|
||||
let remainder = ptr::null_mut::<libc::timespec>();
|
||||
let error = unsafe {
|
||||
libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_100_millis, remainder)
|
||||
};
|
||||
assert_eq!(error, 0);
|
||||
assert!(start_test_sleep.elapsed() > Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//@compile-flags: -Zmiri-disable-isolation
|
||||
#![feature(thread_sleep_until)]
|
||||
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
|
|
@ -15,6 +16,14 @@ fn test_sleep() {
|
|||
assert!((after - before).as_millis() >= 100);
|
||||
}
|
||||
|
||||
fn test_sleep_until() {
|
||||
let before = Instant::now();
|
||||
let hunderd_millis_after_start = before + Duration::from_millis(100);
|
||||
std::thread::sleep_until(hunderd_millis_after_start);
|
||||
let after = Instant::now();
|
||||
assert!((after - before).as_millis() >= 100);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Check `SystemTime`.
|
||||
let now1 = SystemTime::now();
|
||||
|
|
@ -49,4 +58,5 @@ fn main() {
|
|||
duration_sanity(diff);
|
||||
|
||||
test_sleep();
|
||||
test_sleep_until();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue