Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
42d7884
add p3 feature
adamrk Sep 1, 2026
4a6e65c
add p3 test CI job
adamrk Sep 2, 2026
ea32edf
use cfg alias
adamrk Sep 3, 2026
0487755
Require `&mut` for Streams
adamrk Sep 2, 2026
443ef72
remove &self stream methods
adamrk Sep 2, 2026
cabda1f
Add `wstd::main` and `wstd::test` macros for WASIp3
adamrk Sep 2, 2026
adb35fc
switch from feature to target
adamrk Sep 9, 2026
7e37d58
Merge branch 'main' into abk/p3-feature-base
adamrk Sep 9, 2026
26bea8b
merge fixes
adamrk Sep 9, 2026
6435d57
add job printing rust version
adamrk Sep 9, 2026
663c532
try override instead of default
adamrk Sep 9, 2026
d306e06
explicitly add wasi targets
adamrk Sep 9, 2026
2f8c1c2
bump msrv to latest version with p3 target_env
adamrk Sep 9, 2026
5841233
include target_os in directives
adamrk Sep 9, 2026
d880934
Merge branch 'abk/p3-feature-base' into abk/shared-ref-changes
adamrk Sep 9, 2026
47b889b
Merge branch 'abk/shared-ref-changes' into abk/main-macro-for-wasip3
adamrk Sep 9, 2026
114abd0
fixes from merge
adamrk Sep 9, 2026
9d58000
use wit_bindgen::block_on in macros
adamrk Sep 9, 2026
14e1bce
remove build-std
adamrk Sep 9, 2026
2adec58
remove dead code
adamrk Sep 9, 2026
6150b98
no main for non-wasi targets
adamrk Sep 9, 2026
d8994a6
Merge branch 'abk/p3-feature-base' into abk/shared-ref-changes
adamrk Sep 9, 2026
2c7a618
Merge branch 'abk/shared-ref-changes' into abk/main-macro-for-wasip3
adamrk Sep 9, 2026
64f20f5
change block_on static requirement
adamrk Sep 9, 2026
c3a377c
cleanup Cargo.tomls after feature removal
adamrk Sep 10, 2026
fb62b45
cleanup Cargo.tomls after feature removal
adamrk Sep 10, 2026
d3826e3
Merge branch 'abk/p3-feature-base' into abk/shared-ref-changes
adamrk Sep 10, 2026
d4fd717
Merge branch 'abk/shared-ref-changes' into abk/main-macro-for-wasip3
adamrk Sep 10, 2026
fdb266c
keep http macro only in p2
adamrk Sep 10, 2026
1572e34
Merge branch 'main' into abk/shared-ref-changes
adamrk Sep 11, 2026
059432a
Merge branch 'abk/shared-ref-changes' into abk/main-macro-for-wasip3
adamrk Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions examples/macro_main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![cfg_attr(not(target_os = "wasi"), no_main)]
#![cfg(target_os = "wasi")]

//! Verifies that the `main` macro is compiling.

#[wstd::main]
async fn main() {
println!("Hello world");
}
6 changes: 4 additions & 2 deletions examples/tcp_echo_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use wstd::net::TcpListener;

#[wstd::main]
async fn main() -> io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
let mut listener = TcpListener::bind("127.0.0.1:8080").await?;
println!("Listening on {}", listener.local_addr()?);
println!("type `nc localhost 8080` to create a TCP client");

Expand All @@ -17,7 +17,9 @@ async fn main() -> io::Result<()> {
println!("Accepted from: {}", stream.peer_addr()?);
wstd::runtime::spawn(async move {
// If echo copy fails, we can ignore it.
let _ = io::copy(&stream, &stream).await;
let mut stream = stream;
let (mut read_half, mut write_half) = stream.split();
let _ = io::copy(&mut read_half, &mut write_half).await;
})
.detach();
}
Expand Down
12 changes: 6 additions & 6 deletions src/http/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::http::{
error::Context as _,
fields::{header_map_from_wasi, header_map_to_wasi},
};
use crate::io::{AsyncInputStream, AsyncOutputStream};
use crate::io::{AsyncInputStream, AsyncOutputStream, AsyncWrite};
use crate::runtime::{AsyncPollable, Reactor, WaitFor};

