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:
dvdsk 2025-07-03 17:22:37 +02:00
parent f24ee2c9b1
commit 61cf174dce
No known key found for this signature in database
GPG key ID: F687E89FC7894F98
5 changed files with 193 additions and 0 deletions

View file

@ -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(&timespec)? {
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();

View file

@ -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)?;

View file

@ -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));
}
}

View file

@ -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();
}