Stream: git-wasmtime

Topic: wasmtime / issue #10219 Calling wasi:cli/run#run function...


view this post on Zulip Wasmtime GitHub notifications bot (Feb 11 2025 at 10:44):

justingaffney added the bug label to Issue #10219.

view this post on Zulip Wasmtime GitHub notifications bot (Feb 11 2025 at 10:44):

justingaffney opened issue #10219:

Test Case

Clone example repo here: https://github.com/justingaffney/wasmtime-run-twice-error

Steps to Reproduce

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

view this post on Zulip Wasmtime GitHub notifications bot (Feb 11 2025 at 10:51):

bjorn3 commented on issue #10219:

The wasp1->wasip2 adapter directly calls the _start method of the inner wasip1 module from it's run function: https://github.com/bytecodealliance/wasmtime/blob/73d6d6b513121cc75bc140dfb081bf4c90d654e2/crates/wasi-preview1-component-adapter/src/lib.rs#L117-L126

Wasip1 documents that _start may only be called once: https://github.com/WebAssembly/WASI/blob/main/legacy/application-abi.md#current-unstable-abi

Command 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 the unreachable wasm instruction): https://github.com/WebAssembly/wasi-libc/blob/e9524a0980b9bb6bb92e87a41ed1055bdda5bb86/libc-bottom-half/crt/crt1-command.c#L12-L31

view this post on Zulip Wasmtime GitHub notifications bot (Feb 11 2025 at 11:29):

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?

view this post on Zulip Wasmtime GitHub notifications bot (Feb 11 2025 at 16:00):

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 the main function and should instead use a custom export.

view this post on Zulip Wasmtime GitHub notifications bot (Feb 12 2025 at 04:53):

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 call run 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's wasm32-wasip2 target directly, instead of using cargo-component, and I got the same error with wit-component:adapter:wasi_snapshot_preview1!wasi:cli/run@0.2.0#run in the wasm backtrace. I assumed the new wasm32-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?

view this post on Zulip Wasmtime GitHub notifications bot (Feb 12 2025 at 19:42):

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 exporting wasi: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.

view this post on Zulip Wasmtime GitHub notifications bot (Feb 12 2025 at 19:43):

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 exporting wasi: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.

view this post on Zulip Wasmtime GitHub notifications bot (Feb 12 2025 at 19:44):

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 exporting wasi: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.

view this post on Zulip Wasmtime GitHub notifications bot (Feb 13 2025 at 04:35):

justingaffney closed issue #10219:

Test Case

Clone example repo here: https://github.com/justingaffney/wasmtime-run-twice-error

Steps to Reproduce

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

view this post on Zulip Wasmtime GitHub notifications bot (Feb 13 2025 at 04:35):

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