diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index f5b9f69a5f9f9..93f2c519dd1bb 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -326,6 +326,7 @@ #![feature(const_convert)] #![feature(core_intrinsics)] #![feature(core_io_borrowed_buf)] +#![feature(cstr_display)] #![feature(drop_guard)] #![feature(duration_constants)] #![feature(error_generic_member_access)] diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs index 50b690256a20d..0021c013d3d9d 100644 --- a/library/std/src/sys/pal/unix/time.rs +++ b/library/std/src/sys/pal/unix/time.rs @@ -270,22 +270,25 @@ pub struct Instant { } impl Instant { + // CLOCK_UPTIME_RAW clock that increments monotonically, in the same man- + // ner as CLOCK_MONOTONIC_RAW, but that does not incre- + // ment while the system is asleep. The returned value + // is identical to the result of mach_absolute_time() + // after the appropriate mach_timebase conversion is + // applied. + // + // We use `CLOCK_UPTIME_RAW` instead of `CLOCK_MONOTONIC` since + // `CLOCK_UPTIME_RAW` is based on `mach_absolute_time`, which is the + // clock that all timeouts and deadlines are measured against inside + // the kernel. #[cfg(target_vendor = "apple")] pub(crate) const CLOCK_ID: libc::clockid_t = libc::CLOCK_UPTIME_RAW; + #[cfg(not(target_vendor = "apple"))] pub(crate) const CLOCK_ID: libc::clockid_t = libc::CLOCK_MONOTONIC; + pub fn now() -> Instant { // https://pubs.opengroup.org/onlinepubs/9799919799/functions/clock_getres.html - // - // CLOCK_UPTIME_RAW clock that increments monotonically, in the same man- - // ner as CLOCK_MONOTONIC_RAW, but that does not incre- - // ment while the system is asleep. The returned value - // is identical to the result of mach_absolute_time() - // after the appropriate mach_timebase conversion is - // applied. - // - // Instant on macos was historically implemented using mach_absolute_time; - // we preserve this value domain out of an abundance of caution. Instant { t: Timespec::now(Self::CLOCK_ID) } } @@ -308,6 +311,37 @@ impl Instant { pub(crate) fn into_timespec(self) -> Timespec { self.t } + + /// Returns `self` converted into units of `mach_absolute_time`, or `None` + /// if `self` is before the system boot time. If the conversion cannot be + /// performed precisely, this ceils the result up to the nearest + /// representable value. + #[cfg(target_vendor = "apple")] + pub fn into_mach_absolute_time_ceil(self) -> Option { + #[repr(C)] + struct mach_timebase_info { + numer: u32, + denom: u32, + } + + unsafe extern "C" { + unsafe fn mach_timebase_info(info: *mut mach_timebase_info) -> libc::kern_return_t; + } + + let secs = u64::try_from(self.t.tv_sec).ok()?; + + let mut timebase = mach_timebase_info { numer: 0, denom: 0 }; + assert_eq!(unsafe { mach_timebase_info(&mut timebase) }, libc::KERN_SUCCESS); + + // Since `tv_sec` is 64-bit and `tv_nsec` is smaller than 1 billion, + // this cannot overflow. The resulting number needs at most 94 bits. + let nanos = + u128::from(secs) * u128::from(NSEC_PER_SEC) + u128::from(self.t.tv_nsec.as_inner()); + // This multiplication cannot overflow since multiplying a 94-bit + // number by a 32-bit number yields a number that needs at most + // 126 bits. + Some((nanos * u128::from(timebase.denom)).div_ceil(u128::from(timebase.numer))) + } } impl AsInner for Instant { diff --git a/library/std/src/sys/thread/mod.rs b/library/std/src/sys/thread/mod.rs index cb6bf6518f81e..3460270b15fc2 100644 --- a/library/std/src/sys/thread/mod.rs +++ b/library/std/src/sys/thread/mod.rs @@ -73,6 +73,7 @@ cfg_select! { target_os = "fuchsia", target_os = "vxworks", target_os = "wasi", + target_vendor = "apple", ))] pub use unix::sleep_until; #[expect(dead_code)] @@ -133,6 +134,7 @@ cfg_select! { target_os = "fuchsia", target_os = "vxworks", target_os = "wasi", + target_vendor = "apple", )))] pub fn sleep_until(deadline: crate::time::Instant) { use crate::time::Instant; diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index d0396ed713009..4ffb9edee4f08 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -636,6 +636,49 @@ pub fn sleep_until(deadline: crate::time::Instant) { } } +#[cfg(target_vendor = "apple")] +pub fn sleep_until(deadline: crate::time::Instant) { + unsafe extern "C" { + // This is defined in the public header mach/mach_time.h alongside + // `mach_absolute_time`, and like it has been available since the very + // beginning. + // + // There isn't really any documentation on this function, except for a + // short reference in technical note 2169: + // https://developer.apple.com/library/archive/technotes/tn2169/_index.html + safe fn mach_wait_until(deadline: u64) -> libc::kern_return_t; + } + + // Make sure to round up to ensure that we definitely sleep until after + // the deadline has elapsed. + let Some(deadline) = deadline.into_inner().into_mach_absolute_time_ceil() else { + // Since the deadline is before the system boot time, it has already + // passed, so we can return immediately. + return; + }; + + // If the deadline is not representable, then sleep for the maximum duration + // possible and worry about the potential clock issues later (in ca. 600 years). + let deadline = deadline.try_into().unwrap_or(u64::MAX); + loop { + match mach_wait_until(deadline) { + // Success! The deadline has passed. + libc::KERN_SUCCESS => break, + // If the sleep gets interrupted by a signal, `mach_wait_until` + // returns KERN_ABORTED, so we need to restart the syscall. + // Also see Apple's implementation of the POSIX `nanosleep`, which + // converts this error to the POSIX equivalent EINTR: + // https://github.com/apple-oss-distributions/Libc/blob/55b54c0a0c37b3b24393b42b90a4c561d6c606b1/gen/nanosleep.c#L281-L306 + libc::KERN_ABORTED => continue, + // All other errors indicate that something has gone wrong... + error => { + let description = unsafe { CStr::from_ptr(libc::mach_error_string(error)) }; + panic!("mach_wait_until failed: {} (code {error})", description.display()) + } + } + } +} + pub fn yield_now() { let ret = unsafe { libc::sched_yield() }; debug_assert_eq!(ret, 0); diff --git a/library/std/src/thread/functions.rs b/library/std/src/thread/functions.rs index a25bae1aae31e..73d7278785704 100644 --- a/library/std/src/thread/functions.rs +++ b/library/std/src/thread/functions.rs @@ -318,6 +318,7 @@ pub fn sleep(dur: Duration) { /// | Hurd | [clock_nanosleep] (Monotonic Clock)] | /// | Fuchsia | [clock_nanosleep] (Monotonic Clock)] | /// | Vxworks | [clock_nanosleep] (Monotonic Clock)] | +/// | Apple | `mach_wait_until` | /// | Other | `sleep_until` uses [`sleep`] and does not issue a syscall itself | /// /// [currently]: crate::io#platform-specific-behavior diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 2bbae80a0b919..5352b08d8d25e 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -329,6 +329,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(0)) // KERN_SUCCESS } + fn mach_wait_until(&mut self, deadline_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + this.assert_target_os(Os::MacOs, "mach_wait_until"); + + let deadline = this.read_scalar(deadline_op)?.to_u64()?; + // Our mach_absolute_time "ticks" are plain nanoseconds. + let duration = Duration::from_nanos(deadline); + + this.block_thread( + BlockReason::Sleep, + Some((TimeoutClock::Monotonic, TimeoutAnchor::Absolute, duration)), + callback!( + @capture<'tcx> {} + |_this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::TimedOut); + interp_ok(()) + } + ) + ); + + interp_ok(Scalar::from_i32(0)) // KERN_SUCCESS + } + fn nanosleep(&mut self, duration: &OpTy<'tcx>, rem: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index f798f64441b1b..398a869d0e3b9 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -116,6 +116,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } + // FIXME: add a test that directly calls this function. + "mach_wait_until" => { + let [deadline] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let result = this.mach_wait_until(deadline)?; + this.write_scalar(result, dest)?; + } + // Access to command-line arguments "_NSGetArgc" => { let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/typos.toml b/typos.toml index b9d9c6c3522cf..920234a9381bc 100644 --- a/typos.toml +++ b/typos.toml @@ -35,6 +35,7 @@ targetting = "targetting" unparseable = "unparseable" unstability = "unstability" unstalled = "unstalled" +numer = "numer" # this can be valid word, depends on dictionary edition #matcheable = "matcheable"