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
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.
The man pages don't describe any such requirement either.
@Dave Bakker (badeend) You're right -- but it still seems strange to me for the implementation to _always_ return less than requested.
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.
im searching for anywhere we wrote down reasoning for that behavior
i found https://github.com/bytecodealliance/wasmtime/issues/7830 which affirms it is the way it is because the manpage permits it
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
I suspect @Dan Gohman is the one who remembers best
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.
making a copy of all the buffers into a single contiguous buffer is basically just off the table, too expensive
so it really comes down to, do we just write the first buffer, or do we attempt to iterate through all of them
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
Last updated: Dec 06 2025 at 06:05 UTC