-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat(wasip3): implement wasi:cli
#11257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(wasip3): implement wasi:cli
#11257
Conversation
| pub trait IsTerminal { | ||
| /// Returns whether this stream is backed by a TTY. | ||
| fn is_terminal(&self) -> bool; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to use std::io::IsTerminal instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My guess is "no" given the litany of implementations we have, but in such a case could documentation be added that this is basically a mirror of what's in libstd? Also is it worth trying to add a blanket impl<T: std::IsTerminal> wasi::IsTermainal for T impl?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot use the std::io::IsTerminal, because it's sealed: https://github.com/rust-lang/rust/blob/f8f6997469237299c1d60814c7b9828602a1f8e4/library/std/src/io/stdio.rs#L1197
Unfortunately, largely because of that, blanket implementations for T also do not appear to be possible.
For example:
error[E0119]: conflicting implementations of trait `cli::IsTerminal` for type `tokio::io::Stderr`
--> crates/wasi/src/cli.rs:126:1
|
66 | impl<T: std::io::IsTerminal> IsTerminal for T {
| --------------------------------------------- first implementation here
...
126 | impl IsTerminal for tokio::io::Stderr {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `tokio::io::Stderr`
|
= note: upstream crates may add a new impl of trait `std::sealed::Sealed` for type `tokio::io::Stderr` in future versions
= note: upstream crates may add a new impl of trait `std::io::IsTerminal` for type `tokio::io::Stderr` in future versions
tokio::io::Stderr cannot implement std::io::IsTerminal, because it's sealed and we cannot implement wasmtime_wasi::cli::IsTerminal for it if we provide a blanket implementation for std::io::IsTerminal
crates/wasi/src/clocks.rs
Outdated
|
|
||
| impl<T: WasiClocksView> WasiClocksView for Box<T> { | ||
| fn clocks(&mut self) -> &WasiClocksCtx { | ||
| (**self).clocks() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW a trick I've used in the past is to use T::clocks(self) in the body which forcibly ensures that recursion doesn't happen and avoids ** syntax as well. Just a style thing though, no meaningful difference
crates/wasi/src/p3/cli/mod.rs
Outdated
| pub struct TerminalInput; | ||
| pub struct TerminalOutput; | ||
|
|
||
| pub trait InputStream: IsTerminal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the InputStream and OutputStream traits directly have a Send supertrait to avoid needing InputStream + Send for example in the builder and such?
crates/wasi/src/p3/cli/mod.rs
Outdated
| pub struct TerminalOutput; | ||
|
|
||
| pub trait InputStream: IsTerminal { | ||
| fn reader(&self) -> Box<dyn AsyncRead + Send + Sync + Unpin>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to drop Unpin since this is already in a box anyway?
crates/wasi/src/cli.rs
Outdated
| type InputStream = T::InputStream; | ||
| type OutputStream = T::OutputStream; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit wary of making the implementation more generic than it already is, so would it be possible to drop these associated types and hardwire the trait objects? Or are these needed to multiplex the p2/p3 implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depends, if these per-proposal "View" traits are adopted by p2 implementation, then the associated types would be used for exactly this purpose. Currently, only the p3 implementation uses these traits.
I'll move this trait to wasmtime_wasi::p3::cli::WasiCliView without the associated types for now and maybe we can revisit once we have all of WASI implemented in this repo.
crates/wasi/src/p3/view.rs
Outdated
|
|
||
| #[doc(hidden)] | ||
| fn as_wasi_impl(&mut self) -> &mut WasiImpl<Self> | ||
| where | ||
| Self: Sized, | ||
| { | ||
| // TODO: Figure out how to avoid `unsafe` | ||
| // SAFETY: `WasiImpl` is `repr(transparent)` | ||
| unsafe { core::mem::transmute(self) } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain a bit more as to why this is necessary?
I forget the matrix of types/traits involved here, but I would naively expect that &mut WasiImpl<Self> is basically equivalent to WasiImpl<&mut Self>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was hoping for that as well, but I've spent roughly 1h actively working on it and roughly an hour more trying things out while in meetings, but I could not figure it out without significant API changes, so I just gave up and went with the obvious solution requiring unsafe. Really, all we need here is to be able to "wrap" the value in the store.
What I'd like to get from this implementation:
- be able to select a proposal to link (
wasi:cliin this case) and:- only implement parts relevant for that proposal (
WasiCliViewinstead ofWasiView) - add symbols to linker using a familiar API (
wasmtime_wasi::p3::cli::add_to_linker) without having to resort to lower-level, generatedwasmtime_wasi::p3::bindings::wasi::cli::*per-interface functions - which is error prone, tedious and introduces additional maintenance burden
- only implement parts relevant for that proposal (
- be able to link all of WASI, i.e. implement
WasiViewand callwasmtime_wasi::p3::add_to_linker
The convention is that we implement interfaces for WasiCliImpl<T>, where T: WasiCliView. The challenge then in the likes of:
wasmtime/crates/wasi/src/p3/clocks/mod.rs
Line 74 in 90ab791
| type Data<'a> = WasiClocksImpl<&'a mut T>; |
We need a WasiCliImpl(&mut T), where T implements WasiCliView, but I don't see a way to acquire such a T, when we start with WasiView. I can't do a |v| &mut WasiImpl(v), since that would create an intermediate reference, which would be immediately dropped. I don't see a way to thread the lifetime through in a way that supports two different traits directly here.
We also currently have a separate trait for getting the resource table and we cannot call both WasiCliView::cli and ResourceView::table inside the closure, since that would make us mutably borrow twice
There are workarounds though:
- Have
WasiCliView::clireturn a tuple(&mut WasiCliCtx, &mut ResourceTable) - Store
ResourceTableinWasiCliCtx
This morning I actually got another idea, which I'm going to try now: WasiCliCtxView<'a>, which would have to &mut borrows, &mut WasiCliCtx and &mut ResourceTable, so WasiCliView::cli would have to return that. A bit awkward to use, but potentially it could work and hopefully it'd only be required in this lower level/advanced use case API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It worked! No WasiCliImpl and no need for generics in the host implementations anymore
c690633 to
e309adb
Compare
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
e309adb to
3775d98
Compare
Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Roman Volosatovs <[email protected]>
|
Semantics of |
alexcrichton
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice I like how the changes worked out, thanks for pushing on it!
Agreed yeah this is ok for now. For anything else along these lines mind opening an issue for that? I'll open an issue for this one specifically |
|
Oh while cargo vet I think was spurious the failure on Windows I think is valid which is this line. IIRC we set a few extra env vars for Windows for WASI testing |
prtest:full Signed-off-by: Roman Volosatovs <[email protected]>
* feat(wasi): introduce common CLI context Signed-off-by: Roman Volosatovs <[email protected]> * chore(wasi): implement traits for boxed values Signed-off-by: Roman Volosatovs <[email protected]> * chore(wasi): implement `Default` for common WASI builder Signed-off-by: Roman Volosatovs <[email protected]> * feat(wasip3): implement `wasi:cli` Signed-off-by: Roman Volosatovs <[email protected]> * refactor: require streams to be `Send` Signed-off-by: Roman Volosatovs <[email protected]> * refactor: avoid typing `WasiCli` in task Signed-off-by: Roman Volosatovs <[email protected]> * refactor: remove `Unpin` bound from stream I/O Signed-off-by: Roman Volosatovs <[email protected]> * refactor: remove `ResourceView` Signed-off-by: Roman Volosatovs <[email protected]> * chore: update `serve` to new WASI `isatty` API Signed-off-by: Roman Volosatovs <[email protected]> * chore: adapt to stream API changes Signed-off-by: Roman Volosatovs <[email protected]> * refactor: avoid `**` syntax Signed-off-by: Roman Volosatovs <[email protected]> * refactor(wasip3): remove `Impl` wrappers Signed-off-by: Roman Volosatovs <[email protected]> * refactor(wasip3): use shorthand closure syntax Signed-off-by: Roman Volosatovs <[email protected]> * chore: account for different env on different targets prtest:full Signed-off-by: Roman Volosatovs <[email protected]> --------- Signed-off-by: Roman Volosatovs <[email protected]>
follow-up on #11221, this implements
wasi:cli, mostly by moving and cleaning up the implementation from https://github.com/bytecodealliance/wasip3-prototyping