Skip to content

Commit 3baed27

Browse files
committed
std: Implement WASIp2-specific stdio routines
This commit is an extension of previous libstd support but applied to stdio specifically. The stdio routines are updated away from WASIp1 APIs to using WASIp2 APIs natively. The end goal is to eventually drop the dependency on WASIp1 APIs in the standard library entirely in favor of exclusively depending on WASIp2.
1 parent 9385c64 commit 3baed27

File tree

3 files changed

+126
-3
lines changed

3 files changed

+126
-3
lines changed

library/std/src/sys/stdio/mod.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ cfg_select! {
2929
mod uefi;
3030
pub use uefi::*;
3131
}
32-
target_os = "wasi" => {
33-
mod wasi;
34-
pub use wasi::*;
32+
all(target_os = "wasi", target_env = "p1") => {
33+
mod wasip1;
34+
pub use wasip1::*;
35+
}
36+
all(target_os = "wasi", target_env = "p2") => {
37+
mod wasip2;
38+
pub use wasip2::*;
3539
}
3640
target_os = "xous" => {
3741
mod xous;

library/std/src/sys/stdio/wasip2.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use crate::io::{self, BorrowedBuf, BorrowedCursor};
2+
use wasip2::cli;
3+
use wasip2::io::streams::{Error, InputStream, OutputStream, StreamError};
4+
5+
pub struct Stdin(Option<InputStream>);
6+
pub struct Stdout(Option<OutputStream>);
7+
pub struct Stderr(Option<OutputStream>);
8+
9+
fn error_to_io(err: Error) -> io::Error {
10+
// There exists a function in `wasi:filesystem` to optionally acquire an
11+
// error code from an error, but the streams in use in this module are
12+
// exclusively used with stdio meaning that a filesystem error is not
13+
// possible here.
14+
//
15+
// In lieu of an error code, which WASIp2 does not specify, this instead
16+
// carries along the `to_debug_string` implementation that the host
17+
// supplies. If this becomes too expensive in the future this could also
18+
// become `io::Error::from_raw_os_error(libc::EIO)` or similar.
19+
io::Error::new(io::ErrorKind::Other, err.to_debug_string())
20+
}
21+
22+
impl Stdin {
23+
pub const fn new() -> Stdin {
24+
Stdin(None)
25+
}
26+
27+
fn stream(&mut self) -> &InputStream {
28+
self.0.get_or_insert_with(cli::stdin::get_stdin)
29+
}
30+
}
31+
32+
impl io::Read for Stdin {
33+
fn read(&mut self, data: &mut [u8]) -> io::Result<usize> {
34+
let mut buf = BorrowedBuf::from(data);
35+
self.read_buf(buf.unfilled())?;
36+
Ok(buf.len())
37+
}
38+
39+
fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> io::Result<()> {
40+
match self.stream().blocking_read(u64::try_from(buf.capacity()).unwrap()) {
41+
Ok(result) => {
42+
buf.append(&result);
43+
Ok(())
44+
}
45+
Err(StreamError::Closed) => Ok(()),
46+
Err(StreamError::LastOperationFailed(e)) => Err(error_to_io(e)),
47+
}
48+
}
49+
}
50+
51+
impl Stdout {
52+
pub const fn new() -> Stdout {
53+
Stdout(None)
54+
}
55+
56+
fn stream(&mut self) -> &OutputStream {
57+
self.0.get_or_insert_with(cli::stdout::get_stdout)
58+
}
59+
}
60+
61+
fn write(stream: &OutputStream, buf: &[u8]) -> io::Result<usize> {
62+
// WASIp2's `blocking_write_and_flush` function is defined as accepting no
63+
// more than 4096 bytes. Larger writes can be issued by manually using
64+
// `check_write`, `write`, and `blocking_flush`, but for now just go ahead
65+
// and use `blocking_write_and_flush` and report a short write and let a
66+
// higher level loop over the result.
67+
const MAX: usize = 4096;
68+
let buf = &buf[..buf.len().min(MAX)];
69+
match stream.blocking_write_and_flush(buf) {
70+
Ok(()) => Ok(buf.len()),
71+
Err(StreamError::Closed) => Ok(0),
72+
Err(StreamError::LastOperationFailed(e)) => Err(error_to_io(e)),
73+
}
74+
}
75+
76+
impl io::Write for Stdout {
77+
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
78+
write(self.stream(), data)
79+
}
80+
81+
fn flush(&mut self) -> io::Result<()> {
82+
// Note that `OutputStream` has a `flush` function but for stdio all
83+
// writes are accompanied with a flush which means that this flush
84+
// doesn't need to do anything.
85+
Ok(())
86+
}
87+
}
88+
89+
impl Stderr {
90+
pub const fn new() -> Stderr {
91+
Stderr(None)
92+
}
93+
94+
fn stream(&mut self) -> &OutputStream {
95+
self.0.get_or_insert_with(cli::stderr::get_stderr)
96+
}
97+
}
98+
99+
impl io::Write for Stderr {
100+
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
101+
write(self.stream(), data)
102+
}
103+
104+
fn flush(&mut self) -> io::Result<()> {
105+
// See `Stdout::flush` for why this is a noop.
106+
Ok(())
107+
}
108+
}
109+
110+
pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE;
111+
112+
pub fn is_ebadf(_err: &io::Error) -> bool {
113+
// WASIp2 stdio streams are always available so ebadf never shows up.
114+
false
115+
}
116+
117+
pub fn panic_output() -> Option<impl io::Write> {
118+
Some(Stderr::new())
119+
}

0 commit comments

Comments
 (0)