Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
54 changes: 44 additions & 10 deletions library/std/src/sys/pal/unix/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}

Expand All @@ -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<u128> {
#[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<Timespec> for Instant {
Expand Down
2 changes: 2 additions & 0 deletions library/std/src/sys/thread/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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;
Expand Down
43 changes: 43 additions & 0 deletions library/std/src/sys/thread/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions library/std/src/thread/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions src/tools/miri/src/shims/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
7 changes: 7 additions & 0 deletions src/tools/miri/src/shims/unix/macos/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
1 change: 1 addition & 0 deletions typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading