Skip to content

Commit e2ea419

Browse files
committed
Share Timespec between Unix and Hermit
1 parent 25d319a commit e2ea419

File tree

5 files changed

+189
-283
lines changed

5 files changed

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

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

Lines changed: 9 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,23 @@
11
#![allow(dead_code)]
22

3-
use core::hash::{Hash, Hasher};
3+
use core::hash::Hash;
44

55
use super::hermit_abi::{self, CLOCK_MONOTONIC, CLOCK_REALTIME, timespec};
6-
use crate::cmp::Ordering;
76
use crate::ops::{Add, AddAssign, Sub, SubAssign};
7+
use crate::sys::common::timespec::Timespec;
88
use crate::time::Duration;
99

1010
const NSEC_PER_SEC: i32 = 1_000_000_000;
1111

12-
#[derive(Copy, Clone, Debug)]
13-
struct Timespec {
14-
t: timespec,
15-
}
16-
17-
impl Timespec {
18-
const fn zero() -> Timespec {
19-
Timespec { t: timespec { tv_sec: 0, tv_nsec: 0 } }
20-
}
21-
22-
const fn new(tv_sec: i64, tv_nsec: i32) -> Timespec {
23-
assert!(tv_nsec >= 0 && tv_nsec < NSEC_PER_SEC);
24-
// SAFETY: The assert above checks tv_nsec is within the valid range
25-
Timespec { t: timespec { tv_sec, tv_nsec } }
26-
}
27-
28-
fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
29-
fn sub_ge_to_unsigned(a: i64, b: i64) -> u64 {
30-
debug_assert!(a >= b);
31-
a.wrapping_sub(b).cast_unsigned()
32-
}
33-
34-
if self >= other {
35-
// Logic here is identical to Unix version of `Timestamp::sub_timespec`,
36-
// check comments there why operations do not overflow.
37-
Ok(if self.t.tv_nsec >= other.t.tv_nsec {
38-
Duration::new(
39-
sub_ge_to_unsigned(self.t.tv_sec, other.t.tv_sec),
40-
(self.t.tv_nsec - other.t.tv_nsec) as u32,
41-
)
42-
} else {
43-
Duration::new(
44-
sub_ge_to_unsigned(self.t.tv_sec - 1, other.t.tv_sec),
45-
(self.t.tv_nsec + NSEC_PER_SEC - other.t.tv_nsec) as u32,
46-
)
47-
})
48-
} else {
49-
match other.sub_timespec(self) {
50-
Ok(d) => Err(d),
51-
Err(d) => Ok(d),
52-
}
53-
}
54-
}
55-
56-
fn checked_add_duration(&self, other: &Duration) -> Option<Timespec> {
57-
let mut secs = self.t.tv_sec.checked_add_unsigned(other.as_secs())?;
58-
59-
// Nano calculations can't overflow because nanos are <1B which fit
60-
// in a u32.
61-
let mut nsec = other.subsec_nanos() + u32::try_from(self.t.tv_nsec).unwrap();
62-
if nsec >= NSEC_PER_SEC.try_into().unwrap() {
63-
nsec -= u32::try_from(NSEC_PER_SEC).unwrap();
64-
secs = secs.checked_add(1)?;
65-
}
66-
Some(Timespec { t: timespec { tv_sec: secs, tv_nsec: nsec as _ } })
67-
}
68-
69-
fn checked_sub_duration(&self, other: &Duration) -> Option<Timespec> {
70-
let mut secs = self.t.tv_sec.checked_sub_unsigned(other.as_secs())?;
71-
72-
// Similar to above, nanos can't overflow.
73-
let mut nsec = self.t.tv_nsec as i32 - other.subsec_nanos() as i32;
74-
if nsec < 0 {
75-
nsec += NSEC_PER_SEC as i32;
76-
secs = secs.checked_sub(1)?;
77-
}
78-
Some(Timespec { t: timespec { tv_sec: secs, tv_nsec: nsec as _ } })
79-
}
80-
}
81-
82-
impl PartialEq for Timespec {
83-
fn eq(&self, other: &Timespec) -> bool {
84-
self.t.tv_sec == other.t.tv_sec && self.t.tv_nsec == other.t.tv_nsec
85-
}
86-
}
87-
88-
impl Eq for Timespec {}
89-
90-
impl PartialOrd for Timespec {
91-
fn partial_cmp(&self, other: &Timespec) -> Option<Ordering> {
92-
Some(self.cmp(other))
93-
}
94-
}
95-
96-
impl Ord for Timespec {
97-
fn cmp(&self, other: &Timespec) -> Ordering {
98-
let me = (self.t.tv_sec, self.t.tv_nsec);
99-
let other = (other.t.tv_sec, other.t.tv_nsec);
100-
me.cmp(&other)
101-
}
102-
}
103-
104-
impl Hash for Timespec {
105-
fn hash<H: Hasher>(&self, state: &mut H) {
106-
self.t.tv_sec.hash(state);
107-
self.t.tv_nsec.hash(state);
108-
}
109-
}
110-
11112
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
11213
pub struct Instant(Timespec);
11314

11415
impl Instant {
11516
pub fn now() -> Instant {
116-
let mut time: Timespec = Timespec::zero();
117-
let _ = unsafe { hermit_abi::clock_gettime(CLOCK_MONOTONIC, &raw mut time.t) };
17+
let mut time: timespec = timespec { tv_sec: 0, tv_nsec: 0 };
18+
let _ = unsafe { hermit_abi::clock_gettime(CLOCK_MONOTONIC, &raw mut time) };
11819

119-
Instant(time)
20+
Instant(Timespec::new(time.tv_sec, time.tv_nsec as i64).unwrap())
12021
}
12122

12223
#[stable(feature = "time2", since = "1.8.0")]
@@ -210,14 +111,13 @@ pub const UNIX_EPOCH: SystemTime = SystemTime(Timespec::zero());
210111

211112
impl SystemTime {
212113
pub fn new(tv_sec: i64, tv_nsec: i32) -> SystemTime {
213-
SystemTime(Timespec::new(tv_sec, tv_nsec))
114+
SystemTime(Timespec::new(tv_sec, tv_nsec as i64).unwrap())
214115
}
215116

216117
pub fn now() -> SystemTime {
217-
let mut time: Timespec = Timespec::zero();
218-
let _ = unsafe { hermit_abi::clock_gettime(CLOCK_REALTIME, &raw mut time.t) };
219-
220-
SystemTime(time)
118+
let mut time: timespec = timespec { tv_sec: 0, tv_nsec: 0 };
119+
let _ = unsafe { hermit_abi::clock_gettime(CLOCK_REALTIME, &raw mut time) };
120+
SystemTime::new(time.tv_sec, time.tv_nsec)
221121
}
222122

223123
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {

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)