Skip to content

Commit 7bdbceb

Browse files
committed
Move Timespec impl to common to use in Hermit
1 parent bad5026 commit 7bdbceb

File tree

4 files changed

+182
-174
lines changed

4 files changed

+182
-174
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: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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(any(unix, target_os = "wasi"))]
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::time::__timespec64;
64+
use crate::sys::weak::weak;
65+
66+
// __clock_gettime64 was added to 32-bit arches in glibc 2.34,
67+
// and it handles both vDSO calls and ENOSYS fallbacks itself.
68+
weak!(
69+
fn __clock_gettime64(
70+
clockid: libc::clockid_t,
71+
tp: *mut __timespec64,
72+
) -> libc::c_int;
73+
);
74+
75+
if let Some(clock_gettime64) = __clock_gettime64.get() {
76+
let mut t = MaybeUninit::uninit();
77+
cvt(unsafe { clock_gettime64(clock, t.as_mut_ptr()) }).unwrap();
78+
let t = unsafe { t.assume_init() };
79+
return Timespec::new(t.tv_sec as i64, t.tv_nsec as i64).unwrap();
80+
}
81+
}
82+
83+
let mut t = MaybeUninit::uninit();
84+
cvt(unsafe { libc::clock_gettime(clock, t.as_mut_ptr()) }).unwrap();
85+
let t = unsafe { t.assume_init() };
86+
Timespec::new(t.tv_sec as i64, t.tv_nsec as i64).unwrap()
87+
}
88+
89+
pub fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
90+
// When a >= b, the difference fits in u64.
91+
fn sub_ge_to_unsigned(a: i64, b: i64) -> u64 {
92+
debug_assert!(a >= b);
93+
a.wrapping_sub(b).cast_unsigned()
94+
}
95+
96+
if self >= other {
97+
let (secs, nsec) = if self.tv_nsec.as_inner() >= other.tv_nsec.as_inner() {
98+
(
99+
sub_ge_to_unsigned(self.tv_sec, other.tv_sec),
100+
self.tv_nsec.as_inner() - other.tv_nsec.as_inner(),
101+
)
102+
} else {
103+
// Following sequence of assertions explain why `self.tv_sec - 1` does not underflow.
104+
debug_assert!(self.tv_nsec < other.tv_nsec);
105+
debug_assert!(self.tv_sec > other.tv_sec);
106+
debug_assert!(self.tv_sec > i64::MIN);
107+
(
108+
sub_ge_to_unsigned(self.tv_sec - 1, other.tv_sec),
109+
self.tv_nsec.as_inner() + (NSEC_PER_SEC as u32) - other.tv_nsec.as_inner(),
110+
)
111+
};
112+
113+
Ok(Duration::new(secs, nsec))
114+
} else {
115+
match other.sub_timespec(self) {
116+
Ok(d) => Err(d),
117+
Err(d) => Ok(d),
118+
}
119+
}
120+
}
121+
122+
pub fn checked_add_duration(&self, other: &Duration) -> Option<Timespec> {
123+
let mut secs = self.tv_sec.checked_add_unsigned(other.as_secs())?;
124+
125+
// Nano calculations can't overflow because nanos are <1B which fit
126+
// in a u32.
127+
let mut nsec = other.subsec_nanos() + self.tv_nsec.as_inner();
128+
if nsec >= NSEC_PER_SEC as u32 {
129+
nsec -= NSEC_PER_SEC as u32;
130+
secs = secs.checked_add(1)?;
131+
}
132+
Some(unsafe { Timespec::new_unchecked(secs, nsec.into()) })
133+
}
134+
135+
pub fn checked_sub_duration(&self, other: &Duration) -> Option<Timespec> {
136+
let mut secs = self.tv_sec.checked_sub_unsigned(other.as_secs())?;
137+
138+
// Similar to above, nanos can't overflow.
139+
let mut nsec = self.tv_nsec.as_inner() as i32 - other.subsec_nanos() as i32;
140+
if nsec < 0 {
141+
nsec += NSEC_PER_SEC as i32;
142+
secs = secs.checked_sub(1)?;
143+
}
144+
Some(unsafe { Timespec::new_unchecked(secs, nsec.into()) })
145+
}
146+
147+
#[allow(dead_code)]
148+
#[cfg(any(unix, target_os = "wasi"))]
149+
pub fn to_timespec(&self) -> Option<libc::timespec> {
150+
Some(libc::timespec {
151+
tv_sec: self.tv_sec.try_into().ok()?,
152+
tv_nsec: self.tv_nsec.as_inner().try_into().ok()?,
153+
})
154+
}
155+
156+
// On QNX Neutrino, the maximum timespec for e.g. pthread_cond_timedwait
157+
// is 2^64 nanoseconds
158+
#[cfg(target_os = "nto")]
159+
pub(in crate::sys) fn to_timespec_capped(&self) -> Option<libc::timespec> {
160+
// Check if timeout in nanoseconds would fit into an u64
161+
if (self.tv_nsec.as_inner() as u64)
162+
.checked_add((self.tv_sec as u64).checked_mul(NSEC_PER_SEC)?)
163+
.is_none()
164+
{
165+
return None;
166+
}
167+
self.to_timespec()
168+
}
169+
170+
#[cfg(all(
171+
target_os = "linux",
172+
target_env = "gnu",
173+
target_pointer_width = "32",
174+
not(target_arch = "riscv32")
175+
))]
176+
pub fn to_timespec64(&self) -> crate::sys::time::__timespec64 {
177+
crate::sys::time::__timespec64::new(self.tv_sec, self.tv_nsec.as_inner() as _)
178+
}
179+
}

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
//

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

