Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WebAssembly.instantiateStreaming ignores importObject's js.mem

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?

like image 437
Dave Avatar asked Oct 15 '25 03:10

Dave


1 Answers

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

like image 116
Dave Avatar answered Oct 17 '25 16:10

Dave