Skip to content

Commit a3b8e72

Browse files
committed
Move Timespec impl to common to use in Hermit
1 parent 0208ee0 commit a3b8e72

File tree

5 files changed

+191
-183
lines changed

5 files changed

+191
-183
lines changed

library/std/src/sys/pal/common/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#![allow(dead_code)]
1212

1313
pub mod small_c_string;
14+
pub(crate) mod timespec;
1415

1516
#[cfg(test)]
1617
mod tests;
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
use core::num::niche_types::Nanoseconds;
2+
3+
use crate::io;
4+
use crate::time::Duration;
5+
6+
const NSEC_PER_SEC: u64 = 1_000_000_000;
7+
8+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
9+
pub(crate) struct Timespec {
10+
pub(crate) tv_sec: i64,
11+
pub(crate) tv_nsec: Nanoseconds,
12+
}
13+
14+
impl Timespec {
15+
pub(crate) const MAX: Timespec = unsafe { Self::new_unchecked(i64::MAX, 1_000_000_000 - 1) };
16+
17+
// As described below, on Apple OS, dates before epoch are represented differently.
18+
// This is not an issue here however, because we are using tv_sec = i64::MIN,
19+
// which will cause the compatibility wrapper to not be executed at all.
20+
pub(crate) const MIN: Timespec = unsafe { Self::new_unchecked(i64::MIN, 0) };
21+
22+
const unsafe fn new_unchecked(tv_sec: i64, tv_nsec: i64) -> Timespec {
23+
Timespec { tv_sec, tv_nsec: unsafe { Nanoseconds::new_unchecked(tv_nsec as u32) } }
24+
}
25+
26+
pub const fn zero() -> Timespec {
27+
unsafe { Self::new_unchecked(0, 0) }
28+
}
29+
30+
pub(crate) const fn new(tv_sec: i64, tv_nsec: i64) -> Result<Timespec, io::Error> {
31+
// On Apple OS, dates before epoch are represented differently than on other
32+
// Unix platforms: e.g. 1/10th of a second before epoch is represented as `seconds=-1`
33+
// and `nanoseconds=100_000_000` on other platforms, but is `seconds=0` and
34+
// `nanoseconds=-900_000_000` on Apple OS.
35+
//
36+
// To compensate, we first detect this special case by checking if both
37+
// seconds and nanoseconds are in range, and then correct the value for seconds
38+
// and nanoseconds to match the common unix representation.
39+
//
40+
// Please note that Apple OS nonetheless accepts the standard unix format when
41+
// setting file times, which makes this compensation round-trippable and generally
42+
// transparent.
43+
#[cfg(target_vendor = "apple")]
44+
let (tv_sec, tv_nsec) =
45+
if (tv_sec <= 0 && tv_sec > i64::MIN) && (tv_nsec < 0 && tv_nsec > -1_000_000_000) {
46+
(tv_sec - 1, tv_nsec + 1_000_000_000)
47+
} else {
48+
(tv_sec, tv_nsec)
49+
};
50+
if tv_nsec >= 0 && tv_nsec < NSEC_PER_SEC as i64 {
51+
Ok(unsafe { Self::new_unchecked(tv_sec, tv_nsec) })
52+
} else {
53+
Err(io::const_error!(io::ErrorKind::InvalidData, "invalid timestamp"))
54+
}
55+
}
56+
57+
#[cfg(any(unix, target_os = "wasi"))]
58+
pub fn now(clock: libc::clockid_t) -> Timespec {
59+
use crate::mem::MaybeUninit;
60+
use crate::sys::cvt;
61+
62+
// Try to use 64-bit time in preparation for Y2038.
63+
#[cfg(all(
64+
target_os = "linux",
65+
target_env = "gnu",
66+
target_pointer_width = "32",
67+
not(target_arch = "riscv32")
68+
))]
69+
{
70+
use crate::sys::time::__timespec64;
71+
use crate::sys::weak::weak;
72+
73+
// __clock_gettime64 was added to 32-bit arches in glibc 2.34,
74+
// and it handles both vDSO calls and ENOSYS fallbacks itself.
75+
weak!(
76+
fn __clock_gettime64(
77+
clockid: libc::clockid_t,
78+
tp: *mut __timespec64,
79+
) -> libc::c_int;
80+
);
81+
82+
if let Some(clock_gettime64) = __clock_gettime64.get() {
83+
let mut t = MaybeUninit::uninit();
84+
cvt(unsafe { clock_gettime64(clock, t.as_mut_ptr()) }).unwrap();
85+
let t = unsafe { t.assume_init() };
86+
return Timespec::new(t.tv_sec as i64, t.tv_nsec as i64).unwrap();
87+
}
88+
}
89+
90+
let mut t = MaybeUninit::uninit();
91+
cvt(unsafe { libc::clock_gettime(clock, t.as_mut_ptr()) }).unwrap();
92+
let t = unsafe { t.assume_init() };
93+
Timespec::new(t.tv_sec as i64, t.tv_nsec as i64).unwrap()
94+
}
95+
96+
pub fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
97+
// When a >= b, the difference fits in u64.
98+
fn sub_ge_to_unsigned(a: i64, b: i64) -> u64 {
99+
debug_assert!(a >= b);
100+
a.wrapping_sub(b).cast_unsigned()
101+
}
102+
103+
if self >= other {
104+
let (secs, nsec) = if self.tv_nsec.as_inner() >= other.tv_nsec.as_inner() {
105+
(
106+
sub_ge_to_unsigned(self.tv_sec, other.tv_sec),
107+
self.tv_nsec.as_inner() - other.tv_nsec.as_inner(),
108+
)
109+
} else {
110+
// Following sequence of assertions explain why `self.tv_sec - 1` does not underflow.
111+
debug_assert!(self.tv_nsec < other.tv_nsec);
112+
debug_assert!(self.tv_sec > other.tv_sec);
113+
debug_assert!(self.tv_sec > i64::MIN);
114+
(
115+
sub_ge_to_unsigned(self.tv_sec - 1, other.tv_sec),
116+
self.tv_nsec.as_inner() + (NSEC_PER_SEC as u32) - other.tv_nsec.as_inner(),
117+
)
118+
};
119+
120+
Ok(Duration::new(secs, nsec))
121+
} else {
122+
match other.sub_timespec(self) {
123+
Ok(d) => Err(d),
124+
Err(d) => Ok(d),
125+
}
126+
}
127+
}
128+
129+
pub fn checked_add_duration(&self, other: &Duration) -> Option<Timespec> {
130+
let mut secs = self.tv_sec.checked_add_unsigned(other.as_secs())?;
131+
132+
// Nano calculations can't overflow because nanos are <1B which fit
133+
// in a u32.
134+
let mut nsec = other.subsec_nanos() + self.tv_nsec.as_inner();
135+
if nsec >= NSEC_PER_SEC as u32 {
136+
nsec -= NSEC_PER_SEC as u32;
137+
secs = secs.checked_add(1)?;
138+
}
139+
Some(unsafe { Timespec::new_unchecked(secs, nsec.into()) })
140+
}
141+
142+
pub fn checked_sub_duration(&self, other: &Duration) -> Option<Timespec> {
143+
let mut secs = self.tv_sec.checked_sub_unsigned(other.as_secs())?;
144+
145+
// Similar to above, nanos can't overflow.
146+
let mut nsec = self.tv_nsec.as_inner() as i32 - other.subsec_nanos() as i32;
147+
if nsec < 0 {
148+
nsec += NSEC_PER_SEC as i32;
149+
secs = secs.checked_sub(1)?;
150+
}
151+
Some(unsafe { Timespec::new_unchecked(secs, nsec.into()) })
152+
}
153+
154+
#[allow(dead_code)]
155+
#[cfg(any(unix, target_os = "wasi"))]
156+
pub fn to_timespec(&self) -> Option<libc::timespec> {
157+
Some(libc::timespec {
158+
tv_sec: self.tv_sec.try_into().ok()?,
159+
tv_nsec: self.tv_nsec.as_inner().try_into().ok()?,
160+
})
161+
}
162+
163+
// On QNX Neutrino, the maximum timespec for e.g. pthread_cond_timedwait
164+
// is 2^64 nanoseconds
165+
#[cfg(target_os = "nto")]
166+
pub(in crate::sys) fn to_timespec_capped(&self) -> Option<libc::timespec> {
167+
// Check if timeout in nanoseconds would fit into an u64
168+
if (self.tv_nsec.as_inner() as u64)
169+
.checked_add((self.tv_sec as u64).checked_mul(NSEC_PER_SEC)?)
170+
.is_none()
171+
{
172+
return None;
173+
}
174+
self.to_timespec()
175+
}
176+
177+
#[cfg(all(
178+
target_os = "linux",
179+
target_env = "gnu",
180+
target_pointer_width = "32",
181+
not(target_arch = "riscv32")
182+
))]
183+
pub fn to_timespec64(&self) -> crate::sys::time::__timespec64 {
184+
crate::sys::time::__timespec64::new(self.tv_sec, self.tv_nsec.as_inner() as _)
185+
}
186+
}

