Skip to content
Open
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/sys/pal/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#![allow(dead_code)]

pub mod small_c_string;
pub(crate) mod timespec;

#[cfg(test)]
mod tests;
179 changes: 179 additions & 0 deletions library/std/src/sys/pal/common/timespec.rs
Original file line number Diff line number Diff line change
@@ -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<Timespec, io::Error> {
// 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<Duration, Duration> {
// 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<Timespec> {
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<Timespec> {
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<libc::timespec> {
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<libc::timespec> {
// 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 _)
}
}
118 changes: 9 additions & 109 deletions library/std/src/sys/pal/hermit/time.rs
Original file line number Diff line number Diff line change
@@ -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<Duration, Duration> {
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<Timespec> {
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<Timespec> {
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<Ordering> {
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<H: Hasher>(&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")]
Expand Down Expand Up @@ -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<Duration, Duration> {
Expand Down
2 changes: 1 addition & 1 deletion library/std/src/sys/pal/unix/futex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u32>, expected: u32, timeout: Option<Duration>) -> 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.
//
Expand Down
Loading
Loading