Lines changed: 1 addition & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
use core::num::niche_types::Nanoseconds;
2-
1+
use crate::sys::common::timespec::Timespec;
32
use crate::sys_common::AsInner;
43
use crate::time::Duration;
54
use crate::{fmt, io};
65

7-
const NSEC_PER_SEC: u64 = 1_000_000_000;
86
pub const UNIX_EPOCH: SystemTime = SystemTime { t: Timespec::zero() };
97
#[allow(dead_code)] // Used for pthread condvar timeouts
108
pub const TIMESPEC_MAX: libc::timespec =
@@ -23,12 +21,6 @@ pub struct SystemTime {
2321
pub(crate) t: Timespec,
2422
}
2523

26-
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
27-
pub(crate) struct Timespec {
28-
tv_sec: i64,
29-
tv_nsec: Nanoseconds,
30-
}
31-
3224
impl SystemTime {
3325
#[cfg_attr(any(target_os = "horizon", target_os = "hurd"), allow(unused))]
3426
pub fn new(tv_sec: i64, tv_nsec: i64) -> Result<SystemTime, io::Error> {
@@ -61,170 +53,6 @@ impl fmt::Debug for SystemTime {
6153
}
6254
}
6355

64-
impl Timespec {
65-
const unsafe fn new_unchecked(tv_sec: i64, tv_nsec: i64) -> Timespec {
66-
Timespec { tv_sec, tv_nsec: unsafe { Nanoseconds::new_unchecked(tv_nsec as u32) } }
67-
}
68-
69-
pub const fn zero() -> Timespec {
70-
unsafe { Self::new_unchecked(0, 0) }
71-
}
72-
73-
const fn new(tv_sec: i64, tv_nsec: i64) -> Result<Timespec, io::Error> {
74-
// On Apple OS, dates before epoch are represented differently than on other
75-
// Unix platforms: e.g. 1/10th of a second before epoch is represented as `seconds=-1`
76-
// and `nanoseconds=100_000_000` on other platforms, but is `seconds=0` and
77-
// `nanoseconds=-900_000_000` on Apple OS.
78-
//
79-
// To compensate, we first detect this special case by checking if both
80-
// seconds and nanoseconds are in range, and then correct the value for seconds
81-
// and nanoseconds to match the common unix representation.
82-
//
83-
// Please note that Apple OS nonetheless accepts the standard unix format when
84-
// setting file times, which makes this compensation round-trippable and generally
85-
// transparent.
86-
#[cfg(target_vendor = "apple")]
87-
let (tv_sec, tv_nsec) =
88-
if (tv_sec <= 0 && tv_sec > i64::MIN) && (tv_nsec < 0 && tv_nsec > -1_000_000_000) {
89-
(tv_sec - 1, tv_nsec + 1_000_000_000)
90-
} else {
91-
(tv_sec, tv_nsec)
92-
};
93-
if tv_nsec >= 0 && tv_nsec < NSEC_PER_SEC as i64 {
94-
Ok(unsafe { Self::new_unchecked(tv_sec, tv_nsec) })
95-
} else {
96-
Err(io::const_error!(io::ErrorKind::InvalidData, "invalid timestamp"))
97-
}
98-
}
99-
100-
pub fn now(clock: libc::clockid_t) -> Timespec {
101-
use crate::mem::MaybeUninit;
102-
use crate::sys::cvt;
103-
104-
// Try to use 64-bit time in preparation for Y2038.
105-
#[cfg(all(
106-
target_os = "linux",
107-
target_env = "gnu",
108-
target_pointer_width = "32",
109-
not(target_arch = "riscv32")
110-
))]
111-
{
112-
use crate::sys::weak::weak;
113-
114-
// __clock_gettime64 was added to 32-bit arches in glibc 2.34,
115-
// and it handles both vDSO calls and ENOSYS fallbacks itself.
116-
weak!(
117-
fn __clock_gettime64(
118-
clockid: libc::clockid_t,
119-
tp: *mut __timespec64,
120-
) -> libc::c_int;
121-
);
122-
123-
if let Some(clock_gettime64) = __clock_gettime64.get() {
124-
let mut t = MaybeUninit::uninit();
125-
cvt(unsafe { clock_gettime64(clock, t.as_mut_ptr()) }).unwrap();
126-
let t = unsafe { t.assume_init() };
127-
return Timespec::new(t.tv_sec as i64, t.tv_nsec as i64).unwrap();
128-
}
129-
}
130-
131-
let mut t = MaybeUninit::uninit();
132-
cvt(unsafe { libc::clock_gettime(clock, t.as_mut_ptr()) }).unwrap();
133-
let t = unsafe { t.assume_init() };
134-
Timespec::new(t.tv_sec as i64, t.tv_nsec as i64).unwrap()
135-
}
136-
137-
pub fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
138-
// When a >= b, the difference fits in u64.
139-
fn sub_ge_to_unsigned(a: i64, b: i64) -> u64 {
140-
debug_assert!(a >= b);
141-
a.wrapping_sub(b).cast_unsigned()
142-
}
143-
144-
if self >= other {
145-
let (secs, nsec) = if self.tv_nsec.as_inner() >= other.tv_nsec.as_inner() {
146-
(
147-
sub_ge_to_unsigned(self.tv_sec, other.tv_sec),
148-
self.tv_nsec.as_inner() - other.tv_nsec.as_inner(),
149-
)
150-
} else {
151-
// Following sequence of assertions explain why `self.tv_sec - 1` does not underflow.
152-
debug_assert!(self.tv_nsec < other.tv_nsec);
153-
debug_assert!(self.tv_sec > other.tv_sec);
154-
debug_assert!(self.tv_sec > i64::MIN);
155-
(
156-
sub_ge_to_unsigned(self.tv_sec - 1, other.tv_sec),
157-
self.tv_nsec.as_inner() + (NSEC_PER_SEC as u32) - other.tv_nsec.as_inner(),
158-
)
159-
};
160-
161-
Ok(Duration::new(secs, nsec))
162-
} else {
163-
match other.sub_timespec(self) {
164-
Ok(d) => Err(d),
165-
Err(d) => Ok(d),
166-
}
167-
}
168-
}
169-
170-
pub fn checked_add_duration(&self, other: &Duration) -> Option<Timespec> {
171-
let mut secs = self.tv_sec.checked_add_unsigned(other.as_secs())?;
172-
173-
// Nano calculations can't overflow because nanos are <1B which fit
174-
// in a u32.
175-
let mut nsec = other.subsec_nanos() + self.tv_nsec.as_inner();
176-
if nsec >= NSEC_PER_SEC as u32 {
177-
nsec -= NSEC_PER_SEC as u32;
178-
secs = secs.checked_add(1)?;
179-
}
180-
Some(unsafe { Timespec::new_unchecked(secs, nsec.into()) })
181-
}
182-
183-
pub fn checked_sub_duration(&self, other: &Duration) -> Option<Timespec> {
184-
let mut secs = self.tv_sec.checked_sub_unsigned(other.as_secs())?;
185-
186-
// Similar to above, nanos can't overflow.
187-
let mut nsec = self.tv_nsec.as_inner() as i32 - other.subsec_nanos() as i32;
188-
if nsec < 0 {
189-
nsec += NSEC_PER_SEC as i32;
190-
secs = secs.checked_sub(1)?;
191-
}
192-
Some(unsafe { Timespec::new_unchecked(secs, nsec.into()) })
193-
}
194-
195-
#[allow(dead_code)]
196-
pub fn to_timespec(&self) -> Option<libc::timespec> {
197-
Some(libc::timespec {
198-
tv_sec: self.tv_sec.try_into().ok()?,
199-
tv_nsec: self.tv_nsec.as_inner().try_into().ok()?,
200-
})
201-
}
202-
203-
// On QNX Neutrino, the maximum timespec for e.g. pthread_cond_timedwait
204-
// is 2^64 nanoseconds
205-
#[cfg(target_os = "nto")]
206-
pub(in crate::sys) fn to_timespec_capped(&self) -> Option<libc::timespec> {
207-
// Check if timeout in nanoseconds would fit into an u64
208-
if (self.tv_nsec.as_inner() as u64)
209-
.checked_add((self.tv_sec as u64).checked_mul(NSEC_PER_SEC)?)
210-
.is_none()
211-
{
212-
return None;
213-
}
214-
self.to_timespec()
215-
}
216-
217-
#[cfg(all(
218-
target_os = "linux",
219-
target_env = "gnu",
220-
target_pointer_width = "32",
221-
not(target_arch = "riscv32")
222-
))]
223-
pub fn to_timespec64(&self) -> __timespec64 {
224-
__timespec64::new(self.tv_sec, self.tv_nsec.as_inner() as _)
225-
}
226-
}
227-
22856
#[cfg(all(
22957
target_os = "linux",
23058
target_env = "gnu",

0 commit comments

Comments
 (0)