I am creating a WebAssembly instance and trying to provide my own WebAssembly.Memory
, but it always ignores the value I provide and creates its own memory instance instead.
According to MDN, the syntax should be:
WebAssembly.instantiateStreaming(fetch('memory.wasm'), { js: { mem: memory } })
However, when I do the same:
const memory = new WebAssembly.Memory({ initial: 1, maximum: 1 });
const { instance } = await WebAssembly.instantiateStreaming(
fetch('main.wasm'),
{ js: { mem: memory } },
);
console.log('did use same memory?', instance.exports.memory.buffer === memory.buffer);
I see the answer is false
(and the size of the memory it uses is 16777216 bytes rather than 65536, i.e. 256 pages instead of 1). This happens in both Chrome and FireFox, so does not seem to be an implementation issue.
This does not appear to be connected to anything in the main.wasm
file, but as a test I have reduced it to the following minimal source (compiled with emscripten):
#include <emscripten/emscripten.h>
static int counter = 1;
EMSCRIPTEN_KEEPALIVE int inc() {
counter++;
return counter;
}
emcc -O3 main.c -o main.wasm --no-entry
(you can see the full MCVE here: GitHub / live site)
I have tried various variations (such as {js: {memory}}
, {env: {memory}}
, {memory}
, etc.) but it always seems to behave as if I did not set any memory configuration. What do I need to do to make it use my memory config?
Reading deeper into the WebAssembly documentation reveals that there are 2 ways of specifying memory; in the WASM, or from Javascript.
Using wasm2wat
to inspect the emscripten-generated WASM:
(module
[...]
(memory (;0;) 256 256)
(global (;0;) (mut i32) (i32.const 5243920))
(export "memory" (memory 0))
[...]
This is the WASM-created-memory option (and the 256 page min/max matches what I saw), which means it will ignore any passed-in memory object.
To use the passed in memory, the generated WASM must be different:
(import "js" "mem" (memory 1))
(which also explains where the { js: { mem: memory } }
which most examples quote comes from)
Emscripten can be configured using -s IMPORTED_MEMORY
on the emcc
command, which causes it to instead generate:
(import "env" "memory" (memory (;0;) 256 256))
(i.e. it is now possible to pass in a memory object as { env: { memory } }
; not an exact match for the common examples but the same idea)
However it is still requiring that the memory is a minimum (and maximum) of 256 pages. This can be configured with INITIAL_MEMORY
(and if set small, will also need TOTAL_STACK
to change as well):
emcc main.c -o main.wasm --no-entry -s IMPORTED_MEMORY -s INITIAL_MEMORY=64kB -s TOTAL_STACK=16kB
Generates:
(import "env" "memory" (memory (;0;) 1 1))
But the easier setup is probably to use the WASM-generated memory option with the explicit sizes compiled directly, rather than trying to configure it at runtime (i.e. not using -s IMPORTED_MEMORY
):
emcc main.c -o main.wasm --no-entry -s INITIAL_MEMORY=64kB -s TOTAL_STACK=16kB
As an extra, if needed: to have the maximum larger than the minimum, -s ALLOW_MEMORY_GROWTH
must be set (which includes a library for growing the memory at runtime). With this, -s MAXIMUM_MEMORY=??
can be used to set a specific maximum).
None of this seems to be especially well documented, but I pieced it together from various sources: github issue, changelog, module docs
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With