-
Notifications
You must be signed in to change notification settings - Fork 304
Description
Currently wasm-tools component new
uses a special adapter to convert WASI p1 to p2. The adapter only works if it can take a dedicated space from the main module's memory, either via cabi_realloc
or memory.grow
. For managed languages, such as Go or Python, the language runtime usually doesn't export cabi_realloc
. A direct memory.grow
without properly notifying the GC also causes memory corruption.
To resolve this problem in a language agnostic fashion, there are two options:
- In addition to importing the main module's memory (memory 0), the adapter can define a local memory (memory 1) to store its own state. Then the adapter code needs to know when to access memory 0 or memory 1. This can be achieved by annotating the Rust code block, for example,
if iovs_len == 0 {
user_mem!({*nwritten = 0;});
return ERRNO_SUCCESS;
}
The user_mem!
macro inserts special wasm instructions at the beginning and the end of the code block, for example, push a magic number and immediately drops it. We then build a wasm rewriting tool to take the adapter module, and redirect all memory access to memory 0 in the user_mem!
region, and access to memory 1 for the rest of the code.
- To avoid multi-memory, we can rewrite the main module to shift all memory addresses by 2 pages. In this way, the first two pages of the memory are never touched by the main module. The adapter can safely write its state and stack to the first two pages without corrupting the user memory. The adapter code still needs to know which pointers come from the main module and which pointers come from the adapter. For the main module pointers, we need to add the offset in order to access the correct location. If the adapter returns any pointers back to the main module, we need to keep the lie by subtracting the offset from the actual address. These can be achieved using macro similar to option 1, for example,
*user_ptr!(nwritten)=0
.
I’ve successfully implemented the address shifting approach in fastly/Viceroy#515, which requires no code change from wit-component
. Except that many code from gc.rs
can now be removed if we use this approach. The multi-memory approach is conceptually simpler, but requires more changes in wit-component
. I got a table index out of bound error when adapting an empty Rust program, and didn’t dig further.
The goal of this issue is to figure out which approach we prefer to be the official fix.
-
The multi-memory approach doesn’t need to rewrite the user module, and we no longer have the restriction of no memory allocation or table in the adapter code. The cons is that it requires more changes in the
wit-component
crate. Having an extra memory can increase serving cost in some cases. -
The address shifting approach doesn’t require code changes in
wit-component
, and doesn’t need multi-memory support. But it needs to rewrite the user module. Not sure how the shifted address may affect the debugging tools.
Another side note is that, in fastly/Viceroy#515, I use walrus
for wasm rewriting. I understand it’s not always in sync with the latest wasm_parser
, so bringing that dependency in the wasm-tools repo can be a concern. On the other hand, I do like the id arena feature in walrus
, where we abstract away all the wasm indices from the end user. This makes wasm transformations extremely easy to write and less error-prone than wasm-encoder
. Curious to know people’s thoughts on using walrus
in the repo or having our own version of id arena. I understand we will lose the parser/encoder roundtrip once we use the arena.