JavaScript can access a wasm memory, using
var buffer = wasmMemory.buffer;
var view8 = new Int8Array(buffer);
view8[100] = 1; // write
console.log(view8[200]); // read
someAPI(view8.subarray(120, 130)); // make a view
Emscripten uses this extensively to allow convenient mixing of JS and wasm. We have both JS code of our own as well as code written by users.
Wasm memories can grow. When they do so, the wasmMemory.buffer
changes to a new buffer, and any existing views
(like view8
from before) remain valid but do not change length. That means that accessing the new area just grown
is not possible. That is,
function func() {
var ptr = callSomething(); // say that this grows memory
return view8[ptr]; // if ptr is in the newly grown area, we fail
}
(Here "fail" means that we get undefined
, since we read an invalid index in a typed array.) Note that a
potentially common case is if callSomething
does a malloc
, does some writes, and returns that pointer - then
if that malloc
grew memory, we would not be able to read it, unless we did something like this:
function func() {
var ptr = callSomething();
view8 = new Int8Array(wasmMemory.buffer); // create a new view
return view8[ptr];
}
Creating a new view for every memory access would be significant overhead. We might reduce it by updating the view only in places where it might change - after calls and atomic operations - which might help some inner loops, but this may be hard to get right, in particular for user code.
Another option is to call into wasm for every memory operation, that is, replace view8[ptr]
with instance.load8_s(ptr)
where load8_s
is a tiny function exported from the module:
(func "load8_s" (param $ptr i32)
(i32.load8_s (local.get $ptr))
)
Calling into and out of wasm for each memory operation would also introduce signficant overhead, though.
The idea of calling into and out of wasm inspires the question, "what if we had an API for that?" That is, what if wasm Memories
had methods like wasmMemory.load8_s
. That would be identical in behavior to a wasm module exporting a function with such a
load, as we just considered, that is, it would let JS say "do a normal load from this wasm memory", where normal includes
all the semantics of wasm memory operations - trap on out of bounds, automatic handling of memory growth, etc.
The proposed methods on WebAssembly.Memory
objects might be:
load8_s(ptr)
: do a signed 8-bit load, at locationptr
load8_u
,load16_s
, etc.
store8(ptr, value)
: do an 8 bit store ofvalue
to locationptr
store16
,store32
, etc.
view8_s(start, end)
: create an 8-bit signed view from[start, end)
view8_u
,view16_s
, etc.
Notes:
load8_s
etc. is consistent with wasm text; on the other hand the names could be more consistent with JS typed array names if we didloadInt8
etc.- The
view
methods return normal typed array views - we don't need them to be sensitive to later memory growth events. - These could easily be polyfilled as discussed earlier (but the polyfill would be quite slow - we probably wouldn't recommend it to users except for testing).