diff --git a/library/std/src/sys/pal/common/mod.rs b/library/std/src/sys/pal/common/mod.rs index 9af4dee401cf3..8f89df813b7fe 100644 --- a/library/std/src/sys/pal/common/mod.rs +++ b/library/std/src/sys/pal/common/mod.rs @@ -11,6 +11,7 @@ #![allow(dead_code)] pub mod small_c_string; +pub(crate) mod timespec; #[cfg(test)] mod tests; diff --git a/library/std/src/sys/pal/common/timespec.rs b/library/std/src/sys/pal/common/timespec.rs new file mode 100644 index 0000000000000..2e33c26a29682 --- /dev/null +++ b/library/std/src/sys/pal/common/timespec.rs @@ -0,0 +1,179 @@ +use core::num::niche_types::Nanoseconds; + +use crate::io; +use crate::time::Duration; + +const NSEC_PER_SEC: u64 = 1_000_000_000; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub(crate) struct Timespec { + pub(crate) tv_sec: i64, + pub(crate) tv_nsec: Nanoseconds, +} + +impl Timespec { + const unsafe fn new_unchecked(tv_sec: i64, tv_nsec: i64) -> Timespec { + Timespec { tv_sec, tv_nsec: unsafe { Nanoseconds::new_unchecked(tv_nsec as u32) } } + } + + pub const fn zero() -> Timespec { + unsafe { Self::new_unchecked(0, 0) } + } + + pub(crate) const fn new(tv_sec: i64, tv_nsec: i64) -> Result { + // On Apple OS, dates before epoch are represented differently than on other + // Unix platforms: e.g. 1/10th of a second before epoch is represented as `seconds=-1` + // and `nanoseconds=100_000_000` on other platforms, but is `seconds=0` and + // `nanoseconds=-900_000_000` on Apple OS. + // + // To compensate, we first detect this special case by checking if both + // seconds and nanoseconds are in range, and then correct the value for seconds + // and nanoseconds to match the common unix representation. + // + // Please note that Apple OS nonetheless accepts the standard unix format when + // setting file times, which makes this compensation round-trippable and generally + // transparent. + #[cfg(target_vendor = "apple")] + let (tv_sec, tv_nsec) = + if (tv_sec <= 0 && tv_sec > i64::MIN) && (tv_nsec < 0 && tv_nsec > -1_000_000_000) { + (tv_sec - 1, tv_nsec + 1_000_000_000) + } else { + (tv_sec, tv_nsec) + }; + if tv_nsec >= 0 && tv_nsec < NSEC_PER_SEC as i64 { + Ok(unsafe { Self::new_unchecked(tv_sec, tv_nsec) }) + } else { + Err(io::const_error!(io::ErrorKind::InvalidData, "invalid timestamp")) + } + } + + #[cfg(unix)] + pub fn now(clock: libc::clockid_t) -> Timespec { + use crate::mem::MaybeUninit; + use crate::sys::cvt; + + // Try to use 64-bit time in preparation for Y2038. + #[cfg(all( + target_os = "linux", + target_env = "gnu", + target_pointer_width = "32", + not(target_arch = "riscv32") + ))] + { + use crate::sys::time::__timespec64; + use crate::sys::weak::weak; + + // __clock_gettime64 was added to 32-bit arches in glibc 2.34, + // and it handles both vDSO calls and ENOSYS fallbacks itself. + weak!( + fn __clock_gettime64( + clockid: libc::clockid_t, + tp: *mut __timespec64, + ) -> libc::c_int; + ); + + if let Some(clock_gettime64) = __clock_gettime64.get() { + let mut t = MaybeUninit::uninit(); + cvt(unsafe { clock_gettime64(clock, t.as_mut_ptr()) }).unwrap(); + let t = unsafe { t.assume_init() }; + return Timespec::new(t.tv_sec as i64, t.tv_nsec as i64).unwrap(); + } + } + + let mut t = MaybeUninit::uninit(); + cvt(unsafe { libc::clock_gettime(clock, t.as_mut_ptr()) }).unwrap(); + let t = unsafe { t.assume_init() }; + Timespec::new(t.tv_sec as i64, t.tv_nsec as i64).unwrap() + } + + pub fn sub_timespec(&self, other: &Timespec) -> Result { + // When a >= b, the difference fits in u64. + fn sub_ge_to_unsigned(a: i64, b: i64) -> u64 { + debug_assert!(a >= b); + a.wrapping_sub(b).cast_unsigned() + } + + if self >= other { + let (secs, nsec) = if self.tv_nsec.as_inner() >= other.tv_nsec.as_inner() { + ( + sub_ge_to_unsigned(self.tv_sec, other.tv_sec), + self.tv_nsec.as_inner() - other.tv_nsec.as_inner(), + ) + } else { + // Following sequence of assertions explain why `self.tv_sec - 1` does not underflow. + debug_assert!(self.tv_nsec < other.tv_nsec); + debug_assert!(self.tv_sec > other.tv_sec); + debug_assert!(self.tv_sec > i64::MIN); + ( + sub_ge_to_unsigned(self.tv_sec - 1, other.tv_sec), + self.tv_nsec.as_inner() + (NSEC_PER_SEC as u32) - other.tv_nsec.as_inner(), + ) + }; + + Ok(Duration::new(secs, nsec)) + } else { + match other.sub_timespec(self) { + Ok(d) => Err(d), + Err(d) => Ok(d), + } + } + } + + pub fn checked_add_duration(&self, other: &Duration) -> Option { + let mut secs = self.tv_sec.checked_add_unsigned(other.as_secs())?; + + // Nano calculations can't overflow because nanos are <1B which fit + // in a u32. + let mut nsec = other.subsec_nanos() + self.tv_nsec.as_inner(); + if nsec >= NSEC_PER_SEC as u32 { + nsec -= NSEC_PER_SEC as u32; + secs = secs.checked_add(1)?; + } + Some(unsafe { Timespec::new_unchecked(secs, nsec.into()) }) + } + + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + let mut secs = self.tv_sec.checked_sub_unsigned(other.as_secs())?; + + // Similar to above, nanos can't overflow. + let mut nsec = self.tv_nsec.as_inner() as i32 - other.subsec_nanos() as i32; + if nsec < 0 { + nsec += NSEC_PER_SEC as i32; + secs = secs.checked_sub(1)?; + } + Some(unsafe { Timespec::new_unchecked(secs, nsec.into()) }) + } + + #[allow(dead_code)] + #[cfg(unix)] + pub fn to_timespec(&self) -> Option { + Some(libc::timespec { + tv_sec: self.tv_sec.try_into().ok()?, + tv_nsec: self.tv_nsec.as_inner().try_into().ok()?, + }) + } + + // On QNX Neutrino, the maximum timespec for e.g. pthread_cond_timedwait + // is 2^64 nanoseconds + #[cfg(target_os = "nto")] + pub(in crate::sys) fn to_timespec_capped(&self) -> Option { + // Check if timeout in nanoseconds would fit into an u64 + if (self.tv_nsec.as_inner() as u64) + .checked_add((self.tv_sec as u64).checked_mul(NSEC_PER_SEC)?) + .is_none() + { + return None; + } + self.to_timespec() + } + + #[cfg(all( + target_os = "linux", + target_env = "gnu", + target_pointer_width = "32", + not(target_arch = "riscv32") + ))] + pub fn to_timespec64(&self) -> crate::sys::time::__timespec64 { + crate::sys::time::__timespec64::new(self.tv_sec, self.tv_nsec.as_inner() as _) + } +} diff --git a/library/std/src/sys/pal/hermit/time.rs b/library/std/src/sys/pal/hermit/time.rs index bd6fd5a3de428..22b0bc553bda3 100644 --- a/library/std/src/sys/pal/hermit/time.rs +++ b/library/std/src/sys/pal/hermit/time.rs @@ -1,122 +1,23 @@ #![allow(dead_code)] -use core::hash::{Hash, Hasher}; +use core::hash::Hash; use super::hermit_abi::{self, CLOCK_MONOTONIC, CLOCK_REALTIME, timespec}; -use crate::cmp::Ordering; use crate::ops::{Add, AddAssign, Sub, SubAssign}; +use crate::sys::common::timespec::Timespec; use crate::time::Duration; const NSEC_PER_SEC: i32 = 1_000_000_000; -#[derive(Copy, Clone, Debug)] -struct Timespec { - t: timespec, -} - -impl Timespec { - const fn zero() -> Timespec { - Timespec { t: timespec { tv_sec: 0, tv_nsec: 0 } } - } - - const fn new(tv_sec: i64, tv_nsec: i32) -> Timespec { - assert!(tv_nsec >= 0 && tv_nsec < NSEC_PER_SEC); - // SAFETY: The assert above checks tv_nsec is within the valid range - Timespec { t: timespec { tv_sec, tv_nsec } } - } - - fn sub_timespec(&self, other: &Timespec) -> Result { - fn sub_ge_to_unsigned(a: i64, b: i64) -> u64 { - debug_assert!(a >= b); - a.wrapping_sub(b).cast_unsigned() - } - - if self >= other { - // Logic here is identical to Unix version of `Timestamp::sub_timespec`, - // check comments there why operations do not overflow. - Ok(if self.t.tv_nsec >= other.t.tv_nsec { - Duration::new( - sub_ge_to_unsigned(self.t.tv_sec, other.t.tv_sec), - (self.t.tv_nsec - other.t.tv_nsec) as u32, - ) - } else { - Duration::new( - sub_ge_to_unsigned(self.t.tv_sec - 1, other.t.tv_sec), - (self.t.tv_nsec + NSEC_PER_SEC - other.t.tv_nsec) as u32, - ) - }) - } else { - match other.sub_timespec(self) { - Ok(d) => Err(d), - Err(d) => Ok(d), - } - } - } - - fn checked_add_duration(&self, other: &Duration) -> Option { - let mut secs = self.t.tv_sec.checked_add_unsigned(other.as_secs())?; - - // Nano calculations can't overflow because nanos are <1B which fit - // in a u32. - let mut nsec = other.subsec_nanos() + u32::try_from(self.t.tv_nsec).unwrap(); - if nsec >= NSEC_PER_SEC.try_into().unwrap() { - nsec -= u32::try_from(NSEC_PER_SEC).unwrap(); - secs = secs.checked_add(1)?; - } - Some(Timespec { t: timespec { tv_sec: secs, tv_nsec: nsec as _ } }) - } - - fn checked_sub_duration(&self, other: &Duration) -> Option { - let mut secs = self.t.tv_sec.checked_sub_unsigned(other.as_secs())?; - - // Similar to above, nanos can't overflow. - let mut nsec = self.t.tv_nsec as i32 - other.subsec_nanos() as i32; - if nsec < 0 { - nsec += NSEC_PER_SEC as i32; - secs = secs.checked_sub(1)?; - } - Some(Timespec { t: timespec { tv_sec: secs, tv_nsec: nsec as _ } }) - } -} - -impl PartialEq for Timespec { - fn eq(&self, other: &Timespec) -> bool { - self.t.tv_sec == other.t.tv_sec && self.t.tv_nsec == other.t.tv_nsec - } -} - -impl Eq for Timespec {} - -impl PartialOrd for Timespec { - fn partial_cmp(&self, other: &Timespec) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Timespec { - fn cmp(&self, other: &Timespec) -> Ordering { - let me = (self.t.tv_sec, self.t.tv_nsec); - let other = (other.t.tv_sec, other.t.tv_nsec); - me.cmp(&other) - } -} - -impl Hash for Timespec { - fn hash(&self, state: &mut H) { - self.t.tv_sec.hash(state); - self.t.tv_nsec.hash(state); - } -} - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct Instant(Timespec); impl Instant { pub fn now() -> Instant { - let mut time: Timespec = Timespec::zero(); - let _ = unsafe { hermit_abi::clock_gettime(CLOCK_MONOTONIC, &raw mut time.t) }; + let mut time: timespec = timespec { tv_sec: 0, tv_nsec: 0 }; + let _ = unsafe { hermit_abi::clock_gettime(CLOCK_MONOTONIC, &raw mut time) }; - Instant(time) + Instant(Timespec::new(time.tv_sec, time.tv_nsec as i64).unwrap()) } #[stable(feature = "time2", since = "1.8.0")] @@ -210,14 +111,13 @@ pub const UNIX_EPOCH: SystemTime = SystemTime(Timespec::zero()); impl SystemTime { pub fn new(tv_sec: i64, tv_nsec: i32) -> SystemTime { - SystemTime(Timespec::new(tv_sec, tv_nsec)) + SystemTime(Timespec::new(tv_sec, tv_nsec as i64).unwrap()) } pub fn now() -> SystemTime { - let mut time: Timespec = Timespec::zero(); - let _ = unsafe { hermit_abi::clock_gettime(CLOCK_REALTIME, &raw mut time.t) }; - - SystemTime(time) + let mut time: timespec = timespec { tv_sec: 0, tv_nsec: 0 }; + let _ = unsafe { hermit_abi::clock_gettime(CLOCK_REALTIME, &raw mut time) }; + SystemTime::new(time.tv_sec, time.tv_nsec) } pub fn sub_time(&self, other: &SystemTime) -> Result { diff --git a/library/std/src/sys/pal/unix/futex.rs b/library/std/src/sys/pal/unix/futex.rs index 265067d84d502..5c238d9a2fb81 100644 --- a/library/std/src/sys/pal/unix/futex.rs +++ b/library/std/src/sys/pal/unix/futex.rs @@ -28,9 +28,9 @@ pub type SmallPrimitive = u32; /// Returns false on timeout, and true in all other cases. #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))] pub fn futex_wait(futex: &Atomic, expected: u32, timeout: Option) -> bool { - use super::time::Timespec; use crate::ptr::null; use crate::sync::atomic::Ordering::Relaxed; + use crate::sys::common::timespec::Timespec; // Calculate the timeout as an absolute timespec. // diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs index c207f41cad4b5..be796763f486f 100644 --- a/library/std/src/sys/pal/unix/time.rs +++ b/library/std/src/sys/pal/unix/time.rs @@ -1,9 +1,7 @@ -use core::num::niche_types::Nanoseconds; - +use crate::sys::common::timespec::Timespec; use crate::time::Duration; use crate::{fmt, io}; -const NSEC_PER_SEC: u64 = 1_000_000_000; pub const UNIX_EPOCH: SystemTime = SystemTime { t: Timespec::zero() }; #[allow(dead_code)] // Used for pthread condvar timeouts pub const TIMESPEC_MAX: libc::timespec = @@ -22,12 +20,6 @@ pub struct SystemTime { pub(crate) t: Timespec, } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct Timespec { - tv_sec: i64, - tv_nsec: Nanoseconds, -} - impl SystemTime { #[cfg_attr(any(target_os = "horizon", target_os = "hurd"), allow(unused))] pub fn new(tv_sec: i64, tv_nsec: i64) -> Result { @@ -60,170 +52,6 @@ impl fmt::Debug for SystemTime { } } -impl Timespec { - const unsafe fn new_unchecked(tv_sec: i64, tv_nsec: i64) -> Timespec { - Timespec { tv_sec, tv_nsec: unsafe { Nanoseconds::new_unchecked(tv_nsec as u32) } } - } - - pub const fn zero() -> Timespec { - unsafe { Self::new_unchecked(0, 0) } - } - - const fn new(tv_sec: i64, tv_nsec: i64) -> Result { - // On Apple OS, dates before epoch are represented differently than on other - // Unix platforms: e.g. 1/10th of a second before epoch is represented as `seconds=-1` - // and `nanoseconds=100_000_000` on other platforms, but is `seconds=0` and - // `nanoseconds=-900_000_000` on Apple OS. - // - // To compensate, we first detect this special case by checking if both - // seconds and nanoseconds are in range, and then correct the value for seconds - // and nanoseconds to match the common unix representation. - // - // Please note that Apple OS nonetheless accepts the standard unix format when - // setting file times, which makes this compensation round-trippable and generally - // transparent. - #[cfg(target_vendor = "apple")] - let (tv_sec, tv_nsec) = - if (tv_sec <= 0 && tv_sec > i64::MIN) && (tv_nsec < 0 && tv_nsec > -1_000_000_000) { - (tv_sec - 1, tv_nsec + 1_000_000_000) - } else { - (tv_sec, tv_nsec) - }; - if tv_nsec >= 0 && tv_nsec < NSEC_PER_SEC as i64 { - Ok(unsafe { Self::new_unchecked(tv_sec, tv_nsec) }) - } else { - Err(io::const_error!(io::ErrorKind::InvalidData, "invalid timestamp")) - } - } - - pub fn now(clock: libc::clockid_t) -> Timespec { - use crate::mem::MaybeUninit; - use crate::sys::cvt; - - // Try to use 64-bit time in preparation for Y2038. - #[cfg(all( - target_os = "linux", - target_env = "gnu", - target_pointer_width = "32", - not(target_arch = "riscv32") - ))] - { - use crate::sys::weak::weak; - - // __clock_gettime64 was added to 32-bit arches in glibc 2.34, - // and it handles both vDSO calls and ENOSYS fallbacks itself. - weak!( - fn __clock_gettime64( - clockid: libc::clockid_t, - tp: *mut __timespec64, - ) -> libc::c_int; - ); - - if let Some(clock_gettime64) = __clock_gettime64.get() { - let mut t = MaybeUninit::uninit(); - cvt(unsafe { clock_gettime64(clock, t.as_mut_ptr()) }).unwrap(); - let t = unsafe { t.assume_init() }; - return Timespec::new(t.tv_sec as i64, t.tv_nsec as i64).unwrap(); - } - } - - let mut t = MaybeUninit::uninit(); - cvt(unsafe { libc::clock_gettime(clock, t.as_mut_ptr()) }).unwrap(); - let t = unsafe { t.assume_init() }; - Timespec::new(t.tv_sec as i64, t.tv_nsec as i64).unwrap() - } - - pub fn sub_timespec(&self, other: &Timespec) -> Result { - // When a >= b, the difference fits in u64. - fn sub_ge_to_unsigned(a: i64, b: i64) -> u64 { - debug_assert!(a >= b); - a.wrapping_sub(b).cast_unsigned() - } - - if self >= other { - let (secs, nsec) = if self.tv_nsec.as_inner() >= other.tv_nsec.as_inner() { - ( - sub_ge_to_unsigned(self.tv_sec, other.tv_sec), - self.tv_nsec.as_inner() - other.tv_nsec.as_inner(), - ) - } else { - // Following sequence of assertions explain why `self.tv_sec - 1` does not underflow. - debug_assert!(self.tv_nsec < other.tv_nsec); - debug_assert!(self.tv_sec > other.tv_sec); - debug_assert!(self.tv_sec > i64::MIN); - ( - sub_ge_to_unsigned(self.tv_sec - 1, other.tv_sec), - self.tv_nsec.as_inner() + (NSEC_PER_SEC as u32) - other.tv_nsec.as_inner(), - ) - }; - - Ok(Duration::new(secs, nsec)) - } else { - match other.sub_timespec(self) { - Ok(d) => Err(d), - Err(d) => Ok(d), - } - } - } - - pub fn checked_add_duration(&self, other: &Duration) -> Option { - let mut secs = self.tv_sec.checked_add_unsigned(other.as_secs())?; - - // Nano calculations can't overflow because nanos are <1B which fit - // in a u32. - let mut nsec = other.subsec_nanos() + self.tv_nsec.as_inner(); - if nsec >= NSEC_PER_SEC as u32 { - nsec -= NSEC_PER_SEC as u32; - secs = secs.checked_add(1)?; - } - Some(unsafe { Timespec::new_unchecked(secs, nsec.into()) }) - } - - pub fn checked_sub_duration(&self, other: &Duration) -> Option { - let mut secs = self.tv_sec.checked_sub_unsigned(other.as_secs())?; - - // Similar to above, nanos can't overflow. - let mut nsec = self.tv_nsec.as_inner() as i32 - other.subsec_nanos() as i32; - if nsec < 0 { - nsec += NSEC_PER_SEC as i32; - secs = secs.checked_sub(1)?; - } - Some(unsafe { Timespec::new_unchecked(secs, nsec.into()) }) - } - - #[allow(dead_code)] - pub fn to_timespec(&self) -> Option { - Some(libc::timespec { - tv_sec: self.tv_sec.try_into().ok()?, - tv_nsec: self.tv_nsec.as_inner().try_into().ok()?, - }) - } - - // On QNX Neutrino, the maximum timespec for e.g. pthread_cond_timedwait - // is 2^64 nanoseconds - #[cfg(target_os = "nto")] - pub(in crate::sys) fn to_timespec_capped(&self) -> Option { - // Check if timeout in nanoseconds would fit into an u64 - if (self.tv_nsec.as_inner() as u64) - .checked_add((self.tv_sec as u64).checked_mul(NSEC_PER_SEC)?) - .is_none() - { - return None; - } - self.to_timespec() - } - - #[cfg(all( - target_os = "linux", - target_env = "gnu", - target_pointer_width = "32", - not(target_arch = "riscv32") - ))] - pub fn to_timespec64(&self) -> __timespec64 { - __timespec64::new(self.tv_sec, self.tv_nsec.as_inner() as _) - } -} - #[cfg(all( target_os = "linux", target_env = "gnu",