Stream: wasi

Topic: wasmtime pwritev() implementation


view this post on Zulip Tim Chevalier (Aug 04 2025 at 17:44):

Hi,

It looks like wasmtime's implementation of pwritev(), in both preview1 and the preview1 component adapter, only writes one of the vectors even when more than one non-empty vector is provided:

https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-preview1-component-adapter/src/lib.rs#L1201 (there's even a comment saying "Skip leading non-empty buffers", though not why)

and

https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi/src/preview1.rs#L580

As far as I know, the semantics of pwritev() is that all the buffers should be written to the file.

Is this a bug or deliberate?

Thanks,
Tim

A lightweight WebAssembly runtime that is fast, secure, and standards-compliant - bytecodealliance/wasmtime
A lightweight WebAssembly runtime that is fast, secure, and standards-compliant - bytecodealliance/wasmtime

view this post on Zulip Dave Bakker (badeend) (Aug 04 2025 at 17:53):

As far as I know, the semantics of pwritev() is that all the buffers should be written to the file.

I don't think that's right. AFAIK, writes are always allowed to return less than requested.

view this post on Zulip Dave Bakker (badeend) (Aug 04 2025 at 17:55):

The man pages don't describe any such requirement either.

view this post on Zulip Tim Chevalier (Aug 04 2025 at 18:06):

@Dave Bakker (badeend) You're right -- but it still seems strange to me for the implementation to _always_ return less than requested.

view this post on Zulip Dave Bakker (badeend) (Aug 04 2025 at 18:16):

The adapter currently translates 1 "syscall" from the guest into 1 syscall on the host, and doesn't attempt to abstract over the mismatch.

Maybe atomicity might play a role here. If the adapter were to issue writes in a loop, the whole pwritev call from the guest's POV wouldn't be atomic anymore.
Linux' docs mention:

the data written by writev() is written as a single block that is not intermingled with output from writes in other processes

But can't say for sure if this was the actual reason nor if atomicity is even a requirement for WASI. I wasn't involved in this code.

view this post on Zulip Pat Hickey (Aug 05 2025 at 19:26):

im searching for anywhere we wrote down reasoning for that behavior

view this post on Zulip Pat Hickey (Aug 05 2025 at 19:27):

i found https://github.com/bytecodealliance/wasmtime/issues/7830 which affirms it is the way it is because the manpage permits it

Test Case TODO: upload Wasm file here Steps to Reproduce https://github.com/python/cpython/blob/07236f5b39a2e534cf190cd4f7c73300d209520b/Lib/test/test_posix.py#L520-L525 https://github.com/python/c...

view this post on Zulip Pat Hickey (Aug 05 2025 at 19:28):

I can imagine that atomicity was the reason, but I don't know if that reason is well enough motivated that we should keep the behavior or not

view this post on Zulip Pat Hickey (Aug 05 2025 at 19:28):

I suspect @Dan Gohman is the one who remembers best

view this post on Zulip Tim Chevalier (Aug 05 2025 at 19:31):

It would make sense if atomicity was the reason. The only way I can think of to implement the operations without underlying support in wasip2 would be to copy all the buffers into a single buffer -- but then, that probably wouldn't be what you want if you're concerned about performance enough to use these operations.

view this post on Zulip Pat Hickey (Aug 05 2025 at 19:39):

making a copy of all the buffers into a single contiguous buffer is basically just off the table, too expensive

view this post on Zulip Pat Hickey (Aug 05 2025 at 19:40):

so it really comes down to, do we just write the first buffer, or do we attempt to iterate through all of them

view this post on Zulip Alex Crichton (Aug 05 2025 at 20:04):

There's more discussion as well at https://github.com/bytecodealliance/wasmtime/issues/8037 and https://github.com/bytecodealliance/wasmtime/pull/8072. Personally I've also been one advocating for "it's ok to pick just one buffer" for the reason of atomicity.

This clearly is a common papercut though that hits folks though. My assumption is that most folks are doing small "hello world"-style things where it's unreasonable to expect a "real" implementation which would check the return value and loop over the result (e.g. writing raw *.wat). In such a context it might be reasonable to see that if multiple vectors were passed and the total size is less than some small threshold (e.g. 512 bytes) we could copy it to the host and do the write

Am I crazy? I thought I might be misunderstanding something, but then I found someone else's example online that also doesn't work. Test Case ;; hello.wat (module ;; define the expected type for fd...
I noticed that the implementation of fd_write in the preview1 adapter only writes the first iov in the list passed. This change writes all of them. I'm happy to add a test for this, though I co...

Last updated: Dec 06 2025 at 06:05 UTC