Skip to content
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

fd_write only prints first element of the iovec #629

Closed
nomeata opened this issue Nov 25, 2019 · 9 comments · Fixed by #781
Closed

fd_write only prints first element of the iovec #629

nomeata opened this issue Nov 25, 2019 · 9 comments · Fixed by #781

Comments

@nomeata
Copy link

nomeata commented Nov 25, 2019

Consider this Wasm module:

(module
  (type (;0;) (func))
  (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
  (memory (;0;) 2)
  (export "memory" (memory 0))
  (export "start" (func $start))
  (func $start (type 0)
    i32.const 1 ;; stdout
    i32.const 0 ;; iovec ptr
    i32.const 2 ;; entries
    i32.const 24 ;; out bytes
    call $fd_write
    drop
  )
  (data (i32.const 0) "\10\00\00\00\02\00\00\00\12\00\00\00\02\00\00\00ABCD")
)

if I run this as

wat2wasm fd_write_test.wat && wasmtime --disable-cache --invoke=start -d fd_write_test.wasm

I get

AB

not

ABCD

It seems thta fd_write only takes the first element of the iovec.

If I set the third argument to 0 it prints nothing (as it should).

@sunfishcode
Copy link
Member

I haven't investigated this yet, but just a quick note that that's not necessarily a bug; fd_write is permitted to write fewer bytes than requested. The nwritten output tells you how many bytes were actually written.

Also, wasmtime can read .wat files directly, so you can skip the wat2wasm step :-).

@nomeata
Copy link
Author

nomeata commented Nov 25, 2019

I haven't investigated this yet, but just a quick note that that's not necessarily a bug; fd_write is permitted to write fewer bytes than requested. The nwritten output tells you how many bytes were actually written.

Oh, good point. Indeed, you are right, it reports nwritten=2 in that case. I guess I wasn’t expecting it to not being able to print four bytes in one go :-)

Feel free to close this if there is nothing actionable here now.

@sunfishcode
Copy link
Member

This is at least a QOI issue, because clearly it would be better to write the whole output when it isn't impractical to do so. This might be related to our use of Rust's write_vectored function, which sometimes only writes the first buffer. If so, we may want to call the underlying OS APIs directly instead.

@kubkon
Copy link
Member

kubkon commented Nov 28, 2019

Hmm, I think that writing our own wrapper for this shouldn't be too difficult (in fact, I think we already had some code that called the underlying OS APIs directly in the past). I'd hate to see the generality of our current solution across different hosts go though; i.e., Rust's libstd yields itself quite nicely here. On that topic, I wonder whether anything could be done to write_vectored itself on the libstd side? @alexcrichton what do you think?

@alexcrichton
Copy link
Member

I agree yeah that this is probably a bug in Rust's libstd not implementing write_vectored somewhere, I believe it's missing here which is what stdout will delegate to. WASI calls into libstd around here

In that sense I don't think this is a correctness bug, but Rust's libstd should work harder to consume all the input buffers regardless, and so this'd likely be best fixed by patching Rust's libstd implementation.

alexcrichton added a commit to alexcrichton/rust that referenced this issue Dec 13, 2019
This commit implements the `write_vectored` method of the `LineWriter`
type. First discovered in bytecodealliance/wasmtime#629 the
`write_vectored` method of `Stdout` bottoms out here but only ends up
writing the first buffer due to the default implementation of
`write_vectored`.

Like `BufWriter`, however, `LineWriter` can have a non-default
implementation of `write_vectored` which tries to preserve the
vectored-ness as much as possible. Namely we can have a vectored write
for everything before the newline and everything after the newline if
all the stars align well.

Also like `BufWriter`, though, special care is taken to ensure that
whenever bytes are written we're sure to signal success since that
represents a "commit" of writing bytes.
Centril added a commit to Centril/rust that referenced this issue Dec 19, 2019
…, r=sfackler

std: Implement `LineWriter::write_vectored`

This commit implements the `write_vectored` method of the `LineWriter`
type. First discovered in bytecodealliance/wasmtime#629 the
`write_vectored` method of `Stdout` bottoms out here but only ends up
writing the first buffer due to the default implementation of
`write_vectored`.

Like `BufWriter`, however, `LineWriter` can have a non-default
implementation of `write_vectored` which tries to preserve the
vectored-ness as much as possible. Namely we can have a vectored write
for everything before the newline and everything after the newline if
all the stars align well.

Also like `BufWriter`, though, special care is taken to ensure that
whenever bytes are written we're sure to signal success since that
represents a "commit" of writing bytes.
@alexcrichton
Copy link
Member

This has been updated in rust-lang/rust at rust-lang/rust#67270 to print out everything by default, but in the meantime this still doesn't actually quite work naively at the console due to this block where SandboxedTTYWriter doesn't implement the same write_vectored logic and the default implementation only tries to write the first buffer.

@sunfishcode do you think it's worth trying to change this behavior? Given that it's all along this time been conforming to the function's contract, I'd be tempted to close this.

@nomeata
Copy link
Author

nomeata commented Jan 7, 2020

Maybe hair-splitting here, but what happens if there is more than one buffer, but the first one is empty? Is it according to the function’s contract to only look at the first buffer (and thus always return “zero bytes written”)? (I guess https://github.com/bytecodealliance/wasmtime/blob/master/docs/WASI-api.md#fd_write only says that its similar to writev, and man 3 writev does not forbit the behaviour – but it also doesn’t forbit just always returning 0).

@sunfishcode
Copy link
Member

This is admittedly a gray area, but my current understanding comes from text in POSIX discussing atomicity which says "It is a known attribute of terminals that this is not honored, and terminals are explicitly (and implicitly permanently) excepted". Since SandboxedTTYWriter only writes to terminals, it doesn't need to be atomic, so it could implement write_vectored in a straightforward way.

@sunfishcode
Copy link
Member

I've now submitted #781 which fixes this, adding write_vectored to SandboxedTTYWriter. It is tempting to say that people should just always be prepared to call write in a loop if they actually want their whole buffer written, because at some level that is what POSIX etc. expects users to do. And WASI libraries do do this. But write_vectored isn't that hard to do in this case, so it feels nice to save the extra calls, and system call trace output, in the common case.

kubkon pushed a commit to sunfishcode/wasmtime that referenced this issue Jan 15, 2020
kubkon pushed a commit that referenced this issue Jan 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants