about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--library/std/src/sys/pal/unix/thread.rs1
-rw-r--r--src/tools/miri/src/shims/time.rs57
-rw-r--r--src/tools/miri/src/shims/unix/foreign_items.rs11
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-time.rs114
-rw-r--r--src/tools/miri/tests/pass/shims/time.rs10
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(&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();
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();
 }