library/std/src/sys/pal/hermit/time.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,9 @@ pub struct SystemTime(Timespec);
213213
pub const UNIX_EPOCH: SystemTime = SystemTime(Timespec::zero());
214214

215215
impl SystemTime {
216-
pub const MAX: SystemTime = SystemTime { t: Timespec::MAX };
216+
pub const MAX: SystemTime = SystemTime(Timespec::MAX);
217217

218-
pub const MIN: SystemTime = SystemTime { t: Timespec::MIN };
218+
pub const MIN: SystemTime = SystemTime(Timespec::MIN);
219219

220220
pub fn new(tv_sec: i64, tv_nsec: i32) -> SystemTime {
221221
SystemTime(Timespec::new(tv_sec, tv_nsec))

library/std/src/sys/pal/unix/futex.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ pub type SmallPrimitive = u32;
2828
/// Returns false on timeout, and true in all other cases.
2929
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
3030
pub fn futex_wait(futex: &Atomic<u32>, expected: u32, timeout: Option<Duration>) -> bool {
31-
use super::time::Timespec;
3231
use crate::ptr::null;
3332
use crate::sync::atomic::Ordering::Relaxed;
33+
use crate::sys::common::timespec::Timespec;
3434

3535
// Calculate the timeout as an absolute timespec.
3636
//

0 commit comments

Comments
 (0)