You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Crash when wasmtime invokes specific function #2099
wasmtime run ./build/untouched.wasm works fine without crashing. (If I add some top level statements and wasmtime run then these are executed correctly.) It's only when I pass the --invoke option that things go wrong.
The same problem occurs if I embed wasmtime and call its API to invoke a function other than _start.
The same problem occurs if I use ./build/optimized.wasm, or the .wat build output.
The problem does not occur with AssemblyScript 0.17.2/as-wasi 0.4.3. With these versions wasmtime run --invoke foo runs correctly.
Please let me know if you need any more info - thanks!
Just an initial guess: Is it possible that _start isn't called before other functions are invoked? Calling _start is important to set up all the runtime internals, and not doing so can lead to issues like these.
From the Wasmtime side I think that's correct. --invoke just calls the given exported function directly as far as I know. (I'm not a Wasmtime expert though.)
If that's the case, is this a fix on the Wasmtime side or the AssemblyScript side? As I mentioned it used to work okay...
I see that in the command line arguments --explicitStart is not specified. Perhaps specifying it fixes the problem, as it will force a _start function and not generate an implicit (start (function ...)), which may not work? With it, I guess invoking _start first and other exports after should do the right thing.
Looking at the linked issue, with recent versions of the compiler it should be unnecessary to specify --use abort=wasi_abort. Is injected automatically upon import "wasi";. Also, recent versions have console integration built into the standard library, and one doesn't necessarily need as-wasi for it.
Thanks for the tips. Unfortunately it doesn't seem to help:
$ wasmtime run --invoke foo ./build/optimized.wasm
abort: in ~lib/rt/itcms.ts(159:16)
Error: failed to run main module `./build/optimized.wasm`
Caused by:
0: failed to invoke `foo`
1: exit with invalid exit status outside of [0..126)
wasm backtrace:
0: 0x3dd - <unknown>!<wasm function 3>
1: 0x41f - <unknown>!<wasm function 4>
2: 0x9d2 - <unknown>!<wasm function 10>
3: 0xcb6 - <unknown>!<wasm function 12>
4: 0x11b1 - <unknown>!<wasm function 15>
$ wasmtime run --invoke _start --invoke foo ./build/optimized.wasm
error: The argument '--invoke <FUNCTION>' was provided more than once, but cannot be used multiple times
USAGE:
wasmtime run <MODULE> --invoke <FUNCTION>
(By the way, it looks like I still need as-wasi or Console doesn't resolve, but that's by the by.) Duh, I missed that I needed to change it to console. Mea culpa.
Whoa! So when I removed as-wasi and made the change to use console instead of Console, the simple code worked with or without --explicitStart!
Unfortunately, when I added a line that did a string operation (Math.random().toString()) instead of just literals, it failed again. So not yet a solution in practice, but progress...!
If this is the untouched build, the only assert than can be hit is when visitRoots visits pinned objects, which I guess there shouldn't be any. That would indicate perhaps that memory got corrupted, but hard to tell how that would even happen unless Wasmtime mutated the memory, which it shouldn't.
wasmtime run --invoke _start --invoke foo ./build/optimized.wasm
Are you sure you could use multiply invoke commands? For WASI you definitely should insert --invoke _start according wasmtime cli docs. But I'm not sure you could also call --invoke foo at the same time.
You are right, seems one cannot --invoke multiple functions, and it appears that Wasmtime doesn't invoke the implicit start function (when not compiling with --explicitStart) when using the --invoke argument. Seems one cannot do that...?
For reference, I tried the same with the Wasmer CLI, which also doesn't work. As soon as --invoke is specified, the WASI imports are not present. Makes me assume that both the Wasmtime and Wasmer CLIs are meant to run WASI executables only, but to do more with it one needs an embedding that supports, say, calling other exports as well.
commands: These are like executables and need _start invoked one time. Once it returns you are no longer allowed to call into it ever again.
reactors: These are like libraries and need _initialize invoked one time. Once it returns you are allowed to call any exported function as many times as you want. (subject to restrictions defined by the specific reactor you are running)
If assemblyscript wants to allow invoking functions other than _start, it will need to create wasi reactors AFAIK and thus provide an _initialize function instead of a _start function. It is not valid to provide both.
I guess we can alias _start with _initialize and see what happens, though I wonder what will happen in practice if both are present. There isn't really a main function in AS, likely making a distinction not very useful. Top-level code is pretty much initialize code, with one depending on the other in AS.
This does seem like a tricky one - it sounds like WASI is making a distinction between two types of program which it's not obvious how to distinguish in AssemblyScript.
We have also assumed that top-level statements do not run when we invoke foo directly, only when we invoke _start. This was how older versions of AssemblyScript behaved. I worry that aliasing _initialize and _start would change that behaviour. (And for our real use case we really do need to be able to invoke a function without causing the top level code to run.)
On top of that, other languages seem not to care if something is a command or reactor: if we have a WASM module built from Rust we can invoke _start or foo and it doesn't care, both work fine! This certainly wasn't a nuance of WASI/WASM that I knew about before...!
Invoking an export may require top-level code to have executed already, though, say
vara=1;// but more complexexportfunctionfoo(): i32{returna;}
where top-level code leading to a = 1 can be arbitrarily complex, potentially involving the runtime, memory management, etc.
An alternative could be to deprecate _start and only use _initialize, where a user can export a custom _start function that is independent, but then again we would end up with both. Perhaps, if such a custom _start is present, we'd inline what would otherwise go into _initialize there to deduplicate. Not sure, though.
Update/clarification from a colleague who knows more about the WASI ABI than I do. It seems like the response from Wasmtime (quoted above) was slightly wrong.
If a module exports both _start and _initialize then that's an error and Wasmtime will reject the module.
If a module exports _start but not_initialize then it is a command but you can still invoke its exports and the ABI doc does not say that _start is called in this case (and indeed Wasmtime doesn't). You can only call one export on each instance/each call gets a new instance - not quite sure of the nuance here but the impact is it doesn't retain state from call to call.
Otherwise it's a reactor and Wasmtime automatically calls _initialize if present. But then you can call multiple exports on the same instance (and it retains module state from call to call).
(forgive me if this is all familiar ground to you folks, but I had copied a comment that implied something different, and wanted to clarify for anyone coming on this issue later)
Took at stab at this in #2157, replacing the --explicitStart option with a more general --exportStart NAME option. Additionally, when import "wasi" is used, but the option is omitted, the compiler tries to infer from whether there are custom function exports if the module is likely a reactor (has some) or a command (has none). Specifying the option overrides what the compiler would infer, so --exportStart _initialize makes it a reactor.
Activity
dcodeIO commentedon Oct 18, 2021
Just an initial guess: Is it possible that
_startisn't called before other functions are invoked? Calling_startis important to set up all the runtime internals, and not doing so can lead to issues like these.itowlson commentedon Oct 18, 2021
From the Wasmtime side I think that's correct.
--invokejust calls the given exported function directly as far as I know. (I'm not a Wasmtime expert though.)If that's the case, is this a fix on the Wasmtime side or the AssemblyScript side? As I mentioned it used to work okay...
dcodeIO commentedon Oct 18, 2021
I see that in the command line arguments
--explicitStartis not specified. Perhaps specifying it fixes the problem, as it will force a_startfunction and not generate an implicit(start (function ...)), which may not work? With it, I guess invoking_startfirst and other exports after should do the right thing.dcodeIO commentedon Oct 18, 2021
Looking at the linked issue, with recent versions of the compiler it should be unnecessary to specify
--use abort=wasi_abort. Is injected automatically uponimport "wasi";. Also, recent versions haveconsoleintegration built into the standard library, and one doesn't necessarily needas-wasifor it.The Wasmtime command could be (with or without invoking _start, but haven't checked):
itowlson commentedon Oct 18, 2021
Thanks for the tips. Unfortunately it doesn't seem to help:
(By the way, it looks like I still needDuh, I missed that I needed to change it toas-wasiorConsoledoesn't resolve, but that's by the by.)console. Mea culpa.itowlson commentedon Oct 18, 2021
It does change something though: I don't get the cycling in the concat/visitRoots/abort loop any more:
itowlson commentedon Oct 18, 2021
Whoa! So when I removed
as-wasiand made the change to useconsoleinstead ofConsole, the simple code worked with or without--explicitStart!Unfortunately, when I added a line that did a string operation (
Math.random().toString()) instead of just literals, it failed again. So not yet a solution in practice, but progress...!dcodeIO commentedon Oct 18, 2021
Math.randomitself calls a WASI API to seed the RNG, perhaps there's something up with that. Some permission necessary maybe?itowlson commentedon Oct 18, 2021
It's the
toStringcall that fails, and again feels like it's failing in the allocator/GC:dcodeIO commentedon Oct 18, 2021
If this is the untouched build, the only
assertthan can be hit is whenvisitRootsvisits pinned objects, which I guess there shouldn't be any. That would indicate perhaps that memory got corrupted, but hard to tell how that would even happen unless Wasmtime mutated the memory, which it shouldn't.itowlson commentedon Oct 18, 2021
Yeah that was the untouched build (
asc assembly/index.ts --target debug --explicitStart)MaxGraey commentedon Oct 19, 2021
Are you sure you could use multiply invoke commands? For WASI you definitely should insert
--invoke _startaccording wasmtime cli docs. But I'm not sure you could also call--invoke fooat the same time.dcodeIO commentedon Oct 19, 2021
You are right, seems one cannot
--invokemultiple functions, and it appears that Wasmtime doesn't invoke the implicit start function (when not compiling with--explicitStart) when using the--invokeargument. Seems one cannot do that...?For reference, I tried the same with the Wasmer CLI, which also doesn't work. As soon as
--invokeis specified, the WASI imports are not present. Makes me assume that both the Wasmtime and Wasmer CLIs are meant to run WASI executables only, but to do more with it one needs an embedding that supports, say, calling other exports as well.dcodeIO commentedon Oct 19, 2021
Opened an issue at Wasmer. If there is interest in Wasmtime support as well, feel free to file an issue there as well.
itowlson commentedon Oct 27, 2021
Response from the Wasmtime folks (bytecodealliance/wasmtime#3474 (comment)):
dcodeIO commentedon Oct 27, 2021
I guess we can alias
_startwith_initializeand see what happens, though I wonder what will happen in practice if both are present. There isn't really amainfunction in AS, likely making a distinction not very useful. Top-level code is pretty much initialize code, with one depending on the other in AS.itowlson commentedon Oct 27, 2021
This does seem like a tricky one - it sounds like WASI is making a distinction between two types of program which it's not obvious how to distinguish in AssemblyScript.
We have also assumed that top-level statements do not run when we invoke
foodirectly, only when we invoke_start. This was how older versions of AssemblyScript behaved. I worry that aliasing_initializeand_startwould change that behaviour. (And for our real use case we really do need to be able to invoke a function without causing the top level code to run.)On top of that, other languages seem not to care if something is a command or reactor: if we have a WASM module built from Rust we can invoke
_startorfooand it doesn't care, both work fine! This certainly wasn't a nuance of WASI/WASM that I knew about before...!dcodeIO commentedon Oct 27, 2021
Invoking an export may require top-level code to have executed already, though, say
where top-level code leading to
a = 1can be arbitrarily complex, potentially involving the runtime, memory management, etc.An alternative could be to deprecate
_startand only use_initialize, where a user can export a custom_startfunction that is independent, but then again we would end up with both. Perhaps, if such a custom_startis present, we'd inline what would otherwise go into_initializethere to deduplicate. Not sure, though.itowlson commentedon Oct 27, 2021
Update/clarification from a colleague who knows more about the WASI ABI than I do. It seems like the response from Wasmtime (quoted above) was slightly wrong.
_startand_initializethen that's an error and Wasmtime will reject the module._startbut not_initializethen it is a command but you can still invoke its exports and the ABI doc does not say that_startis called in this case (and indeed Wasmtime doesn't). You can only call one export on each instance/each call gets a new instance - not quite sure of the nuance here but the impact is it doesn't retain state from call to call._initializeif present. But then you can call multiple exports on the same instance (and it retains module state from call to call).https://github.com/bytecodealliance/wasmtime/blob/49133f62e69e43b01562141abfe9a0f1f957ab86/crates/wasmtime/src/linker.rs#L1252
(forgive me if this is all familiar ground to you folks, but I had copied a comment that implied something different, and wanted to clarify for anyone coming on this issue later)
dcodeIO commentedon Dec 13, 2021
Took at stab at this in #2157, replacing the
--explicitStartoption with a more general--exportStart NAMEoption. Additionally, whenimport "wasi"is used, but the option is omitted, the compiler tries to infer from whether there are custom function exports if the module is likely a reactor (has some) or a command (has none). Specifying the option overrides what the compiler would infer, so--exportStart _initializemakes it a reactor.