justingaffney added the bug label to Issue #10219.
justingaffney opened issue #10219:
Test Case
Clone example repo here: https://github.com/justingaffney/wasmtime-run-twice-error
Steps to Reproduce
- Build component
cd guest
cargo component build --release
- Run host
cd ../host
cargo run
Expected Results
Calling run function for the first time Hello, world! Calling run function for the second time Hello, world!
Actual Results
Calling run function for the first time Hello, world! Calling run function for the second time thread 'main' panicked at host\src\main.rs:41:35: called `Result::unwrap()` on an `Err` value: error while executing at wasm backtrace: 0: 0xab2 - <unknown>!<wasm function 5> 1: 0xbca0 - wit-component:adapter:wasi_snapshot_preview1!wasi:cli/run@0.2.0#run Caused by: wasm trap: wasm `unreachable` instruction executed
Versions and Environment
Wasmtime version or commit: 29.0.1
Operating system: Windows 11
Architecture: x86_64
Extra Info
Not sure if calling the
wasi:cli/run@0.2.0#run
function multiple times on the same component instance is supposed to work or not, but it is not clear from the error whether this behaviour is expected or not
bjorn3 commented on issue #10219:
The wasp1->wasip2 adapter directly calls the
_start
method of the inner wasip1 module from it'srun
function: https://github.com/bytecodealliance/wasmtime/blob/73d6d6b513121cc75bc140dfb081bf4c90d654e2/crates/wasi-preview1-component-adapter/src/lib.rs#L117-L126Wasip1 documents that
_start
may only be called once: https://github.com/WebAssembly/WASI/blob/main/legacy/application-abi.md#current-unstable-abiCommand instances may assume that they will be called from the environment at most once. Command instances may assume that none of their exports are accessed outside the duration of that call.
Wasi-libc has an explicit check for this which uses
__builtin_trap()
to abort (lowering to theunreachable
wasm instruction): https://github.com/WebAssembly/wasi-libc/blob/e9524a0980b9bb6bb92e87a41ed1055bdda5bb86/libc-bottom-half/crt/crt1-command.c#L12-L31
justingaffney commented on issue #10219:
Thanks for the fast response! Ok makes sense, I'm not clear though on whether going forward that is also the case for wasip2 components that aren't adapting inner wasip1 modules?
alexcrichton commented on issue #10219:
AFAIK there aren't clear semantics defined for WASIp2 at this time. The
run
export is just like any other export and hosts can certainly call it twice (as you're doing here) but whether or not it works is a question beyond the component model which concerns tooling and conventions. My guess though is that conventionally it'll be the same as WASIp1 where if you want to call something twice you probably shouldn't use themain
function and should instead use a custom export.
justingaffney commented on issue #10219:
Ah ok got it, I updated my example repo to add a library package that implements the
run
export directly, and I can callrun
on that component multiple times.If I am understanding this correctly that is because the inner wasip1 module is now a "reactor" module not a "command" module, so the component adapter isn't calling
_start
anymore which would trap if called again? Although it looks like a similar check is made by wasi-libc for_initialize
to ensure it is not called more than once, does the wasip1->wasip2 adapter not need to call_initialize
?Also I tried creating a component using the
main
function by building using Rust'swasm32-wasip2
target directly, instead of using cargo-component, and I got the same error withwit-component:adapter:wasi_snapshot_preview1!wasi:cli/run@0.2.0#run
in the wasm backtrace. I assumed the newwasm32-wasip2
target would build a more "native" wasip2 component, is it also building a wasip1 inner module and using the wasip1->wasip2 component adapter like cargo-component does?
pchickey commented on issue #10219:
Long story short: this behavior is down to the toolchain that created the component, not wasmtime, and the toolchain may not change the way this works even if internals were updated to purely use p2 and not p1. This is an area that is not fully fleshed out yet.
In wasip1 terms, the inner wasip1 module is still a "command" - its internal structure assumes, and enforces by trapping in this manner, that it is instantiated and then the exported function is run at most once. Internally, there is a module exporting
_start
, and then wit-component wrapped that into a component exportingwasi:cli/run@0.2.0#run
.The wasip2 spec doesn't describe that this export may be called at most once. We never carried over the command/reactor distinction into the wit era. De-facto, almost all cli run exports are implemented such that they will trap on a second call to that export on an instance. Any guest using wasi-libc - used by wasi-libc for C/C++ and rustc for Rust, and perhaps by others as well - ends up with that same check that bjorn3 linked to.
As far as the distinction for "native" wasip2 vs p1 is concerned, the runtime doesn't care what the internal modules of a component are as long as the imports and exports conform to the wasip2 interfaces. It hasn't been anyone's priority to finish up the work in wasi-libc, wasi-sdk and rustc to get rid of the wasip1 use and the use of the p1->p2 adapter to create components. It would be nice for that to happen at some point - it would make binaries smaller, and likely allow for omitting unused import functions and other optimizations - but it hasn't been blocking progress elsewhere and everyone is quite busy, so its been on the backburner for a while.
As far as the toolchains changing its behavior for the cli command world run functions: I don't know what all of the considerations are there off the top of my head, but I expect one of the biggest is that there is no precedent in POSIX for the
main
function being entered more than once in the same process, and it would likely cause a lot of existing C/C++ programs to crash or otherwise misbehave. When you're targeting a "clean slate" WASI world like wasi:http/proxy, its a lot more reasonable to expect that an export be called multiple times on the same instance, but even that is not straightforward. There is still work to do to flesh out this design space in both the specs, and in implementations.
pchickey edited a comment on issue #10219:
Long story short: this behavior is down to the toolchain that created the component, not wasmtime, and the toolchain may not change the way this works even if internals were updated to purely use p2 and not p1. This is an area that is not fully fleshed out yet.
In wasip1 terms, the inner wasip1 module is still a "command" - its internal structure assumes, and enforces by trapping in this manner, that it is instantiated and then the exported function is run at most once. Internally, there is a module exporting
_start
, and then wit-component wrapped that into a component exportingwasi:cli/run@0.2.0#run
.The wasip2 spec doesn't describe that this export may be called at most once. We never carried over the command/reactor distinction into the wit era. De-facto, almost all cli run exports are implemented such that they will trap on a second call to that export on an instance. Any guest using wasi-libc - used by wasi-libc for C/C++ and rustc for Rust, I believe Tinygo, and perhaps by others as well - ends up with that same check that bjorn3 linked to.
As far as the distinction for "native" wasip2 vs p1 is concerned, the runtime doesn't care what the internal modules of a component are as long as the imports and exports conform to the wasip2 interfaces. It hasn't been anyone's priority to finish up the work in wasi-libc, wasi-sdk and rustc to get rid of the wasip1 use and the use of the p1->p2 adapter to create components. It would be nice for that to happen at some point - it would make binaries smaller, and likely allow for omitting unused import functions and other optimizations - but it hasn't been blocking progress elsewhere and everyone is quite busy, so its been on the backburner for a while.
As far as the toolchains changing its behavior for the cli command world run functions: I don't know what all of the considerations are there off the top of my head, but I expect one of the biggest is that there is no precedent in POSIX for the
main
function being entered more than once in the same process, and it would likely cause a lot of existing C/C++ programs to crash or otherwise misbehave. When you're targeting a "clean slate" WASI world like wasi:http/proxy, its a lot more reasonable to expect that an export be called multiple times on the same instance, but even that is not straightforward. There is still work to do to flesh out this design space in both the specs, and in implementations.
pchickey edited a comment on issue #10219:
Long story short: this behavior is down to the toolchain that created the component, not wasmtime, and the toolchain may not change the way this works even if internals were updated to purely use p2 and not p1. This is an area that is not fully fleshed out yet.
In wasip1 terms, the inner wasip1 module is still a "command" - its internal structure assumes, and enforces by trapping in this manner, that it is instantiated and then the exported function is run at most once. Internally, there is a module exporting
_start
, and then wit-component wrapped that into a component exportingwasi:cli/run@0.2.0#run
.The wasip2 spec doesn't describe that this export may be called at most once. We never carried over the command/reactor distinction into the wit era. De-facto, almost all cli run exports are implemented such that they will trap on a second call to that export on an instance. Any guest using wasi-libc - used by wasi-libc for C/C++, rustc for Rust, I believe Tinygo, and perhaps by others as well - ends up with that same check that bjorn3 linked to.
As far as the distinction for "native" wasip2 vs p1 is concerned, the runtime doesn't care what the internal modules of a component are as long as the imports and exports conform to the wasip2 interfaces. It hasn't been anyone's priority to finish up the work in wasi-libc, wasi-sdk and rustc to get rid of the wasip1 use and the use of the p1->p2 adapter to create components. It would be nice for that to happen at some point - it would make binaries smaller, and likely allow for omitting unused import functions and other optimizations - but it hasn't been blocking progress elsewhere and everyone is quite busy, so its been on the backburner for a while.
As far as the toolchains changing its behavior for the cli command world run functions: I don't know what all of the considerations are there off the top of my head, but I expect one of the biggest is that there is no precedent in POSIX for the
main
function being entered more than once in the same process, and it would likely cause a lot of existing C/C++ programs to crash or otherwise misbehave. When you're targeting a "clean slate" WASI world like wasi:http/proxy, its a lot more reasonable to expect that an export be called multiple times on the same instance, but even that is not straightforward. There is still work to do to flesh out this design space in both the specs, and in implementations.
justingaffney closed issue #10219:
Test Case
Clone example repo here: https://github.com/justingaffney/wasmtime-run-twice-error
Steps to Reproduce
- Build component
cd guest
cargo component build --release
- Run host
cd ../host
cargo run
Expected Results
Calling run function for the first time Hello, world! Calling run function for the second time Hello, world!
Actual Results
Calling run function for the first time Hello, world! Calling run function for the second time thread 'main' panicked at host\src\main.rs:41:35: called `Result::unwrap()` on an `Err` value: error while executing at wasm backtrace: 0: 0xab2 - <unknown>!<wasm function 5> 1: 0xbca0 - wit-component:adapter:wasi_snapshot_preview1!wasi:cli/run@0.2.0#run Caused by: wasm trap: wasm `unreachable` instruction executed
Versions and Environment
Wasmtime version or commit: 29.0.1
Operating system: Windows 11
Architecture: x86_64
Extra Info
Not sure if calling the
wasi:cli/run@0.2.0#run
function multiple times on the same component instance is supposed to work or not, but it is not clear from the error whether this behaviour is expected or not
justingaffney commented on issue #10219:
Thanks for explaining the relationship between wasip1 modules and wasip2 components, I was a bit confused about how the command/reactor concept fit into the component model since I couldn't find much about it in wasip2 or component model documentation. I don't have any issue with not being able to call the
run
function more than once but I wasn't sure if that behaviour was expected or not, as you've all explained it really depends on the component itself and the toolchain that built it.That issue you linked is very helpful, I am trying to understand component instance reusability and the kinds of assumptions a host can make about reusing an instance, hence why I created this issue. Something like the reuse hint that was mentioned sounds like a good way for a host to generically know whether a component could have its instance reused or not.
Thanks for all the information!
Last updated: Feb 28 2025 at 02:27 UTC