From e8af1a1812f9d736019241018af9462dc546512e Mon Sep 17 00:00:00 2001 From: spinline Date: Fri, 6 Feb 2026 00:20:51 +0300 Subject: [PATCH] fix: Patch coarsetime for MIPS AtomicU64 support - Vendor coarsetime crate with portable-atomic fallback - Use portable-atomic on targets without 64-bit atomics - Patch crates.io to use local coarsetime - Fixes MIPS build failure (AtomicU64 missing) --- Cargo.lock | 9 +- Cargo.toml | 3 + third_party/coarsetime/.cargo-ok | 1 + third_party/coarsetime/.cargo_vcs_info.json | 6 + third_party/coarsetime/.github/dependabot.yml | 8 + .../coarsetime/.github/workflows/issues.yml | 17 + third_party/coarsetime/.gitignore | 4 + third_party/coarsetime/Cargo.toml | 82 +++++ third_party/coarsetime/Cargo.toml.orig | 47 +++ third_party/coarsetime/LICENSE | 25 ++ third_party/coarsetime/README.md | 90 +++++ third_party/coarsetime/benches/benchmark.rs | 61 ++++ third_party/coarsetime/src/clock.rs | 93 +++++ third_party/coarsetime/src/duration.rs | 270 ++++++++++++++ third_party/coarsetime/src/helpers.rs | 26 ++ third_party/coarsetime/src/instant.rs | 335 ++++++++++++++++++ third_party/coarsetime/src/lib.rs | 37 ++ third_party/coarsetime/src/tests.rs | 55 +++ third_party/coarsetime/src/updater.rs | 58 +++ 19 files changed, 1225 insertions(+), 2 deletions(-) create mode 100644 third_party/coarsetime/.cargo-ok create mode 100644 third_party/coarsetime/.cargo_vcs_info.json create mode 100644 third_party/coarsetime/.github/dependabot.yml create mode 100644 third_party/coarsetime/.github/workflows/issues.yml create mode 100644 third_party/coarsetime/.gitignore create mode 100644 third_party/coarsetime/Cargo.toml create mode 100644 third_party/coarsetime/Cargo.toml.orig create mode 100644 third_party/coarsetime/LICENSE create mode 100644 third_party/coarsetime/README.md create mode 100644 third_party/coarsetime/benches/benchmark.rs create mode 100644 third_party/coarsetime/src/clock.rs create mode 100644 third_party/coarsetime/src/duration.rs create mode 100644 third_party/coarsetime/src/helpers.rs create mode 100644 third_party/coarsetime/src/instant.rs create mode 100644 third_party/coarsetime/src/lib.rs create mode 100644 third_party/coarsetime/src/tests.rs create mode 100644 third_party/coarsetime/src/updater.rs diff --git a/Cargo.lock b/Cargo.lock index ec665d1..e205802 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,10 +477,9 @@ checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "coarsetime" version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58eb270476aa4fc7843849f8a35063e8743b4dbcdf6dd0f8ea0886980c204c2" dependencies = [ "libc", + "portable-atomic", "wasix", "wasm-bindgen", ] @@ -2278,6 +2277,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "potential_utf" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index a99ed9c..b27a4d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,6 @@ lto = true codegen-units = 1 panic = "abort" strip = true + +[patch.crates-io] +coarsetime = { path = "third_party/coarsetime" } diff --git a/third_party/coarsetime/.cargo-ok b/third_party/coarsetime/.cargo-ok new file mode 100644 index 0000000..5f8b795 --- /dev/null +++ b/third_party/coarsetime/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/third_party/coarsetime/.cargo_vcs_info.json b/third_party/coarsetime/.cargo_vcs_info.json new file mode 100644 index 0000000..06bc380 --- /dev/null +++ b/third_party/coarsetime/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "831c97016aa3d8f7851999aa1deea8407e7cbd42" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/third_party/coarsetime/.github/dependabot.yml b/third_party/coarsetime/.github/dependabot.yml new file mode 100644 index 0000000..c11601f --- /dev/null +++ b/third_party/coarsetime/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + time: "04:00" + open-pull-requests-limit: 10 diff --git a/third_party/coarsetime/.github/workflows/issues.yml b/third_party/coarsetime/.github/workflows/issues.yml new file mode 100644 index 0000000..c5bf530 --- /dev/null +++ b/third_party/coarsetime/.github/workflows/issues.yml @@ -0,0 +1,17 @@ +name: Close inactive issues +on: + schedule: + - cron: "30 1 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/third_party/coarsetime/.gitignore b/third_party/coarsetime/.gitignore new file mode 100644 index 0000000..59dfc71 --- /dev/null +++ b/third_party/coarsetime/.gitignore @@ -0,0 +1,4 @@ +target +Cargo.lock +.vscode +zig-cache diff --git a/third_party/coarsetime/Cargo.toml b/third_party/coarsetime/Cargo.toml new file mode 100644 index 0000000..ca9033f --- /dev/null +++ b/third_party/coarsetime/Cargo.toml @@ -0,0 +1,82 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "coarsetime" +version = "0.1.37" +authors = ["Frank Denis "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Time and duration crate optimized for speed" +homepage = "https://github.com/jedisct1/rust-coarsetime" +readme = "README.md" +keywords = [ + "time", + "date", + "duration", +] +categories = [ + "concurrency", + "date-and-time", + "os", +] +license = "BSD-2-Clause" +repository = "https://github.com/jedisct1/rust-coarsetime" + +[features] +wasi-abi2 = ["dep:wasi-abi2"] + +[lib] +name = "coarsetime" +path = "src/lib.rs" + +[[bench]] +name = "benchmark" +path = "benches/benchmark.rs" +harness = false + +[dev-dependencies.benchmark-simple] +version = "0.1.10" + +[dependencies.portable-atomic] +version = "1.6" + +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies.wasm-bindgen] +version = "0.2" + +[target.'cfg(any(target_os = "wasix", target_os = "wasi"))'.dependencies.wasix] +version = "0.13" + +[target.'cfg(not(any(target_os = "wasix", target_os = "wasi")))'.dependencies.libc] +version = "0.2" + +[target.'cfg(target_os = "wasi")'.dependencies.wasi-abi2] +version = "0.14.7" +optional = true +package = "wasi" + +[profile.bench] +codegen-units = 1 + +[profile.dev] +overflow-checks = true + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 +panic = "abort" +incremental = false diff --git a/third_party/coarsetime/Cargo.toml.orig b/third_party/coarsetime/Cargo.toml.orig new file mode 100644 index 0000000..a61e37a --- /dev/null +++ b/third_party/coarsetime/Cargo.toml.orig @@ -0,0 +1,47 @@ +[package] +name = "coarsetime" +version = "0.1.37" +description = "Time and duration crate optimized for speed" +authors = ["Frank Denis "] +keywords = ["time", "date", "duration"] +readme = "README.md" +license = "BSD-2-Clause" +homepage = "https://github.com/jedisct1/rust-coarsetime" +repository = "https://github.com/jedisct1/rust-coarsetime" +categories = ["concurrency", "date-and-time", "os"] +edition = "2018" + +[features] +wasi-abi2 = ["dep:wasi-abi2"] + +[target.'cfg(not(any(target_os = "wasix", target_os = "wasi")))'.dependencies] +libc = "0.2" + +[target.'cfg(target_os = "wasi")'.dependencies] +wasi-abi2 = { package = "wasi", version = "0.14.7", optional = true } + +[target.'cfg(any(target_os = "wasix", target_os = "wasi"))'.dependencies] +wasix = "0.13" + +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] +wasm-bindgen = "0.2" + +[dev-dependencies] +benchmark-simple = "0.1.10" + +[profile.bench] +codegen-units = 1 + +[[bench]] +name = "benchmark" +harness = false + +[profile.release] +lto = true +panic = "abort" +opt-level = 3 +codegen-units = 1 +incremental = false + +[profile.dev] +overflow-checks=true diff --git a/third_party/coarsetime/LICENSE b/third_party/coarsetime/LICENSE new file mode 100644 index 0000000..8c950b9 --- /dev/null +++ b/third_party/coarsetime/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2016-2026, Frank Denis +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/coarsetime/README.md b/third_party/coarsetime/README.md new file mode 100644 index 0000000..19d7259 --- /dev/null +++ b/third_party/coarsetime/README.md @@ -0,0 +1,90 @@ +[![Documentation](https://docs.rs/coarsetime/badge.svg)](https://docs.rs/coarsetime) +[![Windows build status](https://ci.appveyor.com/api/projects/status/xlbhk9850dvl5ylh?svg=true)](https://ci.appveyor.com/project/jedisct1/rust-coarsetime) +# coarsetime + +A Rust crate to make time measurements, that focuses on speed, API stability and portability. + +This crate is a partial replacement for the `Time` and `Duration` structures +from the standard library, with the following differences: + +* Speed is privileged over accuracy. In particular, `CLOCK_MONOTONIC_COARSE` is +used to retrieve the clock value on Linux systems, and transformations avoid +operations that can be slow on non-Intel systems. +* The number of system calls can be kept to a minimum. The "most recent +timestamp" is always kept in memory. It can be read with just a load operation, +and can be updated only as frequently as necessary. +* The API is stable, and the same for all platforms. Unlike the standard library, it doesn't silently compile functions that do nothing but panic at runtime on some platforms. + +# Installation + +`coarsetime` is available on [crates.io](https://crates.io/crates/coarsetime) +and works on Rust stable, beta, and nightly. + +Windows and Unix-like systems are supported. + +Available feature: + +* `wasi-abi2`: when targeting WASI, use the second preview of the ABI. Default is to use the regular WASI-core ABI. + +# Documentation + +[API documentation](https://docs.rs/coarsetime) + +# Example + +```rust +extern crate coarsetime; + +use coarsetime::{Duration, Instant, Updater}; + +// Get the current instant. This may require a system call, but it may also +// be faster than the stdlib equivalent. +let now = Instant::now(); + +// Get the latest known instant. This operation is super fast. +// In this case, the value will be identical to `now`, because we haven't +// updated the latest known instant yet. +let ts1 = Instant::recent(); + +// Update the latest known instant. This may require a system call. +// Note that a call to `Instant::now()` also updates the stored instant. +Instant::update(); + +// Now, we may get a different instant. This call is also super fast. +let ts2 = Instant::recent(); + +// Compute the time elapsed between ts2 and ts1. +let elapsed_ts2_ts1 = ts2.duration_since(ts1); + +// Operations such as `+` and `-` between `Instant` and `Duration` are also +// available. +let elapsed_ts2_ts1 = ts2 - ts1; + +// Returns the time elapsed since ts1. +// This retrieves the actual current time, and may require a system call. +let elapsed_since_ts1 = ts1.elapsed(); + +// Returns the approximate time elapsed since ts1. +// This uses the latest known instant, and is super fast. +let elapsed_since_recent = ts1.elapsed_since_recent(); + +// Instant::update() should be called periodically, for example using an +// event loop. Alternatively, the crate provides an easy way to spawn a +// background task that will periodically update the latest known instant. +// Here, the update will happen every 250ms. +let updater = Updater::new(250).start().unwrap(); + +// From now on, Instant::recent() will always return an approximation of the +// current instant. +let ts3 = Instant::recent(); + +// Stop the task. +updater.stop().unwrap(); + +// Returns the elapsed time since the UNIX epoch +let unix_timestamp = Clock::now_since_epoch(); + +// Returns an approximation of the elapsed time since the UNIX epoch, based on +// the latest time update +let unix_timestamp_approx = Clock::recent_since_epoch(); +``` diff --git a/third_party/coarsetime/benches/benchmark.rs b/third_party/coarsetime/benches/benchmark.rs new file mode 100644 index 0000000..f623cfa --- /dev/null +++ b/third_party/coarsetime/benches/benchmark.rs @@ -0,0 +1,61 @@ +use benchmark_simple::*; +use coarsetime::*; +use std::time; + +fn main() { + let options = &Options { + iterations: 250_000, + warmup_iterations: 25_000, + min_samples: 5, + max_samples: 10, + max_rsd: 1.0, + ..Default::default() + }; + bench_coarsetime_now(options); + bench_coarsetime_recent(options); + bench_coarsetime_elapsed(options); + bench_coarsetime_elapsed_since_recent(options); + bench_stdlib_now(options); + bench_stdlib_elapsed(options); +} + +fn bench_coarsetime_now(options: &Options) { + let b = Bench::new(); + Instant::update(); + let res = b.run(options, Instant::now); + println!("coarsetime_now(): {}", res.throughput(1)); +} + +fn bench_coarsetime_recent(options: &Options) { + let b = Bench::new(); + Instant::update(); + let res = b.run(options, Instant::recent); + println!("coarsetime_recent(): {}", res.throughput(1)); +} + +fn bench_coarsetime_elapsed(options: &Options) { + let b = Bench::new(); + let ts = Instant::now(); + let res = b.run(options, || ts.elapsed()); + println!("coarsetime_elapsed(): {}", res.throughput(1)); +} + +fn bench_coarsetime_elapsed_since_recent(options: &Options) { + let b = Bench::new(); + let ts = Instant::now(); + let res = b.run(options, || ts.elapsed_since_recent()); + println!("coarsetime_since_recent(): {}", res.throughput(1)); +} + +fn bench_stdlib_now(options: &Options) { + let b = Bench::new(); + let res = b.run(options, time::Instant::now); + println!("stdlib_now(): {}", res.throughput(1)); +} + +fn bench_stdlib_elapsed(options: &Options) { + let b = Bench::new(); + let ts = time::Instant::now(); + let res = b.run(options, || ts.elapsed()); + println!("stdlib_elapsed(): {}", res.throughput(1)); +} diff --git a/third_party/coarsetime/src/clock.rs b/third_party/coarsetime/src/clock.rs new file mode 100644 index 0000000..ecb43b0 --- /dev/null +++ b/third_party/coarsetime/src/clock.rs @@ -0,0 +1,93 @@ +#[cfg(not(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown" +)))] +use std::time; + +#[cfg(target_has_atomic = "64")] +use std::sync::atomic::{AtomicU64, Ordering}; +#[cfg(not(target_has_atomic = "64"))] +use portable_atomic::{AtomicU64, Ordering}; + +use super::Duration; + +static RECENT: AtomicU64 = AtomicU64::new(0); + +#[cfg(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown" +))] +mod js_imports { + use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + extern "C" { + pub type Date; + + #[wasm_bindgen(static_method_of = Date)] + pub fn now() -> f64; + } +} + +/// System time +#[derive(Debug)] +pub struct Clock; + +/// Alias for `Duration`. +pub type UnixTimeStamp = Duration; + +impl Clock { + /// Returns the elapsed time since the UNIX epoch + #[inline] + pub fn now_since_epoch() -> UnixTimeStamp { + Duration::from_u64(unix_ts()) + } + + /// Returns the elapsed time since the UNIX epoch, based on the latest + /// explicit time update + #[inline] + pub fn recent_since_epoch() -> UnixTimeStamp { + Duration::from_u64(RECENT.load(Ordering::Relaxed)) + } + + /// Updates the cached system time. + /// + /// This function should be called frequently, for example in an event loop + /// or using an `Updater` task. + #[inline] + pub fn update() { + let now = unix_ts(); + RECENT.store(now, Ordering::Relaxed) + } + + /// Sets the cached system time to the specified timestamp. + /// This function is intended for testing purposes only. + /// It should not be used in production code. + pub fn set_recent_since_epoch(recent: UnixTimeStamp) { + RECENT.store(recent.as_u64(), Ordering::Relaxed) + } +} + +#[cfg(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown" +))] +#[inline] +fn unix_ts() -> u64 { + let unix_ts_now_sys = (js_imports::Date::now() / 1000.0).round() as u64; + let unix_ts_now = Duration::from_secs(unix_ts_now_sys); + unix_ts_now.as_u64() +} + +#[cfg(not(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown" +)))] +#[inline] +fn unix_ts() -> u64 { + let unix_ts_now_sys = time::SystemTime::now() + .duration_since(time::UNIX_EPOCH) + .expect("The system clock is not properly set"); + let unix_ts_now = Duration::from(unix_ts_now_sys); + unix_ts_now.as_u64() +} diff --git a/third_party/coarsetime/src/duration.rs b/third_party/coarsetime/src/duration.rs new file mode 100644 index 0000000..b9cf64e --- /dev/null +++ b/third_party/coarsetime/src/duration.rs @@ -0,0 +1,270 @@ +use std::convert::From; +use std::ops::*; +use std::time; + +use super::helpers::*; + +/// A duration type to represent an approximate span of time +#[derive(Copy, Clone, Debug, Hash, Ord, Eq, PartialOrd, PartialEq, Default)] +pub struct Duration(u64); + +impl Duration { + /// Creates a new `Duration` from the specified number of seconds and + /// additional nanosecond precision + #[inline] + pub const fn new(sec: u64, nanos: u32) -> Duration { + Duration(_timespec_to_u64(sec, nanos)) + } + + /// Creates a new Duration from the specified number of days + #[inline] + pub const fn from_days(days: u64) -> Duration { + Duration(_sec_to_u64(days * 86400)) + } + + /// Creates a new Duration from the specified number of hours + #[inline] + pub const fn from_hours(hours: u64) -> Duration { + Duration(_sec_to_u64(hours * 3600)) + } + + /// Creates a new Duration from the specified number of minutes + #[inline] + pub const fn from_mins(mins: u64) -> Duration { + Duration(_sec_to_u64(mins * 60)) + } + + /// Creates a new Duration from the specified number of seconds + #[inline] + pub const fn from_secs(secs: u64) -> Duration { + Duration(_sec_to_u64(secs)) + } + + /// Creates a new Duration from the specified number of milliseconds + #[inline] + pub const fn from_millis(millis: u64) -> Duration { + Duration(_millis_to_u64(millis)) + } + + /// Returns the number of days represented by this duration + #[inline] + pub const fn as_days(&self) -> u64 { + self.as_secs() / 86400 + } + + /// Returns the number of hours represented by this duration + #[inline] + pub const fn as_hours(&self) -> u64 { + self.as_secs() / 3600 + } + + /// Returns the number of minutes represented by this duration + #[inline] + pub const fn as_mins(&self) -> u64 { + self.as_secs() / 60 + } + + /// Returns the number of whole seconds represented by this duration + #[inline] + pub const fn as_secs(&self) -> u64 { + self.0 >> 32 + } + + /// Returns the number of whole milliseconds represented by this duration + #[inline] + pub const fn as_millis(&self) -> u64 { + ((self.0 as u128 * 125) >> 29) as u64 + } + + /// Returns the number of whole microseconds represented by this duration + #[inline] + pub const fn as_micros(&self) -> u64 { + ((self.0 as u128 * 125_000) >> 29) as u64 + } + + /// Returns the number of whole nanoseconds represented by this duration + #[inline] + pub const fn as_nanos(&self) -> u64 { + ((self.0 as u128 * 125_000_000) >> 29) as u64 + } + + /// Returns the nanosecond precision represented by this duration + #[inline] + pub const fn subsec_nanos(&self) -> u32 { + ((self.0 as u32 as u64 * 125_000_000) >> 29) as u32 + } + + /// Return this duration as a number of "ticks". + /// + /// Note that length of a 'tick' is not guaranteed to represent + /// the same amount of time across different platforms, or from + /// one version of `coarsetime` to another. + #[inline] + pub const fn as_ticks(&self) -> u64 { + self.as_u64() + } + + /// Creates a new Duration from the specified number of "ticks". + /// + /// Note that length of a 'tick' is not guaranteed to represent + /// the same amount of time across different platforms, or from + /// one version of `coarsetime` to another. + #[inline] + pub const fn from_ticks(ticks: u64) -> Duration { + Self::from_u64(ticks) + } + + #[doc(hidden)] + #[inline] + pub const fn as_u64(&self) -> u64 { + self.0 + } + + #[doc(hidden)] + #[inline] + pub const fn from_u64(ts: u64) -> Duration { + Duration(ts) + } + + /// Returns the duration as a floating point number, representing the number + /// of seconds + #[inline] + pub fn as_f64(&self) -> f64 { + (self.0 as f64) / ((1u64 << 32) as f64) + } + + /// Returns the absolute difference between two `Duration`s + #[inline] + pub const fn abs_diff(&self, other: Duration) -> Duration { + Duration(self.0.abs_diff(other.0)) + } + + /// Add two durations, saturating on overflow + #[inline] + pub const fn saturating_add(self, rhs: Duration) -> Duration { + Duration(self.0.saturating_add(rhs.0)) + } + + /// Add two durations, returning `None` on overflow + #[inline] + pub fn checked_add(self, rhs: Duration) -> Option { + self.0.checked_add(rhs.0).map(Duration) + } + + /// Subtract two durations, saturating on underflow/overflow + #[inline] + pub const fn saturating_sub(self, rhs: Duration) -> Duration { + Duration(self.0.saturating_sub(rhs.0)) + } + + /// Subtract two durations, returning `None` on underflow/overflow + #[inline] + pub fn checked_sub(self, rhs: Duration) -> Option { + self.0.checked_sub(rhs.0).map(Duration) + } + + /// Multiply a duration by a scalar, saturating on overflow + #[inline] + pub const fn saturating_mul(self, rhs: u32) -> Duration { + Duration(self.0.saturating_mul(rhs as u64)) + } + + /// Multiply a duration by a scalar, returning `None` on overflow + #[inline] + pub fn checked_mul(self, rhs: u32) -> Option { + self.0.checked_mul(rhs as u64).map(Duration) + } + + /// Divide a duration by a scalar, returning `None` for division by zero + #[inline] + pub fn checked_div(self, rhs: u32) -> Option { + self.0.checked_div(rhs as u64).map(Duration) + } +} + +#[doc(hidden)] +impl From for Duration { + #[doc(hidden)] + #[inline] + fn from(ts: u64) -> Duration { + Duration::from_u64(ts) + } +} + +impl Add for Duration { + type Output = Duration; + + #[inline] + fn add(self, rhs: Duration) -> Duration { + Duration(self.0 + rhs.0) + } +} + +impl AddAssign for Duration { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + *self = *self + rhs; + } +} + +impl Sub for Duration { + type Output = Duration; + + #[inline] + fn sub(self, rhs: Duration) -> Duration { + Duration(self.0 - rhs.0) + } +} + +impl SubAssign for Duration { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + *self = *self - rhs; + } +} + +impl Mul for Duration { + type Output = Duration; + + #[inline] + fn mul(self, rhs: u32) -> Duration { + Duration(self.0 * rhs as u64) + } +} + +impl MulAssign for Duration { + #[inline] + fn mul_assign(&mut self, rhs: u32) { + *self = *self * rhs; + } +} + +impl Div for Duration { + type Output = Duration; + + #[inline] + fn div(self, rhs: u32) -> Duration { + Duration(self.0 / rhs as u64) + } +} + +impl DivAssign for Duration { + #[inline] + fn div_assign(&mut self, rhs: u32) { + *self = *self / rhs; + } +} + +impl From for time::Duration { + #[inline] + fn from(duration: Duration) -> time::Duration { + time::Duration::new(duration.as_secs(), duration.subsec_nanos()) + } +} + +impl From for Duration { + #[inline] + fn from(duration_sys: time::Duration) -> Duration { + Duration::new(duration_sys.as_secs(), duration_sys.subsec_nanos()) + } +} diff --git a/third_party/coarsetime/src/helpers.rs b/third_party/coarsetime/src/helpers.rs new file mode 100644 index 0000000..d56797b --- /dev/null +++ b/third_party/coarsetime/src/helpers.rs @@ -0,0 +1,26 @@ +#[inline] +pub const fn _sec_to_u64(sec: u64) -> u64 { + sec.saturating_mul(1 << 32) +} + +#[inline] +pub const fn _millis_to_u64(millis: u64) -> u64 { + let secs = millis / 1_000; + secs.saturating_mul(1 << 32) | ((millis - secs * 1_000) << 22) +} + +#[inline] +pub const fn _nsecs_to_u64(nsecs: u64) -> u64 { + let secs = nsecs / 1_000_000_000; + _timespec_to_u64(secs, (nsecs - secs * 1_000_000_000) as u32) +} + +#[inline] +pub const fn _timespec_to_u64(tp_sec: u64, tp_nsec: u32) -> u64 { + tp_sec.saturating_mul(1 << 32) | ((tp_nsec as u64 * 9_223_372_037) >> 31) +} + +#[inline] +pub const fn _timeval_to_u64(tv_sec: u64, tv_usec: u32) -> u64 { + tv_sec.saturating_mul(1 << 32) | ((tv_usec as u64 * 9_223_372_036_855) >> 31) +} diff --git a/third_party/coarsetime/src/instant.rs b/third_party/coarsetime/src/instant.rs new file mode 100644 index 0000000..a2aca40 --- /dev/null +++ b/third_party/coarsetime/src/instant.rs @@ -0,0 +1,335 @@ +#[allow(unused_imports)] +use std::mem::MaybeUninit; +use std::ops::*; +#[allow(unused_imports)] +use std::ptr::*; +#[cfg(target_has_atomic = "64")] +use std::sync::atomic::{AtomicU64, Ordering}; +#[cfg(not(target_has_atomic = "64"))] +use portable_atomic::{AtomicU64, Ordering}; + +use super::duration::*; +#[allow(unused_imports)] +use super::helpers::*; + +/// A measurement of a *monotonically* increasing clock. +/// Opaque and useful only with `Duration`. +/// +/// Resulting durations are actual durations; they do not get affected by +/// clock adjustments, leap seconds, or similar. +/// In order to get a measurement of the *wall clock*, use `Date` instead. +#[derive(Copy, Clone, Debug, Hash, Ord, Eq, PartialOrd, PartialEq)] +pub struct Instant(u64); + +static RECENT: AtomicU64 = AtomicU64::new(0); + +#[cfg(windows)] +extern "system" { + pub fn GetTickCount64() -> libc::c_ulonglong; +} + +#[cfg(any(target_os = "macos", target_os = "freebsd"))] +#[allow(non_camel_case_types)] +type clockid_t = libc::c_int; + +#[cfg(target_os = "macos")] +const CLOCK_MONOTONIC_RAW_APPROX: clockid_t = 5; + +#[cfg(target_os = "macos")] +extern "system" { + fn clock_gettime_nsec_np(clk_id: clockid_t) -> u64; +} + +#[cfg(target_os = "freebsd")] +const CLOCK_MONOTONIC_FAST: clockid_t = 12; + +#[cfg(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown" +))] +mod js_imports { + use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + extern "C" { + #[allow(non_camel_case_types)] + pub type performance; + + #[wasm_bindgen(static_method_of = performance)] + pub fn now() -> f64; + } +} + +impl Instant { + /// Returns an instant corresponding to "now" + /// + /// This function also updates the stored instant. + pub fn now() -> Instant { + let now = Self::_now(); + Self::_update(now); + Instant(now) + } + + /// Returns an instant corresponding to "now" without updating the cached value. + /// After this, `recent()` will still return the old instant. + /// + /// `now()` is generally preferred over this function. + pub fn now_without_cache_update() -> Instant { + let now = Self::_now(); + Instant(now) + } + + /// Returns an instant corresponding to the latest update + pub fn recent() -> Instant { + match Self::_recent() { + 0 => Instant::now(), + recent => Instant(recent), + } + } + + /// Update the stored instant + /// + /// This function should be called frequently, for example in an event loop + /// or using an `Updater` task. + pub fn update() { + let now = Self::_now(); + Self::_update(now); + } + + /// Returns the amount of time elapsed from another instant to this one + #[inline] + pub fn duration_since(&self, earlier: Instant) -> Duration { + *self - earlier + } + + /// Returns the amount of time elapsed between the this instant was created + /// and the latest update + #[inline] + pub fn elapsed_since_recent(&self) -> Duration { + Self::recent() - *self + } + + /// Returns the amount of time elapsed since this instant was created + /// + /// This function also updates the stored instant. + #[inline] + pub fn elapsed(&self) -> Duration { + Self::now() - *self + } + + /// Return a representation of this instant as a number of "ticks". + /// + /// Note that length of a 'tick' is not guaranteed to represent + /// the same amount of time across different platforms, or from + /// one version of `coarsetime` to another. + /// + /// Note also that the instant represented by "0" ticks is + /// unspecified. It is not guaranteed to be the same time across + /// different platforms, or from one version of `coarsetime` to + /// another. + /// + /// This API is mainly intended for applications that need to + /// store the value of an `Instant` in an + /// [`AtomicU64`](std::sync::atomic::AtomicU64). + #[inline] + pub const fn as_ticks(&self) -> u64 { + self.as_u64() + } + + /// Create an `Instant` from a number of "ticks". + /// + /// Note that length of a 'tick' is not guaranteed to represent + /// the same amount of time across different platforms, or from + /// one version of `coarsetime` to another. + /// + /// Note also that the instant represented by "0" ticks is + /// unspecified. It is not guaranteed to be the same time across + /// different platforms, or from one version of `coarsetime` to + /// another. + #[inline] + pub const fn from_ticks(ticks: u64) -> Instant { + Self::from_u64(ticks) + } + + #[doc(hidden)] + #[inline] + pub const fn as_u64(&self) -> u64 { + self.0 + } + + #[doc(hidden)] + #[inline] + pub const fn from_u64(ts: u64) -> Instant { + Instant(ts) + } + + /// Calculate an `Instant` that is a `Duration` later, saturating on overflow + #[inline] + pub const fn saturating_add(self, rhs: Duration) -> Instant { + Instant(self.0.saturating_add(rhs.as_u64())) + } + + /// Calculate an `Instant` that is a `Duration` later, returning `None` on overflow + #[inline] + pub fn checked_add(self, rhs: Duration) -> Option { + self.0.checked_add(rhs.as_u64()).map(Instant) + } + + /// Calculate an `Instant` that is a `Duration` earlier, saturating on underflow + #[inline] + pub const fn saturating_sub(self, rhs: Duration) -> Instant { + Instant(self.0.saturating_sub(rhs.as_u64())) + } + + /// Calculate an `Instant` that is a `Duration` earlier, returning `None` on underflow + #[inline] + pub fn checked_sub(self, rhs: Duration) -> Option { + self.0.checked_sub(rhs.as_u64()).map(Instant) + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + fn _now() -> u64 { + let mut tp = MaybeUninit::::uninit(); + let tp = unsafe { + libc::clock_gettime(libc::CLOCK_MONOTONIC_COARSE, tp.as_mut_ptr()); + tp.assume_init() + }; + _timespec_to_u64(tp.tv_sec as u64, tp.tv_nsec as u32) + } + + #[cfg(target_os = "macos")] + fn _now() -> u64 { + let nsec = unsafe { clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW_APPROX) }; + _nsecs_to_u64(nsec) + } + + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + fn _now() -> u64 { + let mut tp = MaybeUninit::::uninit(); + let tp = unsafe { + libc::clock_gettime(libc::CLOCK_MONOTONIC_FAST, tp.as_mut_ptr()); + tp.assume_init() + }; + _timespec_to_u64(tp.tv_sec as u64, tp.tv_nsec as u32) + } + + #[cfg(all( + unix, + not(any( + target_os = "macos", + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "dragonfly" + )) + ))] + fn _now() -> u64 { + let mut tv = MaybeUninit::::uninit(); + let tv = unsafe { + libc::gettimeofday(tv.as_mut_ptr(), null_mut()); + tv.assume_init() + }; + _timeval_to_u64(tv.tv_sec as u64, tv.tv_usec as u32) + } + + #[cfg(windows)] + fn _now() -> u64 { + let tc = unsafe { GetTickCount64() } as u64; + _millis_to_u64(tc) + } + + #[cfg(all(target_os = "wasi", not(feature = "wasi-abi2")))] + fn _now() -> u64 { + use wasix::{clock_time_get, CLOCKID_MONOTONIC, CLOCKID_REALTIME}; + let nsec = unsafe { clock_time_get(CLOCKID_MONOTONIC, 1_000_000) } + .or_else(|_| unsafe { clock_time_get(CLOCKID_REALTIME, 1_000_000) }) + .expect("Clock not available"); + _nsecs_to_u64(nsec) + } + + #[cfg(all(target_os = "wasi", feature = "wasi-abi2"))] + fn _now() -> u64 { + let nsec = wasi_abi2::clocks::monotonic_clock::now(); + _nsecs_to_u64(nsec) + } + + #[cfg(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown" + ))] + fn _now() -> u64 { + _millis_to_u64(js_imports::performance::now() as u64) + } + + #[cfg(all(target_arch = "x86_64", target_env = "sgx", target_vendor = "fortanix"))] + fn _now() -> u64 { + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap(); + timestamp.as_secs() * 1_000_000_000 + (timestamp.subsec_nanos() as u64) + } + + #[inline] + fn _update(now: u64) { + RECENT.store(now, Ordering::Relaxed) + } + + #[inline] + fn _recent() -> u64 { + let recent = RECENT.load(Ordering::Relaxed); + if recent != 0 { + recent + } else { + let now = Self::_now(); + Self::_update(now); + Self::_recent() + } + } +} + +impl Default for Instant { + fn default() -> Instant { + Self::now() + } +} + +impl Sub for Instant { + type Output = Duration; + + #[inline] + fn sub(self, other: Instant) -> Duration { + Duration::from_u64(self.0.saturating_sub(other.0)) + } +} + +impl Sub for Instant { + type Output = Instant; + + #[inline] + fn sub(self, rhs: Duration) -> Instant { + Instant(self.0 - rhs.as_u64()) + } +} + +impl SubAssign for Instant { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + *self = *self - rhs; + } +} + +impl Add for Instant { + type Output = Instant; + + #[inline] + fn add(self, rhs: Duration) -> Instant { + Instant(self.0 + rhs.as_u64()) + } +} + +impl AddAssign for Instant { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + *self = *self + rhs; + } +} diff --git a/third_party/coarsetime/src/lib.rs b/third_party/coarsetime/src/lib.rs new file mode 100644 index 0000000..aed8c12 --- /dev/null +++ b/third_party/coarsetime/src/lib.rs @@ -0,0 +1,37 @@ +//! A crate to make time measurements that focuses on speed. +//! +//! This crate is a partial replacement for the `Time` and `Duration` structures +//! from the standard library, with the following differences: +//! +//! * Speed is privileged over accuracy. In particular, `CLOCK_MONOTONIC_COARSE` +//! is used to retrieve the clock value on Linux systems, and transformations avoid +//! operations that can be slow on non-Intel systems. +//! * The number of system calls can be kept to a minimum. The "most recent +//! timestamp" is always kept in memory. +//! It can be read with just a load operation, and can be +//! updated only as frequently as necessary. +//! +//! # Installation +//! +//! `coarsetime` is available on [crates.io](https://crates.io/crates/coarsetime) and works on +//! Rust stable, beta, and nightly. +//! +//! Windows and Unix-like systems are supported. + +#![allow(clippy::trivially_copy_pass_by_ref)] + +mod clock; +mod duration; +mod helpers; +mod instant; +#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +mod updater; + +#[cfg(test)] +mod tests; + +pub use self::clock::*; +pub use self::duration::*; +pub use self::instant::*; +#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +pub use self::updater::*; diff --git a/third_party/coarsetime/src/tests.rs b/third_party/coarsetime/src/tests.rs new file mode 100644 index 0000000..4b69527 --- /dev/null +++ b/third_party/coarsetime/src/tests.rs @@ -0,0 +1,55 @@ +use std::thread::sleep; +use std::time; + +#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +use super::Updater; +use super::{Clock, Duration, Instant}; + +#[test] +fn tests() { + let ts = Instant::now(); + let d = Duration::from_secs(2); + sleep(time::Duration::new(3, 0)); + let elapsed = ts.elapsed().as_secs(); + println!("Elapsed: {elapsed} secs"); + assert!(elapsed >= 2); + assert!(elapsed < 100); + assert!(ts.elapsed_since_recent() > d); + + let ts = Instant::now(); + sleep(time::Duration::new(1, 0)); + assert_eq!(Instant::recent(), ts); + Instant::update(); + assert!(Instant::recent() > ts); + + let clock_now = Clock::recent_since_epoch(); + sleep(time::Duration::new(1, 0)); + assert_eq!(Clock::recent_since_epoch(), clock_now); + assert!(Clock::now_since_epoch() > clock_now); + + #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + tests_updater(); +} + +#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] +#[test] +fn tests_updater() { + let updater = Updater::new(250) + .start() + .expect("Unable to start a background updater"); + let ts = Instant::recent(); + let clock_recent = Clock::recent_since_epoch(); + sleep(time::Duration::new(2, 0)); + assert!(Clock::recent_since_epoch() > clock_recent); + assert!(Instant::recent() != ts); + updater.stop().unwrap(); + let clock_recent = Clock::recent_since_epoch(); + sleep(time::Duration::new(1, 0)); + assert_eq!(Clock::recent_since_epoch(), clock_recent); +} + +#[test] +fn tests_duration() { + let duration = Duration::from_days(1000); + assert_eq!(duration.as_days(), 1000); +} diff --git a/third_party/coarsetime/src/updater.rs b/third_party/coarsetime/src/updater.rs new file mode 100644 index 0000000..f9f4530 --- /dev/null +++ b/third_party/coarsetime/src/updater.rs @@ -0,0 +1,58 @@ +use std::io; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time; + +use super::clock::*; +use super::instant::*; + +/// A service to periodically call `Instant::update()` +#[derive(Debug)] +pub struct Updater { + period: time::Duration, + running: Arc, + th: Option>, +} + +impl Updater { + /// Spawns a background task to call `Instant::update()` periodically + pub fn start(mut self) -> Result { + let period = self.period; + let running = self.running.clone(); + running.store(true, Ordering::Relaxed); + let th: thread::JoinHandle<()> = thread::Builder::new() + .name("coarsetime".to_string()) + .spawn(move || { + while running.load(Ordering::Relaxed) { + thread::sleep(period); + Instant::update(); + Clock::update(); + } + })?; + self.th = Some(th); + Instant::update(); + Clock::update(); + Ok(self) + } + + /// Stops the periodic updates + pub fn stop(mut self) -> Result<(), io::Error> { + self.running.store(false, Ordering::Relaxed); + self.th + .take() + .expect("updater is not running") + .join() + .map_err(|_| io::Error::other("failed to properly stop the updater")) + } + + /// Creates a new `Updater` with the specified update period, in + /// milliseconds. + pub fn new(period_millis: u64) -> Updater { + Updater { + period: time::Duration::from_millis(period_millis), + running: Arc::new(AtomicBool::new(false)), + th: None, + } + } +}