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 viewEmscripten 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 locationptrload8_u,load16_s, etc.
store8(ptr, value): do an 8 bit store ofvalueto locationptrstore16,store32, etc.
view8_s(start, end): create an 8-bit signed view from[start, end)view8_u,view16_s, etc.
Notes:
load8_setc. is consistent with wasm text; on the other hand the names could be more consistent with JS typed array names if we didloadInt8etc.- The
viewmethods 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).