From cbd2a40ae4f60e1e5081614b03e7e8825a0fe909 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 02:44:36 +0200 Subject: [PATCH 1/9] Move wasip2 impl into src/sys/p2 (pure relocation) This is a pure `git mv` relocation with no content changes, so git records clean renames and `git blame`/`--follow` lineage is preserved. The build is intentionally red after this commit; backend wiring and crate-root re-export shims are added in the following commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/{ => sys/p2}/http/body.rs | 0 src/{ => sys/p2}/http/client.rs | 0 src/{ => sys/p2}/http/error.rs | 0 src/{ => sys/p2}/http/fields.rs | 0 src/{ => sys/p2}/http/method.rs | 0 src/{ => sys/p2}/http/mod.rs | 0 src/{ => sys/p2}/http/request.rs | 0 src/{ => sys/p2}/http/response.rs | 0 src/{ => sys/p2}/http/scheme.rs | 0 src/{ => sys/p2}/http/server.rs | 0 src/{ => sys/p2}/io/stdio.rs | 0 src/{ => sys/p2}/io/streams.rs | 0 src/{ => sys/p2}/net/mod.rs | 0 src/{ => sys/p2}/net/tcp_listener.rs | 0 src/{ => sys/p2}/net/tcp_stream.rs | 0 src/{ => sys/p2}/rand/mod.rs | 0 src/{ => sys/p2}/runtime/block_on.rs | 0 src/{ => sys/p2}/runtime/mod.rs | 0 src/{ => sys/p2}/runtime/reactor.rs | 0 src/{ => sys/p2}/time/duration.rs | 0 src/{ => sys/p2}/time/instant.rs | 0 src/{ => sys/p2}/time/mod.rs | 0 src/{ => sys/p2}/time/utils.rs | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => sys/p2}/http/body.rs (100%) rename src/{ => sys/p2}/http/client.rs (100%) rename src/{ => sys/p2}/http/error.rs (100%) rename src/{ => sys/p2}/http/fields.rs (100%) rename src/{ => sys/p2}/http/method.rs (100%) rename src/{ => sys/p2}/http/mod.rs (100%) rename src/{ => sys/p2}/http/request.rs (100%) rename src/{ => sys/p2}/http/response.rs (100%) rename src/{ => sys/p2}/http/scheme.rs (100%) rename src/{ => sys/p2}/http/server.rs (100%) rename src/{ => sys/p2}/io/stdio.rs (100%) rename src/{ => sys/p2}/io/streams.rs (100%) rename src/{ => sys/p2}/net/mod.rs (100%) rename src/{ => sys/p2}/net/tcp_listener.rs (100%) rename src/{ => sys/p2}/net/tcp_stream.rs (100%) rename src/{ => sys/p2}/rand/mod.rs (100%) rename src/{ => sys/p2}/runtime/block_on.rs (100%) rename src/{ => sys/p2}/runtime/mod.rs (100%) rename src/{ => sys/p2}/runtime/reactor.rs (100%) rename src/{ => sys/p2}/time/duration.rs (100%) rename src/{ => sys/p2}/time/instant.rs (100%) rename src/{ => sys/p2}/time/mod.rs (100%) rename src/{ => sys/p2}/time/utils.rs (100%) diff --git a/src/http/body.rs b/src/sys/p2/http/body.rs similarity index 100% rename from src/http/body.rs rename to src/sys/p2/http/body.rs diff --git a/src/http/client.rs b/src/sys/p2/http/client.rs similarity index 100% rename from src/http/client.rs rename to src/sys/p2/http/client.rs diff --git a/src/http/error.rs b/src/sys/p2/http/error.rs similarity index 100% rename from src/http/error.rs rename to src/sys/p2/http/error.rs diff --git a/src/http/fields.rs b/src/sys/p2/http/fields.rs similarity index 100% rename from src/http/fields.rs rename to src/sys/p2/http/fields.rs diff --git a/src/http/method.rs b/src/sys/p2/http/method.rs similarity index 100% rename from src/http/method.rs rename to src/sys/p2/http/method.rs diff --git a/src/http/mod.rs b/src/sys/p2/http/mod.rs similarity index 100% rename from src/http/mod.rs rename to src/sys/p2/http/mod.rs diff --git a/src/http/request.rs b/src/sys/p2/http/request.rs similarity index 100% rename from src/http/request.rs rename to src/sys/p2/http/request.rs diff --git a/src/http/response.rs b/src/sys/p2/http/response.rs similarity index 100% rename from src/http/response.rs rename to src/sys/p2/http/response.rs diff --git a/src/http/scheme.rs b/src/sys/p2/http/scheme.rs similarity index 100% rename from src/http/scheme.rs rename to src/sys/p2/http/scheme.rs diff --git a/src/http/server.rs b/src/sys/p2/http/server.rs similarity index 100% rename from src/http/server.rs rename to src/sys/p2/http/server.rs diff --git a/src/io/stdio.rs b/src/sys/p2/io/stdio.rs similarity index 100% rename from src/io/stdio.rs rename to src/sys/p2/io/stdio.rs diff --git a/src/io/streams.rs b/src/sys/p2/io/streams.rs similarity index 100% rename from src/io/streams.rs rename to src/sys/p2/io/streams.rs diff --git a/src/net/mod.rs b/src/sys/p2/net/mod.rs similarity index 100% rename from src/net/mod.rs rename to src/sys/p2/net/mod.rs diff --git a/src/net/tcp_listener.rs b/src/sys/p2/net/tcp_listener.rs similarity index 100% rename from src/net/tcp_listener.rs rename to src/sys/p2/net/tcp_listener.rs diff --git a/src/net/tcp_stream.rs b/src/sys/p2/net/tcp_stream.rs similarity index 100% rename from src/net/tcp_stream.rs rename to src/sys/p2/net/tcp_stream.rs diff --git a/src/rand/mod.rs b/src/sys/p2/rand/mod.rs similarity index 100% rename from src/rand/mod.rs rename to src/sys/p2/rand/mod.rs diff --git a/src/runtime/block_on.rs b/src/sys/p2/runtime/block_on.rs similarity index 100% rename from src/runtime/block_on.rs rename to src/sys/p2/runtime/block_on.rs diff --git a/src/runtime/mod.rs b/src/sys/p2/runtime/mod.rs similarity index 100% rename from src/runtime/mod.rs rename to src/sys/p2/runtime/mod.rs diff --git a/src/runtime/reactor.rs b/src/sys/p2/runtime/reactor.rs similarity index 100% rename from src/runtime/reactor.rs rename to src/sys/p2/runtime/reactor.rs diff --git a/src/time/duration.rs b/src/sys/p2/time/duration.rs similarity index 100% rename from src/time/duration.rs rename to src/sys/p2/time/duration.rs diff --git a/src/time/instant.rs b/src/sys/p2/time/instant.rs similarity index 100% rename from src/time/instant.rs rename to src/sys/p2/time/instant.rs diff --git a/src/time/mod.rs b/src/sys/p2/time/mod.rs similarity index 100% rename from src/time/mod.rs rename to src/sys/p2/time/mod.rs diff --git a/src/time/utils.rs b/src/sys/p2/time/utils.rs similarity index 100% rename from src/time/utils.rs rename to src/sys/p2/time/utils.rs From 19f2f24cc7ab26918d623ff5bacc6a94f3b1d196 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 02:52:02 +0200 Subject: [PATCH 2/9] Wire up the src/sys backend with crate-root re-export shims Restores a green build after the pure relocation. The crate root now selects a platform backend via a single cfg-if in src/sys/mod.rs, and each moved module is re-exported through a thin shim so the public API is preserved exactly: * add `mod sys` plus src/sys/mod.rs (cfg-if) and src/sys/p2/mod.rs * add crate-root shims: http.rs, net.rs, rand.rs, runtime.rs, time.rs * re-export the moved stdio/streams via src/sys/p2/io and io/mod.rs * repoint the few internal `super::`/private-module paths the move broke * add the cfg-if dependency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Cargo.toml | 2 ++ src/http.rs | 3 +++ src/io/mod.rs | 5 +---- src/lib.rs | 3 +++ src/net.rs | 3 +++ src/rand.rs | 3 +++ src/runtime.rs | 10 ++++++++++ src/sys/mod.rs | 17 +++++++++++++++++ src/sys/p2/http/body.rs | 7 ++----- src/sys/p2/http/response.rs | 3 ++- src/sys/p2/io/mod.rs | 5 +++++ src/sys/p2/io/stdio.rs | 2 +- src/sys/p2/io/streams.rs | 4 ++-- src/sys/p2/mod.rs | 8 ++++++++ src/sys/p2/runtime/mod.rs | 2 +- src/time.rs | 3 +++ 16 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 src/http.rs create mode 100644 src/net.rs create mode 100644 src/rand.rs create mode 100644 src/runtime.rs create mode 100644 src/sys/mod.rs create mode 100644 src/sys/p2/io/mod.rs create mode 100644 src/sys/p2/mod.rs create mode 100644 src/time.rs diff --git a/Cargo.toml b/Cargo.toml index 4e248eb..1df71b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ json = ["dep:serde", "dep:serde_json"] anyhow.workspace = true async-task.workspace = true bytes.workspace = true +cfg-if.workspace = true futures-lite.workspace = true http-body-util.workspace = true http-body.workspace = true @@ -71,6 +72,7 @@ anyhow = "1" async-task = "4.7" axum = { version = "0.8.6", default-features = false } bytes = "1.10.1" +cfg-if = "1" cargo_metadata = "0.22" clap = { version = "4.5.26", features = ["derive"] } futures-core = "0.3.19" diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..ed2c4a2 --- /dev/null +++ b/src/http.rs @@ -0,0 +1,3 @@ +//! HTTP networking support +//! +pub use crate::sys::http::*; diff --git a/src/io/mod.rs b/src/io/mod.rs index 0f34b1b..1f360ac 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -5,18 +5,15 @@ mod cursor; mod empty; mod read; mod seek; -mod stdio; -mod streams; mod write; pub use crate::runtime::AsyncPollable; +pub use crate::sys::io::*; pub use copy::*; pub use cursor::*; pub use empty::*; pub use read::*; pub use seek::*; -pub use stdio::*; -pub use streams::*; pub use write::*; /// The error type for I/O operations. diff --git a/src/lib.rs b/src/lib.rs index ebc673d..e4edc55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,9 @@ //! These are unique capabilities provided by WASI 0.2, and because this library //! is specific to that are exposed from here. +#[allow(unreachable_pub)] +mod sys; + pub mod future; #[macro_use] pub mod http; diff --git a/src/net.rs b/src/net.rs new file mode 100644 index 0000000..6ddff7d --- /dev/null +++ b/src/net.rs @@ -0,0 +1,3 @@ +//! Async network abstractions. + +pub use crate::sys::net::*; diff --git a/src/rand.rs b/src/rand.rs new file mode 100644 index 0000000..3528400 --- /dev/null +++ b/src/rand.rs @@ -0,0 +1,3 @@ +//! Random number generation. + +pub use crate::sys::rand::*; diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..71d3acc --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,10 @@ +//! Async event loop support. +//! +//! The way to use this is to call [`block_on()`]. Inside the future, [`Reactor::current`] +//! will give an instance of the [`Reactor`] running the event loop, which can be +//! to [`AsyncPollable::wait_for`] instances of +//! [`wasip2::Pollable`](https://docs.rs/wasi/latest/wasi/io/poll/struct.Pollable.html). +//! This will automatically wait for the futures to resolve, and call the +//! necessary wakers to work. + +pub use crate::sys::runtime::*; diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 0000000..fd1edb3 --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,17 @@ +//! Platform-specific backends. +//! +//! Each supported target provides an implementation under `src/sys/`, selected +//! here by a single `cfg-if`. The rest of the crate is written against the +//! backend-agnostic `crate::sys::*` re-exports below, mirroring the layout used +//! by the `polling` crate. + +cfg_if::cfg_if! { + if #[cfg(all(target_os = "wasi", target_env = "p2"))] { + mod p2; + use p2 as backend; + } else { + compile_error!("unsupported target: wstd only compiles on `wasm32-wasip2`"); + } +} + +pub use backend::*; diff --git a/src/sys/p2/http/body.rs b/src/sys/p2/http/body.rs index 97b093c..db8e9ef 100644 --- a/src/sys/p2/http/body.rs +++ b/src/sys/p2/http/body.rs @@ -1,8 +1,5 @@ -use crate::http::{ - Error, HeaderMap, - error::Context as _, - fields::{header_map_from_wasi, header_map_to_wasi}, -}; +use super::fields::{header_map_from_wasi, header_map_to_wasi}; +use crate::http::{Error, HeaderMap, error::Context as _}; use crate::io::{AsyncInputStream, AsyncOutputStream}; use crate::runtime::{AsyncPollable, Reactor, WaitFor}; diff --git a/src/sys/p2/http/response.rs b/src/sys/p2/http/response.rs index 2ab8d87..44f264b 100644 --- a/src/sys/p2/http/response.rs +++ b/src/sys/p2/http/response.rs @@ -1,9 +1,10 @@ use http::StatusCode; use wasip2::http::types::IncomingResponse; +use super::fields::header_map_from_wasi; +use crate::http::HeaderMap; use crate::http::body::{Body, BodyHint}; use crate::http::error::Error; -use crate::http::fields::{HeaderMap, header_map_from_wasi}; pub use http::response::{Builder, Response}; diff --git a/src/sys/p2/io/mod.rs b/src/sys/p2/io/mod.rs new file mode 100644 index 0000000..9323962 --- /dev/null +++ b/src/sys/p2/io/mod.rs @@ -0,0 +1,5 @@ +mod stdio; +mod streams; + +pub use stdio::*; +pub use streams::*; diff --git a/src/sys/p2/io/stdio.rs b/src/sys/p2/io/stdio.rs index b2ac153..fa183a3 100644 --- a/src/sys/p2/io/stdio.rs +++ b/src/sys/p2/io/stdio.rs @@ -1,4 +1,4 @@ -use super::{AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite, Result}; +use crate::io::{AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite, Result}; use std::cell::LazyCell; use wasip2::cli::terminal_input::TerminalInput; use wasip2::cli::terminal_output::TerminalOutput; diff --git a/src/sys/p2/io/streams.rs b/src/sys/p2/io/streams.rs index 3676d21..5f95ca5 100644 --- a/src/sys/p2/io/streams.rs +++ b/src/sys/p2/io/streams.rs @@ -1,5 +1,5 @@ -use super::{AsyncPollable, AsyncRead, AsyncWrite}; -use crate::runtime::WaitFor; +use crate::io::{AsyncRead, AsyncWrite}; +use crate::runtime::{AsyncPollable, WaitFor}; use std::future::{Future, poll_fn}; use std::pin::Pin; use std::sync::{Mutex, OnceLock}; diff --git a/src/sys/p2/mod.rs b/src/sys/p2/mod.rs new file mode 100644 index 0000000..f7f323a --- /dev/null +++ b/src/sys/p2/mod.rs @@ -0,0 +1,8 @@ +//! The wasip2 (`wasm32-wasip2`) backend. + +pub mod http; +pub mod io; +pub mod net; +pub mod rand; +pub mod runtime; +pub mod time; diff --git a/src/sys/p2/runtime/mod.rs b/src/sys/p2/runtime/mod.rs index 24b9fc2..4c82015 100644 --- a/src/sys/p2/runtime/mod.rs +++ b/src/sys/p2/runtime/mod.rs @@ -8,7 +8,7 @@ //! necessary wakers to work. #![deny(missing_debug_implementations, nonstandard_style)] -#![warn(missing_docs, unreachable_pub)] +#![warn(missing_docs)] mod block_on; mod reactor; diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..2524af6 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,3 @@ +//! Async time interfaces. + +pub use crate::sys::time::*; From 5d4f2c526d11dda167c55df508b77b1499c56cb9 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 03:19:31 +0200 Subject: [PATCH 3/9] Document the backend contract and assert it at compile time Spell out, in `src/sys/mod.rs`, the duck-typed contract each `src/sys` backend must satisfy so the crate-root modules can be target-agnostic facades. Add a private `const _` block that statically checks the parts the facades depend on (the IO stream traits today), so backend drift fails fast with a clear message instead of surfacing deep inside a facade. No behavior or public-API change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/sys/mod.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/sys/mod.rs b/src/sys/mod.rs index fd1edb3..56e80ab 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -1,9 +1,16 @@ //! Platform-specific backends. //! //! Each supported target provides an implementation under `src/sys/`, selected -//! here by a single `cfg-if`. The rest of the crate is written against the -//! backend-agnostic `crate::sys::*` re-exports below, mirroring the layout used -//! by the `polling` crate. +//! here by a single `cfg-if`. The crate-root modules (`crate::time`, +//! `crate::io`, ...) are target-agnostic facades, free of `#[cfg]`, written +//! against the `crate::sys::*` items the selected backend provides. There is no +//! shared `trait`: backends are duck-typed in the `std::sys` style, and the +//! `const _` assertions below check the shapes the facades depend on. +//! +//! Backend modules: `time`, `io`, `net`, `http`, `rand`, `runtime`. The reified +//! pollable types (`AsyncPollable`, `WaitFor`) are p2-only and intentionally +//! left out of the common contract; once a second backend lands they become a +//! localized escape hatch rather than facade `#[cfg]`s. cfg_if::cfg_if! { if #[cfg(all(target_os = "wasi", target_env = "p2"))] { @@ -15,3 +22,13 @@ cfg_if::cfg_if! { } pub use backend::*; + +// Check the selected backend provides the shapes the facades rely on, so drift +// fails here instead of deep inside a facade. +const _: fn() = || { + fn assert_async_read() {} + fn assert_async_write() {} + + assert_async_read::(); + assert_async_write::(); +}; From 0c6bca18cf3c270ee4d5a058f84f3061af357670 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 03:23:09 +0200 Subject: [PATCH 4/9] Turn `time` into a cfg-free facade over a small clock contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promote the portable time types — `Duration`, `Instant`, `Interval`, `interval`, `Timer`, `Wait`, and the crate-internal `utils::timeout_err` — out of the wasip2 backend and into the `crate::time` facade, where they are written once with no `#[cfg]` and own all of their own arithmetic. The wasip2 backend (`src/sys/p2/time`) keeps only the primitives that genuinely depend on the WASI 0.2 clocks: the `MonotonicInstant` / `MonotonicDuration` nanosecond aliases, `now`, `SystemTime`, and a `Sleep` future built by `sleep_until`. `Timer` now records its deadline at construction (`TimerKind::{Never, At}`) and builds a fresh backend `Sleep` on each `wait`, so a second backend (p3) only has to supply a different `Sleep`/`sleep_until` rather than re-implementing the facade. Extend the backend-contract assertions in `src/sys/mod.rs` accordingly. Public API is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/sys/mod.rs | 6 + src/sys/p2/time/duration.rs | 216 ------------------ src/sys/p2/time/instant.rs | 91 -------- src/sys/p2/time/mod.rs | 142 ++++-------- src/sys/p2/time/utils.rs | 5 - src/time.rs | 432 +++++++++++++++++++++++++++++++++++- 6 files changed, 478 insertions(+), 414 deletions(-) delete mode 100644 src/sys/p2/time/duration.rs delete mode 100644 src/sys/p2/time/instant.rs delete mode 100644 src/sys/p2/time/utils.rs diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 56e80ab..cbb4385 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -28,7 +28,13 @@ pub use backend::*; const _: fn() = || { fn assert_async_read() {} fn assert_async_write() {} + fn assert_sleep_future>() {} assert_async_read::(); assert_async_write::(); + assert_sleep_future::(); + + let _: fn() -> crate::sys::time::MonotonicInstant = crate::sys::time::now; + let _: fn(crate::sys::time::MonotonicInstant) -> crate::sys::time::Sleep = + crate::sys::time::sleep_until; }; diff --git a/src/sys/p2/time/duration.rs b/src/sys/p2/time/duration.rs deleted file mode 100644 index 7f67ceb..0000000 --- a/src/sys/p2/time/duration.rs +++ /dev/null @@ -1,216 +0,0 @@ -use super::{Instant, Wait}; -use std::future::IntoFuture; -use std::ops::{Add, AddAssign, Sub, SubAssign}; -use wasip2::clocks::monotonic_clock; - -/// A Duration type to represent a span of time, typically used for system -/// timeouts. -/// -/// This type wraps `std::time::Duration` so we can implement traits on it -/// without coherence issues, just like if we were implementing this in the -/// stdlib. -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] -pub struct Duration(pub(crate) monotonic_clock::Duration); -impl Duration { - /// Creates a new `Duration` from the specified number of whole seconds and - /// additional nanoseconds. - #[must_use] - #[inline] - pub fn new(secs: u64, nanos: u32) -> Duration { - std::time::Duration::new(secs, nanos).into() - } - - /// Creates a new `Duration` from the specified number of whole seconds. - #[must_use] - #[inline] - pub fn from_secs(secs: u64) -> Duration { - std::time::Duration::from_secs(secs).into() - } - - /// Creates a new `Duration` from the specified number of milliseconds. - #[must_use] - #[inline] - pub fn from_millis(millis: u64) -> Self { - std::time::Duration::from_millis(millis).into() - } - - /// Creates a new `Duration` from the specified number of microseconds. - #[must_use] - #[inline] - pub fn from_micros(micros: u64) -> Self { - std::time::Duration::from_micros(micros).into() - } - - /// Creates a new `Duration` from the specified number of nanoseconds. - #[must_use] - #[inline] - pub fn from_nanos(nanos: u64) -> Self { - std::time::Duration::from_nanos(nanos).into() - } - - /// Creates a new `Duration` from the specified number of seconds represented - /// as `f64`. - /// - /// # Panics - /// This constructor will panic if `secs` is not finite, negative or overflows `Duration`. - /// - /// # Examples - /// ```no_run - /// use wstd::time::Duration; - /// - /// let dur = Duration::from_secs_f64(2.7); - /// assert_eq!(dur, Duration::new(2, 700_000_000)); - /// ``` - #[must_use] - #[inline] - pub fn from_secs_f64(secs: f64) -> Duration { - std::time::Duration::from_secs_f64(secs).into() - } - - /// Creates a new `Duration` from the specified number of seconds represented - /// as `f32`. - /// - /// # Panics - /// This constructor will panic if `secs` is not finite, negative or overflows `Duration`. - #[must_use] - #[inline] - pub fn from_secs_f32(secs: f32) -> Duration { - std::time::Duration::from_secs_f32(secs).into() - } - - /// Returns the number of whole seconds contained by this `Duration`. - #[must_use] - #[inline] - pub const fn as_secs(&self) -> u64 { - self.0 / 1_000_000_000 - } - - /// Returns the number of whole milliseconds contained by this `Duration`. - #[must_use] - #[inline] - pub const fn as_millis(&self) -> u128 { - (self.0 / 1_000_000) as u128 - } - - /// Returns the number of whole microseconds contained by this `Duration`. - #[must_use] - #[inline] - pub const fn as_micros(&self) -> u128 { - (self.0 / 1_000) as u128 - } - - /// Returns the total number of nanoseconds contained by this `Duration`. - #[must_use] - #[inline] - pub const fn as_nanos(&self) -> u128 { - self.0 as u128 - } -} - -impl From for Duration { - fn from(inner: std::time::Duration) -> Self { - Self( - inner - .as_nanos() - .try_into() - .expect("only dealing with durations that can fit in u64"), - ) - } -} - -impl From for std::time::Duration { - fn from(duration: Duration) -> Self { - Self::from_nanos(duration.0) - } -} - -impl Add for Duration { - type Output = Self; - - fn add(self, rhs: Duration) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl AddAssign for Duration { - fn add_assign(&mut self, rhs: Duration) { - *self = Self(self.0 + rhs.0) - } -} - -impl Sub for Duration { - type Output = Self; - - fn sub(self, rhs: Duration) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl SubAssign for Duration { - fn sub_assign(&mut self, rhs: Duration) { - *self = Self(self.0 - rhs.0) - } -} - -impl IntoFuture for Duration { - type Output = Instant; - - type IntoFuture = Wait; - - fn into_future(self) -> Self::IntoFuture { - crate::task::sleep(self) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_new_from_as() { - assert_eq!(Duration::new(456, 864209753).as_secs(), 456); - assert_eq!(Duration::new(456, 864209753).as_millis(), 456864); - assert_eq!(Duration::new(456, 864209753).as_micros(), 456864209); - assert_eq!(Duration::new(456, 864209753).as_nanos(), 456864209753); - - assert_eq!(Duration::from_secs(9876543210).as_secs(), 9876543210); - assert_eq!(Duration::from_secs(9876543210).as_millis(), 9876543210_000); - assert_eq!( - Duration::from_secs(9876543210).as_micros(), - 9876543210_000000 - ); - assert_eq!( - Duration::from_secs(9876543210).as_nanos(), - 9876543210_000000000 - ); - - assert_eq!(Duration::from_millis(9876543210).as_secs(), 9876543); - assert_eq!(Duration::from_millis(9876543210).as_millis(), 9876543210); - assert_eq!( - Duration::from_millis(9876543210).as_micros(), - 9876543210_000 - ); - assert_eq!( - Duration::from_millis(9876543210).as_nanos(), - 9876543210_000000 - ); - - assert_eq!(Duration::from_micros(9876543210).as_secs(), 9876); - assert_eq!(Duration::from_micros(9876543210).as_millis(), 9876543); - assert_eq!(Duration::from_micros(9876543210).as_micros(), 9876543210); - assert_eq!(Duration::from_micros(9876543210).as_nanos(), 9876543210_000); - - assert_eq!(Duration::from_nanos(9876543210).as_secs(), 9); - assert_eq!(Duration::from_nanos(9876543210).as_millis(), 9876); - assert_eq!(Duration::from_nanos(9876543210).as_micros(), 9876543); - assert_eq!(Duration::from_nanos(9876543210).as_nanos(), 9876543210); - } - - #[test] - fn test_from_secs_float() { - assert_eq!(Duration::from_secs_f64(158.9).as_secs(), 158); - assert_eq!(Duration::from_secs_f32(158.9).as_secs(), 158); - assert_eq!(Duration::from_secs_f64(159.1).as_secs(), 159); - assert_eq!(Duration::from_secs_f32(159.1).as_secs(), 159); - } -} diff --git a/src/sys/p2/time/instant.rs b/src/sys/p2/time/instant.rs deleted file mode 100644 index 6e9cf97..0000000 --- a/src/sys/p2/time/instant.rs +++ /dev/null @@ -1,91 +0,0 @@ -use super::{Duration, Wait}; -use std::future::IntoFuture; -use std::ops::{Add, AddAssign, Sub, SubAssign}; -use wasip2::clocks::monotonic_clock; - -/// A measurement of a monotonically nondecreasing clock. Opaque and useful only -/// with Duration. -/// -/// This type wraps `std::time::Duration` so we can implement traits on it -/// without coherence issues, just like if we were implementing this in the -/// stdlib. -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] -pub struct Instant(pub(crate) monotonic_clock::Instant); - -impl Instant { - /// Returns an instant corresponding to "now". - /// - /// # Examples - /// - /// ```no_run - /// use wstd::time::Instant; - /// - /// let now = Instant::now(); - /// ``` - #[must_use] - pub fn now() -> Self { - Instant(wasip2::clocks::monotonic_clock::now()) - } - - /// Returns the amount of time elapsed from another instant to this one, or zero duration if - /// that instant is later than this one. - pub fn duration_since(&self, earlier: Instant) -> Duration { - Duration::from_nanos(self.0.saturating_sub(earlier.0)) - } - - /// Returns the amount of time elapsed since this instant. - pub fn elapsed(&self) -> Duration { - Instant::now().duration_since(*self) - } -} - -impl Add for Instant { - type Output = Self; - - fn add(self, rhs: Duration) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl AddAssign for Instant { - fn add_assign(&mut self, rhs: Duration) { - *self = Self(self.0 + rhs.0) - } -} - -impl Sub for Instant { - type Output = Self; - - fn sub(self, rhs: Duration) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl SubAssign for Instant { - fn sub_assign(&mut self, rhs: Duration) { - *self = Self(self.0 - rhs.0) - } -} - -impl IntoFuture for Instant { - type Output = Instant; - - type IntoFuture = Wait; - - fn into_future(self) -> Self::IntoFuture { - crate::task::sleep_until(self) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_duration_since() { - let x = Instant::now(); - let d = Duration::new(456, 789); - let y = x + d; - assert_eq!(y.duration_since(x), d); - } -} diff --git a/src/sys/p2/time/mod.rs b/src/sys/p2/time/mod.rs index db0e1b3..92b8983 100644 --- a/src/sys/p2/time/mod.rs +++ b/src/sys/p2/time/mod.rs @@ -1,25 +1,30 @@ -//! Async time interfaces. +//! Monotonic and system clocks for the wasip2 backend. +//! +//! This is the platform half of the [`crate::time`] facade. The facade owns the +//! portable `Duration`/`Instant`/`Timer` types and all of their arithmetic; +//! this module provides only the primitives that genuinely depend on the WASI +//! 0.2 clocks. See [`crate::sys`] for the full backend contract. -pub(crate) mod utils; - -mod duration; -mod instant; -pub use duration::Duration; -pub use instant::Instant; - -use pin_project_lite::pin_project; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use wasip2::clocks::{ - monotonic_clock::{subscribe_duration, subscribe_instant}, - wall_clock, -}; +use wasip2::clocks::{monotonic_clock, wall_clock}; + +use crate::runtime::{Reactor, WaitFor}; + +/// A measurement of the monotonic clock, in nanoseconds. +/// +/// The facade's `Instant` wraps this. Keeping it a plain integer lets the +/// facade own all time arithmetic without coupling to the backend. +pub type MonotonicInstant = monotonic_clock::Instant; + +/// A span of monotonic-clock time, in nanoseconds. +pub type MonotonicDuration = monotonic_clock::Duration; -use crate::{ - iter::AsyncIterator, - runtime::{AsyncPollable, Reactor}, -}; +/// Return the current monotonic-clock instant. +pub fn now() -> MonotonicInstant { + monotonic_clock::now() +} /// A measurement of the system clock, useful for talking to external entities /// like the file system or other processes. May be converted losslessly to a @@ -42,97 +47,32 @@ impl From for std::time::SystemTime { } } -/// An async iterator representing notifications at fixed interval. -pub fn interval(duration: Duration) -> Interval { - Interval { duration } -} - -/// An async iterator representing notifications at fixed interval. +/// A future that resolves once the monotonic clock reaches a deadline. /// -/// See the [`interval`] function for more. -#[derive(Debug)] -pub struct Interval { - duration: Duration, -} -impl AsyncIterator for Interval { - type Item = Instant; - - async fn next(&mut self) -> Option { - Some(Timer::after(self.duration).wait().await) - } -} - +/// Created by [`sleep_until`]. This is the backend `Sleep` type named by the +/// facade's `Timer`/`Wait`; on p2 it is a thin wrapper over a reactor-scheduled +/// `monotonic-clock` pollable. +#[must_use = "futures do nothing unless polled or .awaited"] #[derive(Debug)] -pub struct Timer(Option); - -impl Timer { - pub fn never() -> Timer { - Timer(None) - } - pub fn at(deadline: Instant) -> Timer { - let pollable = Reactor::current().schedule(subscribe_instant(deadline.0)); - Timer(Some(pollable)) - } - pub fn after(duration: Duration) -> Timer { - let pollable = Reactor::current().schedule(subscribe_duration(duration.0)); - Timer(Some(pollable)) - } - pub fn set_after(&mut self, duration: Duration) { - *self = Self::after(duration); - } - pub fn wait(&self) -> Wait { - let wait_for = self.0.as_ref().map(AsyncPollable::wait_for); - Wait { wait_for } - } +pub struct Sleep { + wait_for: WaitFor, } -pin_project! { - /// Future created by [`Timer::wait`] - #[must_use = "futures do nothing unless polled or .awaited"] - pub struct Wait { - #[pin] - wait_for: Option - } -} +impl Future for Sleep { + type Output = (); -impl Future for Wait { - type Output = Instant; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - match this.wait_for.as_pin_mut() { - None => Poll::Pending, - Some(f) => match f.poll(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(()) => Poll::Ready(Instant::now()), - }, - } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.wait_for).poll(cx) } } -#[cfg(test)] -mod test { - use super::*; - - async fn debug_duration(what: &str, f: impl Future) { - let start = Instant::now(); - let now = f.await; - let d = now.duration_since(start); - let d: std::time::Duration = d.into(); - println!("{what} awaited for {} s", d.as_secs_f32()); - } - - #[test] - fn timer_now() { - crate::runtime::block_on(debug_duration("timer_now", async { - Timer::at(Instant::now()).wait().await - })); - } - - #[test] - fn timer_after_100_milliseconds() { - crate::runtime::block_on(debug_duration("timer_after_100_milliseconds", async { - Timer::after(Duration::from_millis(100)).wait().await - })); +/// Create a [`Sleep`] future that resolves when the monotonic clock reaches +/// `deadline`. +/// +/// Must be called from within [`crate::runtime::block_on`]. +pub fn sleep_until(deadline: MonotonicInstant) -> Sleep { + let pollable = Reactor::current().schedule(monotonic_clock::subscribe_instant(deadline)); + Sleep { + wait_for: pollable.wait_for(), } } diff --git a/src/sys/p2/time/utils.rs b/src/sys/p2/time/utils.rs deleted file mode 100644 index e6e3993..0000000 --- a/src/sys/p2/time/utils.rs +++ /dev/null @@ -1,5 +0,0 @@ -use std::io; - -pub(crate) fn timeout_err(msg: &'static str) -> io::Error { - io::Error::new(io::ErrorKind::TimedOut, msg) -} diff --git a/src/time.rs b/src/time.rs index 2524af6..043bb03 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,3 +1,433 @@ //! Async time interfaces. +//! +//! This module is a target-agnostic *facade*: it owns the portable +//! `Duration`/`Instant`/`Timer`/`Interval` types and all of their arithmetic, +//! and is written once against the small clock contract each backend provides +//! under `crate::sys::time` (see [`crate::sys`]). The only backend-specific +//! type re-exported here is [`SystemTime`]. -pub use crate::sys::time::*; +use pin_project_lite::pin_project; +use std::future::{Future, IntoFuture}; +use std::ops::{Add, AddAssign, Sub, SubAssign}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use crate::iter::AsyncIterator; + +pub use crate::sys::time::SystemTime; + +pub(crate) mod utils { + use std::io; + + pub(crate) fn timeout_err(msg: &'static str) -> io::Error { + io::Error::new(io::ErrorKind::TimedOut, msg) + } +} + +/// A Duration type to represent a span of time, typically used for system +/// timeouts. +/// +/// This type wraps `std::time::Duration` so we can implement traits on it +/// without coherence issues, just like if we were implementing this in the +/// stdlib. +#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] +pub struct Duration(pub(crate) crate::sys::time::MonotonicDuration); +impl Duration { + /// Creates a new `Duration` from the specified number of whole seconds and + /// additional nanoseconds. + #[must_use] + #[inline] + pub fn new(secs: u64, nanos: u32) -> Duration { + std::time::Duration::new(secs, nanos).into() + } + + /// Creates a new `Duration` from the specified number of whole seconds. + #[must_use] + #[inline] + pub fn from_secs(secs: u64) -> Duration { + std::time::Duration::from_secs(secs).into() + } + + /// Creates a new `Duration` from the specified number of milliseconds. + #[must_use] + #[inline] + pub fn from_millis(millis: u64) -> Self { + std::time::Duration::from_millis(millis).into() + } + + /// Creates a new `Duration` from the specified number of microseconds. + #[must_use] + #[inline] + pub fn from_micros(micros: u64) -> Self { + std::time::Duration::from_micros(micros).into() + } + + /// Creates a new `Duration` from the specified number of nanoseconds. + #[must_use] + #[inline] + pub fn from_nanos(nanos: u64) -> Self { + std::time::Duration::from_nanos(nanos).into() + } + + /// Creates a new `Duration` from the specified number of seconds represented + /// as `f64`. + /// + /// # Panics + /// This constructor will panic if `secs` is not finite, negative or overflows `Duration`. + /// + /// # Examples + /// ```no_run + /// use wstd::time::Duration; + /// + /// let dur = Duration::from_secs_f64(2.7); + /// assert_eq!(dur, Duration::new(2, 700_000_000)); + /// ``` + #[must_use] + #[inline] + pub fn from_secs_f64(secs: f64) -> Duration { + std::time::Duration::from_secs_f64(secs).into() + } + + /// Creates a new `Duration` from the specified number of seconds represented + /// as `f32`. + /// + /// # Panics + /// This constructor will panic if `secs` is not finite, negative or overflows `Duration`. + #[must_use] + #[inline] + pub fn from_secs_f32(secs: f32) -> Duration { + std::time::Duration::from_secs_f32(secs).into() + } + + /// Returns the number of whole seconds contained by this `Duration`. + #[must_use] + #[inline] + pub const fn as_secs(&self) -> u64 { + self.0 / 1_000_000_000 + } + + /// Returns the number of whole milliseconds contained by this `Duration`. + #[must_use] + #[inline] + pub const fn as_millis(&self) -> u128 { + (self.0 / 1_000_000) as u128 + } + + /// Returns the number of whole microseconds contained by this `Duration`. + #[must_use] + #[inline] + pub const fn as_micros(&self) -> u128 { + (self.0 / 1_000) as u128 + } + + /// Returns the total number of nanoseconds contained by this `Duration`. + #[must_use] + #[inline] + pub const fn as_nanos(&self) -> u128 { + self.0 as u128 + } +} + +impl From for Duration { + fn from(inner: std::time::Duration) -> Self { + Self( + inner + .as_nanos() + .try_into() + .expect("only dealing with durations that can fit in u64"), + ) + } +} + +impl From for std::time::Duration { + fn from(duration: Duration) -> Self { + Self::from_nanos(duration.0) + } +} + +impl Add for Duration { + type Output = Self; + + fn add(self, rhs: Duration) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: Duration) { + *self = Self(self.0 + rhs.0) + } +} + +impl Sub for Duration { + type Output = Self; + + fn sub(self, rhs: Duration) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + *self = Self(self.0 - rhs.0) + } +} + +impl IntoFuture for Duration { + type Output = Instant; + + type IntoFuture = Wait; + + fn into_future(self) -> Self::IntoFuture { + crate::task::sleep(self) + } +} + +/// A measurement of a monotonically nondecreasing clock. Opaque and useful only +/// with Duration. +/// +/// This type wraps `std::time::Duration` so we can implement traits on it +/// without coherence issues, just like if we were implementing this in the +/// stdlib. +#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)] +pub struct Instant(pub(crate) crate::sys::time::MonotonicInstant); + +impl Instant { + /// Returns an instant corresponding to "now". + /// + /// # Examples + /// + /// ```no_run + /// use wstd::time::Instant; + /// + /// let now = Instant::now(); + /// ``` + #[must_use] + pub fn now() -> Self { + Instant(crate::sys::time::now()) + } + + /// Returns the amount of time elapsed from another instant to this one, or zero duration if + /// that instant is later than this one. + pub fn duration_since(&self, earlier: Instant) -> Duration { + Duration::from_nanos(self.0.saturating_sub(earlier.0)) + } + + /// Returns the amount of time elapsed since this instant. + pub fn elapsed(&self) -> Duration { + Instant::now().duration_since(*self) + } +} + +impl Add for Instant { + type Output = Self; + + fn add(self, rhs: Duration) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for Instant { + fn add_assign(&mut self, rhs: Duration) { + *self = Self(self.0 + rhs.0) + } +} + +impl Sub for Instant { + type Output = Self; + + fn sub(self, rhs: Duration) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl SubAssign for Instant { + fn sub_assign(&mut self, rhs: Duration) { + *self = Self(self.0 - rhs.0) + } +} + +impl IntoFuture for Instant { + type Output = Instant; + + type IntoFuture = Wait; + + fn into_future(self) -> Self::IntoFuture { + crate::task::sleep_until(self) + } +} + +/// An async iterator representing notifications at fixed interval. +pub fn interval(duration: Duration) -> Interval { + Interval { duration } +} + +/// An async iterator representing notifications at fixed interval. +/// +/// See the [`interval`] function for more. +#[derive(Debug)] +pub struct Interval { + duration: Duration, +} +impl AsyncIterator for Interval { + type Item = Instant; + + async fn next(&mut self) -> Option { + Some(Timer::after(self.duration).wait().await) + } +} + +/// A measurement that resolves at a deadline, or never. +/// +/// A `Timer` records *when* it should fire when it is constructed; each call to +/// [`Timer::wait`] then builds a fresh [`Wait`] future against the backend +/// clock. Because the deadline is captured up front, `wait` is repeatable and a +/// `Timer` can be polled into more than once. +#[derive(Debug)] +pub struct Timer(TimerKind); + +#[derive(Debug, Clone, Copy)] +enum TimerKind { + /// Never fires; the resulting [`Wait`] is pending forever. + Never, + /// Fires once the monotonic clock reaches this instant. + At(Instant), +} + +impl Timer { + /// Create a `Timer` that never fires. + pub fn never() -> Timer { + Timer(TimerKind::Never) + } + /// Create a `Timer` that fires at `deadline`. + pub fn at(deadline: Instant) -> Timer { + Timer(TimerKind::At(deadline)) + } + /// Create a `Timer` that fires `duration` from now. + /// + /// The deadline is computed at construction time, matching the behavior of + /// `std::time` and preserving it across repeated [`wait`](Timer::wait) + /// calls. + pub fn after(duration: Duration) -> Timer { + Timer(TimerKind::At(Instant::now() + duration)) + } + /// Reset the `Timer` to fire `duration` from now. + pub fn set_after(&mut self, duration: Duration) { + *self = Self::after(duration); + } + /// Create a future that resolves when the `Timer` fires. + pub fn wait(&self) -> Wait { + let sleep = match self.0 { + TimerKind::Never => None, + TimerKind::At(deadline) => Some(crate::sys::time::sleep_until(deadline.0)), + }; + Wait { sleep } + } +} + +pin_project! { + /// Future created by [`Timer::wait`] + #[must_use = "futures do nothing unless polled or .awaited"] + pub struct Wait { + #[pin] + sleep: Option + } +} + +impl Future for Wait { + type Output = Instant; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + match this.sleep.as_pin_mut() { + None => Poll::Pending, + Some(sleep) => match sleep.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(()) => Poll::Ready(Instant::now()), + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_from_as() { + assert_eq!(Duration::new(456, 864209753).as_secs(), 456); + assert_eq!(Duration::new(456, 864209753).as_millis(), 456864); + assert_eq!(Duration::new(456, 864209753).as_micros(), 456864209); + assert_eq!(Duration::new(456, 864209753).as_nanos(), 456864209753); + + assert_eq!(Duration::from_secs(9876543210).as_secs(), 9876543210); + assert_eq!(Duration::from_secs(9876543210).as_millis(), 9876543210_000); + assert_eq!( + Duration::from_secs(9876543210).as_micros(), + 9876543210_000000 + ); + assert_eq!( + Duration::from_secs(9876543210).as_nanos(), + 9876543210_000000000 + ); + + assert_eq!(Duration::from_millis(9876543210).as_secs(), 9876543); + assert_eq!(Duration::from_millis(9876543210).as_millis(), 9876543210); + assert_eq!( + Duration::from_millis(9876543210).as_micros(), + 9876543210_000 + ); + assert_eq!( + Duration::from_millis(9876543210).as_nanos(), + 9876543210_000000 + ); + + assert_eq!(Duration::from_micros(9876543210).as_secs(), 9876); + assert_eq!(Duration::from_micros(9876543210).as_millis(), 9876543); + assert_eq!(Duration::from_micros(9876543210).as_micros(), 9876543210); + assert_eq!(Duration::from_micros(9876543210).as_nanos(), 9876543210_000); + + assert_eq!(Duration::from_nanos(9876543210).as_secs(), 9); + assert_eq!(Duration::from_nanos(9876543210).as_millis(), 9876); + assert_eq!(Duration::from_nanos(9876543210).as_micros(), 9876543); + assert_eq!(Duration::from_nanos(9876543210).as_nanos(), 9876543210); + } + + #[test] + fn test_from_secs_float() { + assert_eq!(Duration::from_secs_f64(158.9).as_secs(), 158); + assert_eq!(Duration::from_secs_f32(158.9).as_secs(), 158); + assert_eq!(Duration::from_secs_f64(159.1).as_secs(), 159); + assert_eq!(Duration::from_secs_f32(159.1).as_secs(), 159); + } + + #[test] + fn test_duration_since() { + let x = Instant::now(); + let d = Duration::new(456, 789); + let y = x + d; + assert_eq!(y.duration_since(x), d); + } + + async fn debug_duration(what: &str, f: impl Future) { + let start = Instant::now(); + let now = f.await; + let d = now.duration_since(start); + let d: std::time::Duration = d.into(); + println!("{what} awaited for {} s", d.as_secs_f32()); + } + + #[test] + fn timer_now() { + crate::runtime::block_on(debug_duration("timer_now", async { + Timer::at(Instant::now()).wait().await + })); + } + + #[test] + fn timer_after_100_milliseconds() { + crate::runtime::block_on(debug_duration("timer_after_100_milliseconds", async { + Timer::after(Duration::from_millis(100)).wait().await + })); + } +} From 5428a40aa28e81845e2e3021bbed6f899ff8a2d4 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 03:42:23 +0200 Subject: [PATCH 5/9] Hoist the portable `rand` surface into a facade The length guard and slice copy in `get_random_bytes` / `get_insecure_random_bytes` are identical across backends; only the host RNG call differs. Move the two public functions up into the `crate::rand` facade and reduce the wasip2 backend to `random_bytes` / `insecure_random_bytes` primitives that return the raw bytes. Public API is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/rand.rs | 18 +++++++++++++++++- src/sys/p2/rand/mod.rs | 29 ++++++++++------------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/rand.rs b/src/rand.rs index 3528400..cdad313 100644 --- a/src/rand.rs +++ b/src/rand.rs @@ -1,3 +1,19 @@ //! Random number generation. -pub use crate::sys::rand::*; +/// Fill the slice with cryptographically secure random bytes. +pub fn get_random_bytes(buf: &mut [u8]) { + if buf.is_empty() { + return; + } + let output = crate::sys::rand::random_bytes(buf.len() as u64); + buf.copy_from_slice(&output); +} + +/// Fill the slice with insecure random bytes. +pub fn get_insecure_random_bytes(buf: &mut [u8]) { + if buf.is_empty() { + return; + } + let output = crate::sys::rand::insecure_random_bytes(buf.len() as u64); + buf.copy_from_slice(&output); +} diff --git a/src/sys/p2/rand/mod.rs b/src/sys/p2/rand/mod.rs index 8474b80..c572382 100644 --- a/src/sys/p2/rand/mod.rs +++ b/src/sys/p2/rand/mod.rs @@ -1,25 +1,16 @@ -//! Random number generation. +//! Random number generation primitives for the wasip2 backend. +//! +//! This is the platform half of the [`crate::rand`] facade: it only reaches the +//! host RNG. The length guard and slice copy live in the facade. use wasip2::random; -/// Fill the slice with cryptographically secure random bytes. -pub fn get_random_bytes(buf: &mut [u8]) { - match buf.len() { - 0 => {} - _ => { - let output = random::random::get_random_bytes(buf.len() as u64); - buf.copy_from_slice(&output[..]); - } - } +/// Return `len` cryptographically secure random bytes. +pub fn random_bytes(len: u64) -> Vec { + random::random::get_random_bytes(len) } -/// Fill the slice with insecure random bytes. -pub fn get_insecure_random_bytes(buf: &mut [u8]) { - match buf.len() { - 0 => {} - _ => { - let output = random::insecure::get_insecure_random_bytes(buf.len() as u64); - buf.copy_from_slice(&output[..]); - } - } +/// Return `len` insecure, non-cryptographic random bytes. +pub fn insecure_random_bytes(len: u64) -> Vec { + random::insecure::get_insecure_random_bytes(len) } From e030a6a6b4c590cd6fd523a69259a9dcb4ff3595 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 03:44:12 +0200 Subject: [PATCH 6/9] Hoist the portable runtime executor surface into a facade `spawn` and the `async_task::Task` re-export are identical across backends; only `block_on` and the `Reactor` internals are platform specific. Move `spawn` and `Task` up into the `crate::runtime` facade and re-export `block_on`/`Reactor`/`AsyncPollable`/`WaitFor` from the wasip2 backend. `REACTOR` and `Reactor` stay in the backend. Public API is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/runtime.rs | 15 ++++++++++++++- src/sys/p2/runtime/mod.rs | 12 ------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/runtime.rs b/src/runtime.rs index 71d3acc..4aeb1e4 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -7,4 +7,17 @@ //! This will automatically wait for the futures to resolve, and call the //! necessary wakers to work. -pub use crate::sys::runtime::*; +pub use ::async_task::Task; + +pub use crate::sys::runtime::{AsyncPollable, Reactor, WaitFor, block_on}; + +/// Spawn a `Future` as a `Task` on the current `Reactor`. +/// +/// Panics if called from outside `block_on`. +pub fn spawn(fut: F) -> Task +where + F: std::future::Future + 'static, + T: 'static, +{ + Reactor::current().spawn(fut) +} diff --git a/src/sys/p2/runtime/mod.rs b/src/sys/p2/runtime/mod.rs index 4c82015..009eb1f 100644 --- a/src/sys/p2/runtime/mod.rs +++ b/src/sys/p2/runtime/mod.rs @@ -13,7 +13,6 @@ mod block_on; mod reactor; -pub use ::async_task::Task; pub use block_on::block_on; pub use reactor::{AsyncPollable, Reactor, WaitFor}; use std::cell::RefCell; @@ -23,14 +22,3 @@ use std::cell::RefCell; std::thread_local! { pub(crate) static REACTOR: RefCell> = const { RefCell::new(None) }; } - -/// Spawn a `Future` as a `Task` on the current `Reactor`. -/// -/// Panics if called from outside `block_on`. -pub fn spawn(fut: F) -> Task -where - F: std::future::Future + 'static, - T: 'static, -{ - Reactor::current().spawn(fut) -} From 1e978e1c1bfbd84c3451c4dd56994a080cde1249 Mon Sep 17 00:00:00 2001 From: Yosh Date: Tue, 9 Jun 2026 03:50:12 +0200 Subject: [PATCH 7/9] Hoist the portable http surface into a facade Convert `src/http.rs` from a blanket `pub use crate::sys::http::*` shim into a real facade: it owns the portable surface (`StatusCode`, `Uri`/`Authority`/ `PathAndQuery`, the `error` module of `anyhow`/`http`-crate error types, and the `body`/`request`/`response`/`server` public submodule paths) and re-exports the backend's concrete types (`Client`, `Method`, `Scheme`, `HeaderMap`, `Body`, ...) through `crate::sys::http::*`. This keeps every public path identical while moving the target-agnostic shape out of the backend, so a future p3 backend only has to supply the platform pieces. The backend `sys::p2::http` now exposes just the platform impl: submodules with `pub`/`pub(crate)` visibility plus `pub use wasip2::http::types::{ErrorCode, HeaderError}`. Its impl files import the public error/value types from the facade (`crate::http::*`) instead of the removed backend-level re-exports, and the public `server` module doc moves up to the facade. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/http.rs | 70 ++++++++++++++++++++++++++++++++++++-- src/sys/p2/http/client.rs | 2 +- src/sys/p2/http/error.rs | 13 ------- src/sys/p2/http/fields.rs | 2 +- src/sys/p2/http/mod.rs | 27 ++++----------- src/sys/p2/http/request.rs | 8 ++--- src/sys/p2/http/server.rs | 23 ++----------- 7 files changed, 84 insertions(+), 61 deletions(-) delete mode 100644 src/sys/p2/http/error.rs diff --git a/src/http.rs b/src/http.rs index ed2c4a2..12309be 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,3 +1,69 @@ //! HTTP networking support -//! -pub use crate::sys::http::*; + +pub use http::status::StatusCode; +pub use http::uri::{Authority, PathAndQuery, Uri}; + +pub use crate::sys::http::client::Client; +pub use crate::sys::http::fields::{HeaderMap, HeaderName, HeaderValue}; +pub use crate::sys::http::method::Method; +pub use crate::sys::http::scheme::{InvalidUri, Scheme}; +#[doc(inline)] +pub use body::{Body, util::BodyExt}; +pub use error::{Error, ErrorCode, Result}; +pub use request::Request; +pub use response::Response; + +pub mod body { + //! HTTP body types. + pub use crate::sys::http::body::*; +} + +pub mod error { + //! The http portion of wstd uses `anyhow::Error` as its `Error` type. + //! + //! There are various concrete error types + + pub use crate::http::body::InvalidContentLength; + pub use crate::sys::http::{ErrorCode, HeaderError}; + pub use anyhow::Context; + pub use http::header::{InvalidHeaderName, InvalidHeaderValue}; + pub use http::method::InvalidMethod; + + pub type Error = anyhow::Error; + /// The `http` result type. + pub type Result = std::result::Result; +} + +pub mod request { + //! HTTP request types. + pub use crate::sys::http::request::*; +} + +pub mod response { + //! HTTP response types. + pub use crate::sys::http::response::*; +} + +pub mod server { + //! HTTP servers + //! + //! The WASI HTTP server uses the [typed main] idiom, with a `main` function + //! that takes a [`Request`] and succeeds with a [`Response`], using the + //! [`http_server`] macro: + //! + //! ```no_run + //! use wstd::http::{Request, Response, Body, Error}; + //! #[wstd::http_server] + //! async fn main(_request: Request) -> Result, Error> { + //! Ok(Response::new("Hello!\n".into())) + //! } + //! ``` + //! + //! [typed main]: https://sunfishcode.github.io/typed-main-wasi-presentation/chapter_1.html + //! [`Request`]: crate::http::Request + //! [`Responder`]: crate::http::server::Responder + //! [`Response`]: crate::http::Response + //! [`http_server`]: crate::http_server + + pub use crate::sys::http::server::*; +} diff --git a/src/sys/p2/http/client.rs b/src/sys/p2/http/client.rs index 3676fa8..6f7c47b 100644 --- a/src/sys/p2/http/client.rs +++ b/src/sys/p2/http/client.rs @@ -1,6 +1,6 @@ -use super::{Body, Error, Request, Response}; use crate::http::request::try_into_outgoing; use crate::http::response::try_from_incoming; +use crate::http::{Body, Error, Request, Response}; use crate::io::AsyncPollable; use crate::time::Duration; use wasip2::http::types::RequestOptions as WasiRequestOptions; diff --git a/src/sys/p2/http/error.rs b/src/sys/p2/http/error.rs deleted file mode 100644 index a4f22b0..0000000 --- a/src/sys/p2/http/error.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! The http portion of wstd uses `anyhow::Error` as its `Error` type. -//! -//! There are various concrete error types - -pub use crate::http::body::InvalidContentLength; -pub use anyhow::Context; -pub use http::header::{InvalidHeaderName, InvalidHeaderValue}; -pub use http::method::InvalidMethod; -pub use wasip2::http::types::{ErrorCode, HeaderError}; - -pub type Error = anyhow::Error; -/// The `http` result type. -pub type Result = std::result::Result; diff --git a/src/sys/p2/http/fields.rs b/src/sys/p2/http/fields.rs index de6df16..76162ef 100644 --- a/src/sys/p2/http/fields.rs +++ b/src/sys/p2/http/fields.rs @@ -1,6 +1,6 @@ pub use http::header::{HeaderMap, HeaderName, HeaderValue}; -use super::{Error, error::Context}; +use crate::http::{Error, error::Context}; use wasip2::http::types::Fields; pub(crate) fn header_map_from_wasi(wasi_fields: Fields) -> Result { diff --git a/src/sys/p2/http/mod.rs b/src/sys/p2/http/mod.rs index 39f0a40..3ae5a9f 100644 --- a/src/sys/p2/http/mod.rs +++ b/src/sys/p2/http/mod.rs @@ -1,25 +1,12 @@ -//! HTTP networking support -//! -pub use http::status::StatusCode; -pub use http::uri::{Authority, PathAndQuery, Uri}; - -#[doc(inline)] -pub use body::{Body, util::BodyExt}; -pub use client::Client; -pub use error::{Error, ErrorCode, Result}; -pub use fields::{HeaderMap, HeaderName, HeaderValue}; -pub use method::Method; -pub use request::Request; -pub use response::Response; -pub use scheme::{InvalidUri, Scheme}; +//! HTTP networking support (wasip2 backend). pub mod body; - -mod client; -pub mod error; -mod fields; -mod method; +pub(crate) mod client; +pub(crate) mod fields; +pub(crate) mod method; pub mod request; pub mod response; -mod scheme; +pub(crate) mod scheme; pub mod server; + +pub use wasip2::http::types::{ErrorCode, HeaderError}; diff --git a/src/sys/p2/http/request.rs b/src/sys/p2/http/request.rs index 6694d03..fd65cff 100644 --- a/src/sys/p2/http/request.rs +++ b/src/sys/p2/http/request.rs @@ -1,10 +1,10 @@ -use super::{ +use super::fields::{header_map_from_wasi, header_map_to_wasi}; +use super::method::{from_wasi_method, to_wasi_method}; +use super::scheme::{from_wasi_scheme, to_wasi_scheme}; +use crate::http::{ Authority, HeaderMap, PathAndQuery, Uri, body::{Body, BodyHint}, error::{Context, Error, ErrorCode}, - fields::{header_map_from_wasi, header_map_to_wasi}, - method::{from_wasi_method, to_wasi_method}, - scheme::{from_wasi_scheme, to_wasi_scheme}, }; use wasip2::http::outgoing_handler::OutgoingRequest; use wasip2::http::types::IncomingRequest; diff --git a/src/sys/p2/http/server.rs b/src/sys/p2/http/server.rs index 9fb6ff4..709197f 100644 --- a/src/sys/p2/http/server.rs +++ b/src/sys/p2/http/server.rs @@ -1,24 +1,7 @@ -//! HTTP servers -//! -//! The WASI HTTP server uses the [typed main] idiom, with a `main` function -//! that takes a [`Request`] and succeeds with a [`Response`], using the -//! [`http_server`] macro: -//! -//! ```no_run -//! use wstd::http::{Request, Response, Body, Error}; -//! #[wstd::http_server] -//! async fn main(_request: Request) -> Result, Error> { -//! Ok(Response::new("Hello!\n".into())) -//! } -//! ``` -//! -//! [typed main]: https://sunfishcode.github.io/typed-main-wasi-presentation/chapter_1.html -//! [`Request`]: crate::http::Request -//! [`Responder`]: crate::http::server::Responder -//! [`Response`]: crate::http::Response -//! [`http_server`]: crate::http_server +//! HTTP servers (wasip2 backend). -use super::{Body, Error, Response, error::ErrorCode, fields::header_map_to_wasi}; +use super::fields::header_map_to_wasi; +use crate::http::{Body, Error, Response, error::ErrorCode}; use http::header::CONTENT_LENGTH; use wasip2::exports::http::incoming_handler::ResponseOutparam; use wasip2::http::types::OutgoingResponse; From efeaeea1650ab16760de2e38fdab3e6aa2d30966 Mon Sep 17 00:00:00 2001 From: Yosh Date: Fri, 3 Jul 2026 02:48:58 +0200 Subject: [PATCH 8/9] Turn net into a facade that delegates to the backend `src/net.rs` was a blanket `pub use crate::sys::net::*`. Make it a real facade: declare the crate's public network surface -- `TcpStream`, `TcpListener`, `ReadHalf`, `WriteHalf`, `Incoming` -- as thin wrappers at the crate root that delegate every operation to the selected backend's implementation under `crate::sys::net`. The one genuinely portable piece, `TcpStream::connect`'s `ToSocketAddrs` resolve-and-retry loop, is written once here against the backend's `connect_addr` primitive (removed from the backend), so a second backend inherits it for free. Socket-shutdown cleanup still runs through the inner backend handles' own `Drop`; the facade keeps explicit `Drop` impls so the public drop contract is unchanged. Public API is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/net.rs | 205 ++++++++++++++++++++++++++++++++++- src/sys/p2/net/tcp_stream.rs | 26 +---- 2 files changed, 205 insertions(+), 26 deletions(-) diff --git a/src/net.rs b/src/net.rs index 6ddff7d..37b9371 100644 --- a/src/net.rs +++ b/src/net.rs @@ -1,3 +1,206 @@ //! Async network abstractions. +//! +//! The types here are the crate's public network surface. They are thin +//! facades that delegate to the selected backend's implementation under +//! [`crate::sys::net`]; the portable address-resolution loop in +//! [`TcpStream::connect`] is the one piece written once here rather than per +//! backend. A second backend only has to supply the `sys::net` primitives. -pub use crate::sys::net::*; +use crate::io::{self, AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite}; +use crate::iter::AsyncIterator; +use std::io::ErrorKind; +use std::net::{SocketAddr, ToSocketAddrs}; + +/// A TCP stream between a local and a remote socket. +pub struct TcpStream { + inner: crate::sys::net::TcpStream, +} + +impl TcpStream { + /// Opens a TCP connection to a remote host. + /// + /// `addr` is an address of the remote host. Anything which implements the + /// [`ToSocketAddrs`] trait can be supplied as the address. If `addr` + /// yields multiple addresses, connect will be attempted with each of the + /// addresses until a connection is successful. If none of the addresses + /// result in a successful connection, the error returned from the last + /// connection attempt (the last address) is returned. + pub async fn connect(addr: impl ToSocketAddrs) -> io::Result { + let addrs = addr.to_socket_addrs()?; + let mut last_err = None; + for addr in addrs { + match TcpStream::connect_addr(addr).await { + Ok(stream) => return Ok(stream), + Err(e) => last_err = Some(e), + } + } + + Err(last_err.unwrap_or_else(|| { + io::Error::new(ErrorKind::InvalidInput, "could not resolve to any address") + })) + } + + /// Establishes a connection to the specified `addr`. + pub async fn connect_addr(addr: SocketAddr) -> io::Result { + Ok(Self { + inner: crate::sys::net::TcpStream::connect_addr(addr).await?, + }) + } + + /// Returns the socket address of the remote peer of this TCP connection. + pub fn peer_addr(&self) -> io::Result { + self.inner.peer_addr() + } + + /// Splits this stream into a read half and a write half, which can be used + /// to read and write concurrently. + pub fn split(&self) -> (ReadHalf<'_>, WriteHalf<'_>) { + let (read, write) = self.inner.split(); + (ReadHalf(read), WriteHalf(write)) + } +} + +impl AsyncRead for TcpStream { + async fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf).await + } + + fn as_async_input_stream(&self) -> Option<&AsyncInputStream> { + self.inner.as_async_input_stream() + } +} + +impl AsyncRead for &TcpStream { + async fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut inner = &self.inner; + inner.read(buf).await + } + + fn as_async_input_stream(&self) -> Option<&AsyncInputStream> { + self.inner.as_async_input_stream() + } +} + +impl AsyncWrite for TcpStream { + async fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf).await + } + + async fn flush(&mut self) -> io::Result<()> { + self.inner.flush().await + } + + fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> { + self.inner.as_async_output_stream() + } +} + +impl AsyncWrite for &TcpStream { + async fn write(&mut self, buf: &[u8]) -> io::Result { + let mut inner = &self.inner; + inner.write(buf).await + } + + async fn flush(&mut self) -> io::Result<()> { + let mut inner = &self.inner; + inner.flush().await + } + + fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> { + self.inner.as_async_output_stream() + } +} + +impl Drop for TcpStream { + fn drop(&mut self) { + // The socket shutdown runs when the inner backend handle is dropped. + } +} + +/// The read half of a [`TcpStream`], created by [`TcpStream::split`]. +pub struct ReadHalf<'a>(crate::sys::net::ReadHalf<'a>); + +impl<'a> AsyncRead for ReadHalf<'a> { + async fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf).await + } + + fn as_async_input_stream(&self) -> Option<&AsyncInputStream> { + self.0.as_async_input_stream() + } +} + +impl<'a> Drop for ReadHalf<'a> { + fn drop(&mut self) { + // The receive-side shutdown runs when the inner backend handle is dropped. + } +} + +/// The write half of a [`TcpStream`], created by [`TcpStream::split`]. +pub struct WriteHalf<'a>(crate::sys::net::WriteHalf<'a>); + +impl<'a> AsyncWrite for WriteHalf<'a> { + async fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf).await + } + + async fn flush(&mut self) -> io::Result<()> { + self.0.flush().await + } + + fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> { + self.0.as_async_output_stream() + } +} + +impl<'a> Drop for WriteHalf<'a> { + fn drop(&mut self) { + // The send-side shutdown runs when the inner backend handle is dropped. + } +} + +/// A TCP socket server, listening for connections. +#[derive(Debug)] +pub struct TcpListener { + inner: crate::sys::net::TcpListener, +} + +impl TcpListener { + /// Creates a new TcpListener which will be bound to the specified address. + /// + /// The returned listener is ready for accepting connections. + pub async fn bind(addr: &str) -> io::Result { + Ok(Self { + inner: crate::sys::net::TcpListener::bind(addr).await?, + }) + } + + /// Returns the local socket address of this listener. + pub fn local_addr(&self) -> io::Result { + self.inner.local_addr() + } + + /// Returns an iterator over the connections being received on this listener. + pub fn incoming(&self) -> Incoming<'_> { + Incoming { + inner: self.inner.incoming(), + } + } +} + +/// An iterator that infinitely accepts connections on a [`TcpListener`]. +#[derive(Debug)] +pub struct Incoming<'a> { + inner: crate::sys::net::Incoming<'a>, +} + +impl<'a> AsyncIterator for Incoming<'a> { + type Item = io::Result; + + async fn next(&mut self) -> Option { + self.inner + .next() + .await + .map(|result| result.map(|inner| TcpStream { inner })) + } +} diff --git a/src/sys/p2/net/tcp_stream.rs b/src/sys/p2/net/tcp_stream.rs index af3674a..5ce66d8 100644 --- a/src/sys/p2/net/tcp_stream.rs +++ b/src/sys/p2/net/tcp_stream.rs @@ -1,5 +1,4 @@ -use std::io::ErrorKind; -use std::net::{SocketAddr, ToSocketAddrs}; +use std::net::SocketAddr; use wasip2::sockets::instance_network::instance_network; use wasip2::sockets::network::Ipv4SocketAddress; use wasip2::sockets::tcp::{IpAddressFamily, IpSocketAddress}; @@ -29,29 +28,6 @@ impl TcpStream { } } - /// Opens a TCP connection to a remote host. - /// - /// `addr` is an address of the remote host. Anything which implements the - /// [`ToSocketAddrs`] trait can be supplied as the address. If `addr` - /// yields multiple addresses, connect will be attempted with each of the - /// addresses until a connection is successful. If none of the addresses - /// result in a successful connection, the error returned from the last - /// connection attempt (the last address) is returned. - pub async fn connect(addr: impl ToSocketAddrs) -> io::Result { - let addrs = addr.to_socket_addrs()?; - let mut last_err = None; - for addr in addrs { - match TcpStream::connect_addr(addr).await { - Ok(stream) => return Ok(stream), - Err(e) => last_err = Some(e), - } - } - - Err(last_err.unwrap_or_else(|| { - io::Error::new(ErrorKind::InvalidInput, "could not resolve to any address") - })) - } - /// Establishes a connection to the specified `addr`. pub async fn connect_addr(addr: SocketAddr) -> io::Result { let family = match addr { From 1200707435ebfb7d048b0f23ea8a0480d011078e Mon Sep 17 00:00:00 2001 From: Yosh Date: Fri, 3 Jul 2026 16:28:47 +0200 Subject: [PATCH 9/9] Target CI at wasm32-wasip2 for the wasip2-only crates Now that `wstd` and `wstd-axum` only compile for `wasm32-wasip2` (the `sys` backend is cfg-gated and other targets hit `compile_error!`), the CI steps that built them for the host target fail to resolve `crate::sys::*`. Scope those steps to the target: - `check` and `Clippy` build `wstd`/`wstd-axum` with `--target wasm32-wasip2 --all-targets` and keep the `test-programs` host harness on the host. - `Docs` builds with `--target wasm32-wasip2`. - `ci/publish.rs` passes `--target wasm32-wasip2` when packaging `wstd` and `wstd-axum`. `--all-targets` newly lints the `#[cfg(test)]` code, so regroup the digit literals in the `time` duration tests to satisfy `inconsistent_digit_grouping`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yaml | 10 +++++++--- ci/publish.rs | 4 ++++ src/time.rs | 18 ++++++++++++------ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1362609..30e5735 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -49,7 +49,9 @@ jobs: role-session-name: github-ci - name: check - run: cargo check --workspace --all --bins --examples + run: | + cargo check -p wstd -p wstd-axum --target wasm32-wasip2 --all-targets + cargo check -p test-programs - name: wstd tests run: cargo test -p wstd -p wstd-axum --target wasm32-wasip2 -- --nocapture @@ -73,10 +75,12 @@ jobs: run: cargo fmt --all -- --check - name: Docs - run: cargo doc + run: cargo doc --target wasm32-wasip2 - name: Clippy - run: cargo clippy --all + run: | + cargo clippy -p wstd -p wstd-axum --target wasm32-wasip2 --all-targets + cargo clippy -p test-programs verify-publish: name: Verify publish diff --git a/ci/publish.rs b/ci/publish.rs index 66867ce..bf9ba79 100644 --- a/ci/publish.rs +++ b/ci/publish.rs @@ -365,6 +365,10 @@ fn verify(crates: &[Crate]) { .arg("--manifest-path") .arg(&krate.manifest) .env("CARGO_TARGET_DIR", "./target"); + // wstd and wstd-axum only compile for wasm32-wasip2 + if krate.name == "wstd" || krate.name == "wstd-axum" { + cmd.arg("--target").arg("wasm32-wasip2"); + } let status = cmd.status().unwrap(); assert!(status.success(), "failed to verify {:?}", &krate.manifest); let tar = Command::new("tar") diff --git a/src/time.rs b/src/time.rs index 043bb03..a60b4da 100644 --- a/src/time.rs +++ b/src/time.rs @@ -361,31 +361,37 @@ mod tests { assert_eq!(Duration::new(456, 864209753).as_nanos(), 456864209753); assert_eq!(Duration::from_secs(9876543210).as_secs(), 9876543210); - assert_eq!(Duration::from_secs(9876543210).as_millis(), 9876543210_000); + assert_eq!( + Duration::from_secs(9876543210).as_millis(), + 9_876_543_210_000 + ); assert_eq!( Duration::from_secs(9876543210).as_micros(), - 9876543210_000000 + 9_876_543_210_000_000 ); assert_eq!( Duration::from_secs(9876543210).as_nanos(), - 9876543210_000000000 + 9_876_543_210_000_000_000 ); assert_eq!(Duration::from_millis(9876543210).as_secs(), 9876543); assert_eq!(Duration::from_millis(9876543210).as_millis(), 9876543210); assert_eq!( Duration::from_millis(9876543210).as_micros(), - 9876543210_000 + 9_876_543_210_000 ); assert_eq!( Duration::from_millis(9876543210).as_nanos(), - 9876543210_000000 + 9_876_543_210_000_000 ); assert_eq!(Duration::from_micros(9876543210).as_secs(), 9876); assert_eq!(Duration::from_micros(9876543210).as_millis(), 9876543); assert_eq!(Duration::from_micros(9876543210).as_micros(), 9876543210); - assert_eq!(Duration::from_micros(9876543210).as_nanos(), 9876543210_000); + assert_eq!( + Duration::from_micros(9876543210).as_nanos(), + 9_876_543_210_000 + ); assert_eq!(Duration::from_nanos(9876543210).as_secs(), 9); assert_eq!(Duration::from_nanos(9876543210).as_millis(), 9876);