pub use ::http_body::{Body as HttpBody, Frame, SizeHint};
Expand Down Expand Up @@ -77,7 +77,7 @@ impl Body {
match self.0 {
BodyInner::Incoming(incoming) => incoming.send(outgoing_body).await,
BodyInner::Boxed(box_body) => {
let out_stream = AsyncOutputStream::new(
let mut out_stream = AsyncOutputStream::new(
outgoing_body
.write()
.expect("outgoing body already written"),
Expand Down Expand Up @@ -108,7 +108,7 @@ impl Body {
}
}
BodyInner::Complete { data, trailers } => {
let out_stream = AsyncOutputStream::new(
let mut out_stream = AsyncOutputStream::new(
outgoing_body
.write()
.expect("outgoing body already written"),
Expand Down Expand Up @@ -348,14 +348,14 @@ impl Incoming {
}
async fn send(self, outgoing_body: WasiOutgoingBody) -> Result<(), Error> {
let in_body = self.body;
let in_stream =
let mut in_stream =
AsyncInputStream::new(in_body.stream().expect("incoming body already read"));
let out_stream = AsyncOutputStream::new(
let mut out_stream = AsyncOutputStream::new(
outgoing_body
.write()
.expect("outgoing body already written"),
);
in_stream.copy_to(&out_stream).await.map_err(|e| {
in_stream.copy_to(&mut out_stream).await.map_err(|e| {
Error::from(e).context("copying incoming body stream to outgoing body stream")
})?;
drop(in_stream);
Expand Down
4 changes: 2 additions & 2 deletions src/io/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub trait AsyncRead {
// If the `AsyncRead` implementation is an unbuffered wrapper around an
// `AsyncInputStream`, some I/O operations can be more efficient.
#[inline]
fn as_async_input_stream(&self) -> Option<&io::AsyncInputStream> {
fn as_async_input_stream(&mut self) -> Option<&mut io::AsyncInputStream> {
None
}
}
Expand All @@ -45,7 +45,7 @@ impl<R: AsyncRead + ?Sized> AsyncRead for &mut R {
}

#[inline]
fn as_async_input_stream(&self) -> Option<&io::AsyncInputStream> {
fn as_async_input_stream(&mut self) -> Option<&mut io::AsyncInputStream> {
(**self).as_async_input_stream()
}
}
8 changes: 4 additions & 4 deletions src/io/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ impl AsyncRead for Stdin {
}

#[inline]
fn as_async_input_stream(&self) -> Option<&AsyncInputStream> {
Some(&self.stream)
fn as_async_input_stream(&mut self) -> Option<&mut AsyncInputStream> {
Some(&mut self.stream)
}
}

Expand Down Expand Up @@ -93,7 +93,7 @@ impl AsyncWrite for Stdout {
}

#[inline]
fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> {
fn as_async_output_stream(&mut self) -> Option<&mut AsyncOutputStream> {
self.stream.as_async_output_stream()
}
}
Expand Down Expand Up @@ -143,7 +143,7 @@ impl AsyncWrite for Stderr {
}

#[inline]
fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> {
fn as_async_output_stream(&mut self) -> Option<&mut AsyncOutputStream> {
self.stream.as_async_output_stream()
}
}
Expand Down
118 changes: 37 additions & 81 deletions src/io/streams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl AsyncInputStream {
stream,
}
}
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<()> {
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<()> {
// Lazily initialize the AsyncPollable
let subscription = self
.subscription
Expand All @@ -43,40 +43,13 @@ impl AsyncInputStream {
}
}
/// Await for read readiness.
async fn ready(&self) {
async fn ready(&mut self) {
poll_fn(|cx| self.poll_ready(cx)).await
}
/// Asynchronously read from the input stream.
/// This method is the same as [`AsyncRead::read`], but doesn't require a `&mut self`.
pub async fn read(&self, buf: &mut [u8]) -> std::io::Result<usize> {
let read = loop {
self.ready().await;
// Ideally, the ABI would be able to read directly into buf.
// However, with the default generated bindings, it returns a
// newly allocated vec, which we need to copy into buf.
match self.stream.read(buf.len() as u64) {
// A read of 0 bytes from WASI's `read` doesn't mean
// end-of-stream as it does in Rust. However, `self.ready()`
// cannot guarantee that at least one byte is ready for
// reading, so in this case we try again.
Ok(r) if r.is_empty() => continue,
Ok(r) => break r,
// 0 bytes from Rust's `read` means end-of-stream.
Err(StreamError::Closed) => return Ok(0),
Err(StreamError::LastOperationFailed(err)) => {
return Err(std::io::Error::other(err.to_debug_string()));
}
}
};
let len = read.len();
buf[0..len].copy_from_slice(&read);
Ok(len)
}

/// Move the entire contents of an input stream directly into an output
/// stream, until the input stream has closed. This operation is optimized
/// to avoid copying stream contents into and out of memory.
pub async fn copy_to(&self, writer: &AsyncOutputStream) -> std::io::Result<u64> {
pub async fn copy_to(&mut self, writer: &mut AsyncOutputStream) -> std::io::Result<u64> {
let mut written = 0;
loop {
self.ready().await;
Expand Down Expand Up @@ -124,11 +97,32 @@ impl AsyncInputStream {

impl AsyncRead for AsyncInputStream {
async fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
Self::read(self, buf).await
let read = loop {
self.ready().await;
// Ideally, the ABI would be able to read directly into buf.
// However, with the default generated bindings, it returns a
// newly allocated vec, which we need to copy into buf.
match self.stream.read(buf.len() as u64) {
// A read of 0 bytes from WASI's `read` doesn't mean
// end-of-stream as it does in Rust. However, `self.ready()`
// cannot guarantee that at least one byte is ready for
// reading, so in this case we try again.
Ok(r) if r.is_empty() => continue,
Ok(r) => break r,
// 0 bytes from Rust's `read` means end-of-stream.
Err(StreamError::Closed) => return Ok(0),
Err(StreamError::LastOperationFailed(err)) => {
return Err(std::io::Error::other(err.to_debug_string()));
}
}
};
let len = read.len();
buf[0..len].copy_from_slice(&read);
Ok(len)
}

#[inline]
fn as_async_input_stream(&self) -> Option<&AsyncInputStream> {
fn as_async_input_stream(&mut self) -> Option<&mut AsyncInputStream> {
Some(self)
}
}
Expand All @@ -150,9 +144,10 @@ impl AsyncInputChunkStream {
impl futures_lite::stream::Stream for AsyncInputChunkStream {
type Item = Result<Vec<u8>, std::io::Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.stream.poll_ready(cx) {
let this = self.get_mut();
match this.stream.poll_ready(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(()) => match self.stream.stream.read(self.chunk_size as u64) {
Poll::Ready(()) => match this.stream.stream.read(this.chunk_size as u64) {
Ok(r) if r.is_empty() => Poll::Pending,
Ok(r) => Poll::Ready(Some(Ok(r))),
Err(StreamError::LastOperationFailed(err)) => {
Expand Down Expand Up @@ -233,23 +228,19 @@ impl AsyncOutputStream {
}
}
/// Await write readiness.
async fn ready(&self) {
async fn ready(&mut self) {
// Lazily initialize the AsyncPollable
let subscription = self
.subscription
.get_or_init(|| AsyncPollable::new(self.stream.subscribe()));
// Wait on readiness
subscription.wait_for().await;
}
/// Asynchronously write to the output stream. This method is the same as
/// [`AsyncWrite::write`], but doesn't require a `&mut self`.
///
/// Awaits for write readiness, and then performs at most one write to the
/// output stream. Returns how much of the argument `buf` was written, or
/// a `std::io::Error` indicating either an error returned by the stream write
/// using the debug string provided by the WASI error, or else that the,
/// indicated by `std::io::ErrorKind::ConnectionReset`.
pub async fn write(&self, buf: &[u8]) -> std::io::Result<usize> {
}

impl AsyncWrite for AsyncOutputStream {
// Required methods
async fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
// Loops at most twice.
loop {
match self.stream.check_write() {
Expand Down Expand Up @@ -279,32 +270,7 @@ impl AsyncOutputStream {
}
}
}

/// Asynchronously write to the output stream. This method is the same as
/// [`AsyncWrite::write_all`], but doesn't require a `&mut self`.
pub async fn write_all(&self, buf: &[u8]) -> std::io::Result<()> {
let mut to_write = &buf[0..];
loop {
let bytes_written = self.write(to_write).await?;
to_write = &to_write[bytes_written..];
if to_write.is_empty() {
return Ok(());
}
}
}

/// Asyncronously flush the output stream. Initiates a flush, and then
/// awaits until the flush is complete and the output stream is ready for
/// writing again.
///
/// This method is the same as [`AsyncWrite::flush`], but doesn't require
/// a `&mut self`.
///
/// Fails with a `std::io::Error` indicating either an error returned by
/// the stream flush, using the debug string provided by the WASI error,
/// or else that the stream is closed, indicated by
/// `std::io::ErrorKind::ConnectionReset`.
pub async fn flush(&self) -> std::io::Result<()> {
async fn flush(&mut self) -> std::io::Result<()> {
match self.stream.flush() {
Ok(()) => {
self.ready().await;
Expand All @@ -318,19 +284,9 @@ impl AsyncOutputStream {
}
}
}
}

impl AsyncWrite for AsyncOutputStream {
// Required methods
async fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Self::write(self, buf).await
}
async fn flush(&mut self) -> std::io::Result<()> {
Self::flush(self).await
}

#[inline]
fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> {
fn as_async_output_stream(&mut self) -> Option<&mut AsyncOutputStream> {
Some(self)
}
}
4 changes: 2 additions & 2 deletions src/io/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub trait AsyncWrite {
// If the `AsyncWrite` implementation is an unbuffered wrapper around an
// `AsyncOutputStream`, some I/O operations can be more efficient.
#[inline]
fn as_async_output_stream(&self) -> Option<&io::AsyncOutputStream> {
fn as_async_output_stream(&mut self) -> Option<&mut io::AsyncOutputStream> {
None
}
}
Expand All @@ -42,7 +42,7 @@ impl<W: AsyncWrite + ?Sized> AsyncWrite for &mut W {
}

#[inline]
fn as_async_output_stream(&self) -> Option<&io::AsyncOutputStream> {
fn as_async_output_stream(&mut self) -> Option<&mut io::AsyncOutputStream> {
(**self).as_async_output_stream()
}
}
16 changes: 13 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,25 @@ pub mod net;
pub mod rand;
#[cfg(all(target_os = "wasi", target_env = "p2"))]
pub mod runtime;
#[cfg(all(target_os = "wasi", target_env = "p3"))]
pub mod runtime {
pub fn block_on<F, T>(fut: F) -> F::Output
where
F: Future<Output = T>,
T: 'static,
{
wasip3::wit_bindgen::block_on(fut)
}
}
#[cfg(all(target_os = "wasi", target_env = "p2"))]
pub mod task;
#[cfg(all(target_os = "wasi", target_env = "p2"))]
pub mod time;

#[cfg(all(target_os = "wasi", target_env = "p2"))]
pub use wstd_macro::{
attr_macro_http_server as http_server, attr_macro_main as main, attr_macro_test as test,
};
pub use wstd_macro::attr_macro_http_server as http_server;

pub use wstd_macro::{attr_macro_main as main, attr_macro_test as test};

// Re-export the active WASI backend crate for use only by `wstd-macro` macros.
// The proc macros need to generate code that uses these definitions, but we
Expand Down
4 changes: 2 additions & 2 deletions src/net/tcp_listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ impl TcpListener {
}

/// Returns an iterator over the connections being received on this listener.
pub fn incoming(&self) -> Incoming<'_> {
pub fn incoming(&mut self) -> Incoming<'_> {
Incoming { listener: self }
}
}

/// An iterator that infinitely accepts connections on a TcpListener.
#[derive(Debug)]
pub struct Incoming<'a> {
listener: &'a TcpListener,
listener: &'a mut TcpListener,
}

impl<'a> AsyncIterator for Incoming<'a> {
Expand Down
Loading
Loading