From e22524fab8dc8773de5f0bbc2610f08a28d3f614 Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Thu, 4 Oct 2018 13:16:39 +0200 Subject: [PATCH] Update loader to support environments where 'new WebAssembly.Instance' is limited, see #295 --- lib/loader/index.js | 152 +++++++++++++++++++++--------------- lib/loader/tests/index.html | 8 ++ 2 files changed, 97 insertions(+), 63 deletions(-) create mode 100644 lib/loader/tests/index.html diff --git a/lib/loader/index.js b/lib/loader/index.js index 67584d98..b82d26bf 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -2,45 +2,69 @@ const hasBigInt64 = typeof BigUint64Array !== "undefined"; -/** Instantiates an AssemblyScript module using the specified imports. */ -function instantiate(module, imports) { +/** Gets a string from an U32 and an U16 view on a memory. */ +function getStringImpl(U32, U16, ptr) { + var dataLength = U32[ptr >>> 2]; + var dataOffset = (ptr + 4) >>> 1; + var dataRemain = dataLength; + var parts = []; + const chunkSize = 1024; + while (dataRemain > chunkSize) { + let last = U16[dataOffset + chunkSize - 1]; + let size = last >= 0xD800 && last < 0xDC00 ? chunkSize - 1 : chunkSize; + let part = U16.subarray(dataOffset, dataOffset += size); + parts.push(String.fromCharCode.apply(String, part)); + dataRemain -= size; + } + return parts.join("") + String.fromCharCode.apply(String, U16.subarray(dataOffset, dataOffset + dataRemain)); +} - // Set up the imports object - if (!imports) imports = {}; +/** Prepares the base module prior to instantiation. */ +function preInstantiate(imports) { + var baseModule = {}; + + // add the internal abort function that is called when an assertion fails or an error is thrown if (!imports.env) imports.env = {}; - if (!imports.env.abort) imports.env.abort = function(mesg, file, line, colm) { - mesg = mem ? getString(mesg) : ""; - file = mem ? getString(file) : ""; - throw Error("abort: " + mesg + " at " + file + ":" + line + ":" + colm); + if (!imports.env.abort) imports.env.abort = function abort(mesg, file, line, colm) { + var memory = baseModule.memory || this.memory; // prefer exported, otherwise try imported + function getString(memory, ptr) { + if (!memory) return ""; + var buffer = memory.buffer; + return getStringImpl(new Uint32Array(buffer), new Uint16Array(buffer), ptr); + } + throw Error("abort: " + getString(memory, mesg) + " at " + getString(memory, file) + ":" + line + ":" + colm); } - // Instantiate the module and obtain its (flat) exports - var instance = new WebAssembly.Instance(module, imports); - var exports = instance.exports; - var memory_allocate = exports["memory.allocate"]; - var memory_fill = exports["memory.fill"]; - var memory_free = exports["memory.free"]; + return baseModule; +} + +/** Prepares the final module once instantiation is complete. */ +function postInstantiate(baseModule, instance) { + var memory = instance.exports.memory; + var memory_allocate = instance.exports["memory.allocate"]; + var memory_fill = instance.exports["memory.fill"]; + var memory_free = instance.exports["memory.free"]; // Provide views for all sorts of basic values - var mem, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64; + var buffer, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64; /** Updates memory views if memory has grown meanwhile. */ function checkMem() { // see: https://github.com/WebAssembly/design/issues/1210 - if (mem !== exports.memory.buffer) { - mem = exports.memory.buffer; - I8 = new Int8Array(mem); - U8 = new Uint8Array(mem); - I16 = new Int16Array(mem); - U16 = new Uint16Array(mem); - I32 = new Int32Array(mem); - U32 = new Uint32Array(mem); + if (buffer !== memory.buffer) { + buffer = memory.buffer; + I8 = new Int8Array(buffer); + U8 = new Uint8Array(buffer); + I16 = new Int16Array(buffer); + U16 = new Uint16Array(buffer); + I32 = new Int32Array(buffer); + U32 = new Uint32Array(buffer); if (hasBigInt64) { - I64 = new BigInt64Array(mem); - U64 = new BigUint64Array(mem); + I64 = new BigInt64Array(buffer); + U64 = new BigUint64Array(buffer); } - F32 = new Float32Array(mem); - F64 = new Float64Array(mem); + F32 = new Float32Array(buffer); + F64 = new Float64Array(buffer); } } checkMem(); @@ -56,24 +80,16 @@ function instantiate(module, imports) { return ptr; } + baseModule.newString = newString; + /** Gets a string from the module's memory by its pointer. */ function getString(ptr) { checkMem(); - var dataLength = U32[ptr >>> 2]; - var dataOffset = (ptr + 4) >>> 1; - var dataRemain = dataLength; - var parts = []; - const chunkSize = 1024; - while (dataRemain > chunkSize) { - let last = U16[dataOffset + chunkSize - 1]; - let size = last >= 0xD800 && last < 0xDC00 ? chunkSize - 1 : chunkSize; - let part = U16.subarray(dataOffset, dataOffset += size); - parts.push(String.fromCharCode.apply(String, part)); - dataRemain -= size; - } - return parts.join("") + String.fromCharCode.apply(String, U16.subarray(dataOffset, dataOffset + dataRemain)); + return getStringImpl(U32, U16, ptr); } + baseModule.getString = getString; + function computeBufferSize(byteLength) { const HEADER_SIZE = 8; return 1 << (32 - Math.clz32(byteLength + HEADER_SIZE - 1)); @@ -100,7 +116,7 @@ function instantiate(module, imports) { U32[ buf >>> 2] = byteLength; // .byteLength U32[(buf + 4) >>> 2] = 0; // 0 if (view) { - new ctor(mem, buf + 8, length).set(view); + new ctor(buffer, buf + 8, length).set(view); if (view.length < length && !unsafe) { let setLength = elementSize * view.length; memory_fill(buf + 8 + setLength, 0, byteLength - setLength); @@ -111,6 +127,8 @@ function instantiate(module, imports) { return ptr; } + baseModule.newArray = newArray; + /** Gets a view on a typed array in the module's memory by its pointer. */ function getArray(ctor, ptr) { var elementSize = ctor.BYTES_PER_ELEMENT; @@ -119,9 +137,11 @@ function instantiate(module, imports) { var buf = U32[ ptr >>> 2]; var byteOffset = U32[(ptr + 4) >>> 2]; var byteLength = U32[(ptr + 8) >>> 2]; - return new ctor(mem, buf + 8 + byteOffset, (byteLength - byteOffset) / elementSize); + return new ctor(buffer, buf + 8 + byteOffset, (byteLength - byteOffset) / elementSize); } + baseModule.getArray = getArray; + /** Frees a typed array in the module's memory. Must not be accessed anymore afterwards. */ function freeArray(ptr) { checkMem(); @@ -130,40 +150,46 @@ function instantiate(module, imports) { memory_free(ptr); } + baseModule.freeArray = freeArray; + // Demangle exports and provide the usual utility on the prototype - return demangle(exports, { - get I8() { checkMem(); return I8; }, - get U8() { checkMem(); return U8; }, - get I16() { checkMem(); return I16; }, - get U16() { checkMem(); return U16; }, - get I32() { checkMem(); return I32; }, - get U32() { checkMem(); return U32; }, - get I64() { checkMem(); return I64; }, - get U64() { checkMem(); return U64; }, - get F32() { checkMem(); return F32; }, - get F64() { checkMem(); return F64; }, - newString, - getString, - newArray, - getArray, - freeArray - }); + return demangle(instance.exports, Object.defineProperties(baseModule, { + I8: { get: function() { checkMem(); return I8; } }, + U8: { get: function() { checkMem(); return U8; } }, + I16: { get: function() { checkMem(); return I16; } }, + U16: { get: function() { checkMem(); return U16; } }, + I32: { get: function() { checkMem(); return I32; } }, + U32: { get: function() { checkMem(); return U32; } }, + I64: { get: function() { checkMem(); return I64; } }, + U64: { get: function() { checkMem(); return U64; } }, + F32: { get: function() { checkMem(); return F32; } }, + F64: { get: function() { checkMem(); return F64; } } + })); +} + +/** Instantiates an AssemblyScript module using the specified imports. */ +function instantiate(module, imports) { + return postInstantiate( + preInstantiate(imports || (imports = {})), + new WebAssembly.Instance(module, imports) + ); } exports.instantiate = instantiate; /** Instantiates an AssemblyScript module from a buffer using the specified imports. */ function instantiateBuffer(buffer, imports) { - var module = new WebAssembly.Module(buffer); - return instantiate(module, imports); + return instantiate(new WebAssembly.Module(buffer), imports); } exports.instantiateBuffer = instantiateBuffer; /** Instantiates an AssemblyScript module from a response using the specified imports. */ async function instantiateStreaming(response, imports) { - var module = await WebAssembly.compileStreaming(response); - return instantiate(module, imports); + return postInstantiate( + preInstantiate(imports || (imports = {})), + await WebAssembly.instantiateStreaming(response, imports) + ); } exports.instantiateStreaming = instantiateStreaming; diff --git a/lib/loader/tests/index.html b/lib/loader/tests/index.html new file mode 100644 index 00000000..323ccd22 --- /dev/null +++ b/lib/loader/tests/index.html @@ -0,0 +1,8 @@ + + +