diff options
| -rw-r--r-- | library/std/src/sys/pal/unix/thread.rs | 1 | ||||
| -rw-r--r-- | src/tools/miri/src/shims/time.rs | 57 | ||||
| -rw-r--r-- | src/tools/miri/src/shims/unix/foreign_items.rs | 11 | ||||
| -rw-r--r-- | src/tools/miri/tests/pass-dep/libc/libc-time.rs | 114 | ||||
| -rw-r--r-- | src/tools/miri/tests/pass/shims/time.rs | 10 |
5 files changed, 193 insertions, 0 deletions
diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs index 3e4585c7187..53f0d1eeda5 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/pal/unix/thread.rs @@ -297,6 +297,7 @@ impl Thread { } // Any unix that has clock_nanosleep + // If this list changes update the MIRI chock_nanosleep shim #[cfg(any( target_os = "freebsd", target_os = "netbsd", diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 28f4ca5bb1b..4d21fd248c8 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -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(); diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index b3c58397a02..5f3778d967e 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -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)?; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-time.rs b/src/tools/miri/tests/pass-dep/libc/libc-time.rs index e53201e0bc5..e8957846ad5 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-time.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-time.rs @@ -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)); + } +} diff --git a/src/tools/miri/tests/pass/shims/time.rs b/src/tools/miri/tests/pass/shims/time.rs index 226f04ade0f..ef0b400f1a7 100644 --- a/src/tools/miri/tests/pass/shims/time.rs +++ b/src/tools/miri/tests/pass/shims/time.rs @@ -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(); } |
