Skip to content

feat: Add __getFunction method for loader #2037

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Sep 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/loader/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
70 changes: 40 additions & 30 deletions lib/loader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,22 @@ 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();

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);
};
Comment on lines +49 to +51
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


/** Gets a string from memory. */
function getStringImpl(buffer, ptr) {
let len = new Uint32Array(buffer)[ptr + SIZE_OFFSET >>> 2] >>> 1;
Expand Down Expand Up @@ -83,51 +92,44 @@ 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;
extendedExports.__unpin = __unpin;
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
Expand Down Expand Up @@ -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);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this slower than passing the fn.index from AS to JS? It could be

  function __getFunction(index) {
    if (!table) throw Error(E_NO_EXPORT_TABLE);
    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));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand All @@ -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, {
Expand All @@ -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;
Expand All @@ -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:")],
Expand Down
7 changes: 5 additions & 2 deletions lib/loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
],
"version": "0.0.0",
"author": "Daniel Wirtz <[email protected]>",
"contributors": [
"MaxGraey <[email protected]>"
],
"license": "Apache-2.0",
"homepage": "https://assemblyscript.org",
"repository": {
Expand All @@ -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"
},
Expand Down
4 changes: 4 additions & 0 deletions lib/loader/tests/assembly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Uint8Array>();
export const INT16ARRAY_ID = idof<Int16Array>();
export const UINT16ARRAY_ID = idof<Uint16Array>();
Expand Down
Binary file modified lib/loader/tests/build/default.wasm
Binary file not shown.
Binary file modified lib/loader/tests/build/legacy.wasm
Binary file not shown.
31 changes: 26 additions & 5 deletions lib/loader/tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -261,32 +271,43 @@ 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);
})();

// 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);
})();

// 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);
})();

// 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);
Expand Down