diff --git a/lib/loader/index.d.ts b/lib/loader/index.d.ts index ce2dc2ef3a..3ffc842373 100644 --- a/lib/loader/index.d.ts +++ b/lib/loader/index.d.ts @@ -82,6 +82,9 @@ export interface ASUtil { /** Gets a live view on a Float64Array's values in the module's memory. */ __getFloat64ArrayView(ptr: number): Float64Array; + /** Gets a function from poiner which contain table's index. */ + __getFunction(ptr: number): ((...args: unknown[]) => unknown) | null; + /** Tests whether a managed object is an instance of the class represented by the specified base id. */ __instanceof(ptr: number, baseId: number): boolean; /** Allocates a new string in the module's memory and returns a reference (pointer) to it. */ diff --git a/lib/loader/index.js b/lib/loader/index.js index f0bfca90fa..f61735b0f9 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -34,6 +34,10 @@ const ARRAYBUFFERVIEW_SIZE = 12; const ARRAY_LENGTH_OFFSET = 12; const ARRAY_SIZE = 16; +const E_NO_EXPORT_TABLE = "Operation requires compiling with --exportTable"; +const E_NO_EXPORT_RUNTIME = "Operation requires compiling with --exportRuntime"; +const F_NO_EXPORT_RUNTIME = () => { throw Error(E_NO_EXPORT_RUNTIME); }; + const BIGINT = typeof BigUint64Array !== "undefined"; const THIS = Symbol(); @@ -41,6 +45,11 @@ const STRING_SMALLSIZE = 192; // break-even point in V8 const STRING_CHUNKSIZE = 1024; // mitigate stack overflow const utf16 = new TextDecoder("utf-16le", { fatal: true }); // != wtf16 +/** polyfill for Object.hasOwn */ +Object.hasOwn = Object.hasOwn || function(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +}; + /** Gets a string from memory. */ function getStringImpl(buffer, ptr) { let len = new Uint32Array(buffer)[ptr + SIZE_OFFSET >>> 2] >>> 1; @@ -83,22 +92,17 @@ function preInstantiate(imports) { return extendedExports; } -const E_NOEXPORTRUNTIME = "Operation requires compiling with --exportRuntime"; -const F_NOEXPORTRUNTIME = function() { throw Error(E_NOEXPORTRUNTIME); }; - /** Prepares the final module once instantiation is complete. */ function postInstantiate(extendedExports, instance) { const exports = instance.exports; const memory = exports.memory; const table = exports.table; - const __new = exports.__new || F_NOEXPORTRUNTIME; - const __pin = exports.__pin || F_NOEXPORTRUNTIME; - const __unpin = exports.__unpin || F_NOEXPORTRUNTIME; - const __collect = exports.__collect || F_NOEXPORTRUNTIME; + const __new = exports.__new || F_NO_EXPORT_RUNTIME; + const __pin = exports.__pin || F_NO_EXPORT_RUNTIME; + const __unpin = exports.__unpin || F_NO_EXPORT_RUNTIME; + const __collect = exports.__collect || F_NO_EXPORT_RUNTIME; const __rtti_base = exports.__rtti_base; - const getRttiCount = __rtti_base - ? function (arr) { return arr[__rtti_base >>> 2]; } - : F_NOEXPORTRUNTIME; + const getRttiCount = __rtti_base ? arr => arr[__rtti_base >>> 2] : F_NO_EXPORT_RUNTIME; extendedExports.__new = __new; extendedExports.__pin = __pin; @@ -106,28 +110,26 @@ function postInstantiate(extendedExports, instance) { extendedExports.__collect = __collect; /** Gets the runtime type info for the given id. */ - function getInfo(id) { + function getRttInfo(id) { + const U32 = new Uint32Array(memory.buffer); + if ((id >>>= 0) >= getRttiCount(U32)) throw Error(`invalid id: ${id}`); + return U32[(__rtti_base + 4 >>> 2) + (id << 1)]; + } + + /** Gets the runtime base id for the given id. */ + function getRttBase(id) { const U32 = new Uint32Array(memory.buffer); - const count = getRttiCount(U32); - if ((id >>>= 0) >= count) throw Error(`invalid id: ${id}`); - return U32[(__rtti_base + 4 >>> 2) + id * 2]; + if ((id >>>= 0) >= getRttiCount(U32)) throw Error(`invalid id: ${id}`); + return U32[(__rtti_base + 4 >>> 2) + (id << 1) + 1]; } /** Gets and validate runtime type info for the given id for array like objects */ function getArrayInfo(id) { - const info = getInfo(id); + const info = getRttInfo(id); if (!(info & (ARRAYBUFFERVIEW | ARRAY | STATICARRAY))) throw Error(`not an array: ${id}, flags=${info}`); return info; } - /** Gets the runtime base id for the given id. */ - function getBase(id) { - const U32 = new Uint32Array(memory.buffer); - const count = getRttiCount(U32); - if ((id >>>= 0) >= count) throw Error(`invalid id: ${id}`); - return U32[(__rtti_base + 4 >>> 2) + id * 2 + 1]; - } - /** Gets the runtime alignment of a collection's values. */ function getValueAlign(info) { return 31 - Math.clz32((info >>> VAL_ALIGN_OFFSET) & 31); // -1 if none @@ -263,6 +265,15 @@ function postInstantiate(extendedExports, instance) { extendedExports.__getArrayBuffer = __getArrayBuffer; + /** Gets a function from poiner which contain table's index. */ + function __getFunction(ptr) { + if (!table) throw Error(E_NO_EXPORT_TABLE); + const index = new Uint32Array(memory.buffer)[ptr >>> 2]; + return table.get(index); + } + + extendedExports.__getFunction = __getFunction; + /** Copies a typed array's values from the module's memory. */ function getTypedArray(Type, alignLog2, ptr) { return new Type(getTypedArrayView(Type, alignLog2, ptr)); @@ -309,7 +320,7 @@ function postInstantiate(extendedExports, instance) { if (id <= getRttiCount(U32)) { do { if (id == baseId) return true; - id = getBase(id); + id = getRttBase(id); } while (id); } return false; @@ -373,14 +384,13 @@ export function demangle(exports, extendedExports = {}) { const setArgumentsLength = exports["__argumentsLength"] ? length => { exports["__argumentsLength"].value = length; } : exports["__setArgumentsLength"] || exports["__setargc"] || (() => { /* nop */ }); - for (let internalName in exports) { - if (!Object.prototype.hasOwnProperty.call(exports, internalName)) continue; + for (let internalName of Object.keys(exports)) { const elem = exports[internalName]; let parts = internalName.split("."); let curr = extendedExports; while (parts.length > 1) { let part = parts.shift(); - if (!Object.prototype.hasOwnProperty.call(curr, part)) curr[part] = {}; + if (!Object.hasOwn(curr, part)) curr[part] = {}; curr = curr[part]; } let name = parts[0]; @@ -406,7 +416,7 @@ export function demangle(exports, extendedExports = {}) { name = name.substring(hash + 1); curr = curr[className].prototype; if (/^(get|set):/.test(name)) { - if (!Object.prototype.hasOwnProperty.call(curr, name = name.substring(4))) { + if (!Object.hasOwn(curr, name = name.substring(4))) { let getter = exports[internalName.replace("set:", "get:")]; let setter = exports[internalName.replace("get:", "set:")]; Object.defineProperty(curr, name, { @@ -417,7 +427,7 @@ export function demangle(exports, extendedExports = {}) { } } else { if (name === 'constructor') { - (curr[name] = (...args) => { + (curr[name] = function(...args) { setArgumentsLength(args.length); return elem(...args); }).original = elem; @@ -430,7 +440,7 @@ export function demangle(exports, extendedExports = {}) { } } else { if (/^(get|set):/.test(name)) { - if (!Object.prototype.hasOwnProperty.call(curr, name = name.substring(4))) { + if (!Object.hasOwn(curr, name = name.substring(4))) { Object.defineProperty(curr, name, { get: exports[internalName.replace("set:", "get:")], set: exports[internalName.replace("get:", "set:")], diff --git a/lib/loader/package.json b/lib/loader/package.json index 2092906ae5..8b9a69518f 100644 --- a/lib/loader/package.json +++ b/lib/loader/package.json @@ -11,6 +11,9 @@ ], "version": "0.0.0", "author": "Daniel Wirtz ", + "contributors": [ + "MaxGraey " + ], "license": "Apache-2.0", "homepage": "https://assemblyscript.org", "repository": { @@ -30,8 +33,8 @@ }, "scripts": { "asbuild": "npm run asbuild:default && npm run asbuild:legacy", - "asbuild:default": "node ../../bin/asc tests/assembly/index.ts --binaryFile tests/build/default.wasm --exportRuntime", - "asbuild:legacy": "node ../../bin/asc tests/assembly/index.ts --disable mutable-globals --binaryFile tests/build/legacy.wasm --exportRuntime", + "asbuild:default": "node ../../bin/asc tests/assembly/index.ts --binaryFile tests/build/default.wasm --exportRuntime --exportTable", + "asbuild:legacy": "node ../../bin/asc tests/assembly/index.ts --disable mutable-globals --binaryFile tests/build/legacy.wasm --exportRuntime --exportTable", "build": "npx esm2umd loader index.js > umd/index.js", "test": "node tests && node tests/umd" }, diff --git a/lib/loader/tests/assembly/index.ts b/lib/loader/tests/assembly/index.ts index af9801e6af..9f76940b29 100644 --- a/lib/loader/tests/assembly/index.ts +++ b/lib/loader/tests/assembly/index.ts @@ -69,6 +69,10 @@ export function dotrace(num: f64): void { trace("The answer is", 1, num); } +export function getVaraddFunc(): (a: i32, b: i32) => i32 { + return varadd; +} + export const UINT8ARRAY_ID = idof(); export const INT16ARRAY_ID = idof(); export const UINT16ARRAY_ID = idof(); diff --git a/lib/loader/tests/build/default.wasm b/lib/loader/tests/build/default.wasm index cd267fa256..b105c1038d 100644 Binary files a/lib/loader/tests/build/default.wasm and b/lib/loader/tests/build/default.wasm differ diff --git a/lib/loader/tests/build/legacy.wasm b/lib/loader/tests/build/legacy.wasm index cd267fa256..b105c1038d 100644 Binary files a/lib/loader/tests/build/legacy.wasm and b/lib/loader/tests/build/legacy.wasm differ diff --git a/lib/loader/tests/index.js b/lib/loader/tests/index.js index 1f59445aa6..2808a24b0e 100644 --- a/lib/loader/tests/index.js +++ b/lib/loader/tests/index.js @@ -231,6 +231,16 @@ function test(file) { assert.strictEqual(car.isDoorsOpen, 0); assert(typeof +car === "number"); // uses Car.prototype.valueOf to obtain `thisPtr` + // should be able to return a function + { + const addFunc = exports.__getFunction(exports.getVaraddFunc()); + assert(typeof addFunc === "function"); + assert.strictEqual(addFunc(1, 2), 3); + + const invalidFunc = exports.__getFunction(0); + assert(invalidFunc == null); + } + // should be able to use trace exports.dotrace(42); @@ -261,7 +271,10 @@ function test(file) { function testInstantiate(file) { // should be able to instantiate from a buffer (async () => { - const { exports, instance, module } = await loader.instantiate(fs.readFileSync(__dirname + "/build/" + file), {}); + const { exports, instance, module } = await loader.instantiate( + fs.readFileSync(__dirname + "/build/" + file), + {} + ); assert(exports.memory); assert(instance && instance instanceof WebAssembly.Instance); assert(module && module instanceof WebAssembly.Module); @@ -269,8 +282,10 @@ function testInstantiate(file) { // should be able to instantiate from a wasm module (async () => { - const wasmModule = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/" + file)); - const { exports, instance, module } = await loader.instantiate(wasmModule, {}); + const { exports, instance, module } = await loader.instantiate( + new WebAssembly.Module(fs.readFileSync(__dirname + "/build/" + file)), + {} + ); assert(exports.memory); assert(instance && instance instanceof WebAssembly.Instance); assert(module && module instanceof WebAssembly.Module); @@ -278,7 +293,10 @@ function testInstantiate(file) { // should be able to instantiate from a promise yielding a buffer (async () => { - const { exports, instance, module } = await loader.instantiate(fs.promises.readFile(__dirname + "/build/" + file), {}); + const { exports, instance, module } = await loader.instantiate( + fs.promises.readFile(__dirname + "/build/" + file), + {} + ); assert(exports.memory); assert(instance && instance instanceof WebAssembly.Instance); assert(module && module instanceof WebAssembly.Module); @@ -286,7 +304,10 @@ function testInstantiate(file) { // should be able to mimic instantiateStreaming under node (for now) (async () => { - const { exports, instance, module } = await loader.instantiateStreaming(fs.promises.readFile(__dirname + "/build/" + file), {}); + const { exports, instance, module } = await loader.instantiateStreaming( + fs.promises.readFile(__dirname + "/build/" + file), + {} + ); assert(exports.memory); assert(instance && instance instanceof WebAssembly.Instance); assert(module && module instanceof WebAssembly.Module);