Skip to content

Commit b0549e0

Browse files
authored
feat: Add __getFunction method for loader (#2037)
1 parent 71e407b commit b0549e0

File tree

7 files changed

+78
-37
lines changed

7 files changed

+78
-37
lines changed

lib/loader/index.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ export interface ASUtil {
8282
/** Gets a live view on a Float64Array's values in the module's memory. */
8383
__getFloat64ArrayView(ptr: number): Float64Array;
8484

85+
/** Gets a function from poiner which contain table's index. */
86+
__getFunction(ptr: number): ((...args: unknown[]) => unknown) | null;
87+
8588
/** Tests whether a managed object is an instance of the class represented by the specified base id. */
8689
__instanceof(ptr: number, baseId: number): boolean;
8790
/** Allocates a new string in the module's memory and returns a reference (pointer) to it. */

lib/loader/index.js

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,22 @@ const ARRAYBUFFERVIEW_SIZE = 12;
3434
const ARRAY_LENGTH_OFFSET = 12;
3535
const ARRAY_SIZE = 16;
3636

37+
const E_NO_EXPORT_TABLE = "Operation requires compiling with --exportTable";
38+
const E_NO_EXPORT_RUNTIME = "Operation requires compiling with --exportRuntime";
39+
const F_NO_EXPORT_RUNTIME = () => { throw Error(E_NO_EXPORT_RUNTIME); };
40+
3741
const BIGINT = typeof BigUint64Array !== "undefined";
3842
const THIS = Symbol();
3943

4044
const STRING_SMALLSIZE = 192; // break-even point in V8
4145
const STRING_CHUNKSIZE = 1024; // mitigate stack overflow
4246
const utf16 = new TextDecoder("utf-16le", { fatal: true }); // != wtf16
4347

48+
/** polyfill for Object.hasOwn */
49+
Object.hasOwn = Object.hasOwn || function(obj, prop) {
50+
return Object.prototype.hasOwnProperty.call(obj, prop);
51+
};
52+
4453
/** Gets a string from memory. */
4554
function getStringImpl(buffer, ptr) {
4655
let len = new Uint32Array(buffer)[ptr + SIZE_OFFSET >>> 2] >>> 1;
@@ -83,51 +92,44 @@ function preInstantiate(imports) {
8392
return extendedExports;
8493
}
8594

86-
const E_NOEXPORTRUNTIME = "Operation requires compiling with --exportRuntime";
87-
const F_NOEXPORTRUNTIME = function() { throw Error(E_NOEXPORTRUNTIME); };
88-
8995
/** Prepares the final module once instantiation is complete. */
9096
function postInstantiate(extendedExports, instance) {
9197
const exports = instance.exports;
9298
const memory = exports.memory;
9399
const table = exports.table;
94-
const __new = exports.__new || F_NOEXPORTRUNTIME;
95-
const __pin = exports.__pin || F_NOEXPORTRUNTIME;
96-
const __unpin = exports.__unpin || F_NOEXPORTRUNTIME;
97-
const __collect = exports.__collect || F_NOEXPORTRUNTIME;
100+
const __new = exports.__new || F_NO_EXPORT_RUNTIME;
101+
const __pin = exports.__pin || F_NO_EXPORT_RUNTIME;
102+
const __unpin = exports.__unpin || F_NO_EXPORT_RUNTIME;
103+
const __collect = exports.__collect || F_NO_EXPORT_RUNTIME;
98104
const __rtti_base = exports.__rtti_base;
99-
const getRttiCount = __rtti_base
100-
? function (arr) { return arr[__rtti_base >>> 2]; }
101-
: F_NOEXPORTRUNTIME;
105+
const getRttiCount = __rtti_base ? arr => arr[__rtti_base >>> 2] : F_NO_EXPORT_RUNTIME;
102106

103107
extendedExports.__new = __new;
104108
extendedExports.__pin = __pin;
105109
extendedExports.__unpin = __unpin;
106110
extendedExports.__collect = __collect;
107111

108112
/** Gets the runtime type info for the given id. */
109-
function getInfo(id) {
113+
function getRttInfo(id) {
114+
const U32 = new Uint32Array(memory.buffer);
115+
if ((id >>>= 0) >= getRttiCount(U32)) throw Error(`invalid id: ${id}`);
116+
return U32[(__rtti_base + 4 >>> 2) + (id << 1)];
117+
}
118+
119+
/** Gets the runtime base id for the given id. */
120+
function getRttBase(id) {
110121
const U32 = new Uint32Array(memory.buffer);
111-
const count = getRttiCount(U32);
112-
if ((id >>>= 0) >= count) throw Error(`invalid id: ${id}`);
113-
return U32[(__rtti_base + 4 >>> 2) + id * 2];
122+
if ((id >>>= 0) >= getRttiCount(U32)) throw Error(`invalid id: ${id}`);
123+
return U32[(__rtti_base + 4 >>> 2) + (id << 1) + 1];
114124
}
115125

116126
/** Gets and validate runtime type info for the given id for array like objects */
117127
function getArrayInfo(id) {
118-
const info = getInfo(id);
128+
const info = getRttInfo(id);
119129
if (!(info & (ARRAYBUFFERVIEW | ARRAY | STATICARRAY))) throw Error(`not an array: ${id}, flags=${info}`);
120130
return info;
121131
}
122132

123-
/** Gets the runtime base id for the given id. */
124-
function getBase(id) {
125-
const U32 = new Uint32Array(memory.buffer);
126-
const count = getRttiCount(U32);
127-
if ((id >>>= 0) >= count) throw Error(`invalid id: ${id}`);
128-
return U32[(__rtti_base + 4 >>> 2) + id * 2 + 1];
129-
}
130-
131133
/** Gets the runtime alignment of a collection's values. */
132134
function getValueAlign(info) {
133135
return 31 - Math.clz32((info >>> VAL_ALIGN_OFFSET) & 31); // -1 if none
@@ -263,6 +265,15 @@ function postInstantiate(extendedExports, instance) {
263265

264266
extendedExports.__getArrayBuffer = __getArrayBuffer;
265267

268+
/** Gets a function from poiner which contain table's index. */
269+
function __getFunction(ptr) {
270+
if (!table) throw Error(E_NO_EXPORT_TABLE);
271+
const index = new Uint32Array(memory.buffer)[ptr >>> 2];
272+
return table.get(index);
273+
}
274+
275+
extendedExports.__getFunction = __getFunction;
276+
266277
/** Copies a typed array's values from the module's memory. */
267278
function getTypedArray(Type, alignLog2, ptr) {
268279
return new Type(getTypedArrayView(Type, alignLog2, ptr));
@@ -309,7 +320,7 @@ function postInstantiate(extendedExports, instance) {
309320
if (id <= getRttiCount(U32)) {
310321
do {
311322
if (id == baseId) return true;
312-
id = getBase(id);
323+
id = getRttBase(id);
313324
} while (id);
314325
}
315326
return false;
@@ -373,14 +384,13 @@ export function demangle(exports, extendedExports = {}) {
373384
const setArgumentsLength = exports["__argumentsLength"]
374385
? length => { exports["__argumentsLength"].value = length; }
375386
: exports["__setArgumentsLength"] || exports["__setargc"] || (() => { /* nop */ });
376-
for (let internalName in exports) {
377-
if (!Object.prototype.hasOwnProperty.call(exports, internalName)) continue;
387+
for (let internalName of Object.keys(exports)) {
378388
const elem = exports[internalName];
379389
let parts = internalName.split(".");
380390
let curr = extendedExports;
381391
while (parts.length > 1) {
382392
let part = parts.shift();
383-
if (!Object.prototype.hasOwnProperty.call(curr, part)) curr[part] = {};
393+
if (!Object.hasOwn(curr, part)) curr[part] = {};
384394
curr = curr[part];
385395
}
386396
let name = parts[0];
@@ -406,7 +416,7 @@ export function demangle(exports, extendedExports = {}) {
406416
name = name.substring(hash + 1);
407417
curr = curr[className].prototype;
408418
if (/^(get|set):/.test(name)) {
409-
if (!Object.prototype.hasOwnProperty.call(curr, name = name.substring(4))) {
419+
if (!Object.hasOwn(curr, name = name.substring(4))) {
410420
let getter = exports[internalName.replace("set:", "get:")];
411421
let setter = exports[internalName.replace("get:", "set:")];
412422
Object.defineProperty(curr, name, {
@@ -417,7 +427,7 @@ export function demangle(exports, extendedExports = {}) {
417427
}
418428
} else {
419429
if (name === 'constructor') {
420-
(curr[name] = (...args) => {
430+
(curr[name] = function(...args) {
421431
setArgumentsLength(args.length);
422432
return elem(...args);
423433
}).original = elem;
@@ -430,7 +440,7 @@ export function demangle(exports, extendedExports = {}) {
430440
}
431441
} else {
432442
if (/^(get|set):/.test(name)) {
433-
if (!Object.prototype.hasOwnProperty.call(curr, name = name.substring(4))) {
443+
if (!Object.hasOwn(curr, name = name.substring(4))) {
434444
Object.defineProperty(curr, name, {
435445
get: exports[internalName.replace("set:", "get:")],
436446
set: exports[internalName.replace("get:", "set:")],

lib/loader/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
],
1212
"version": "0.0.0",
1313
"author": "Daniel Wirtz <dcode+assemblyscript@dcode.io>",
14+
"contributors": [
15+
"MaxGraey <maxgraey@gmail.com>"
16+
],
1417
"license": "Apache-2.0",
1518
"homepage": "https://assemblyscript.org",
1619
"repository": {
@@ -30,8 +33,8 @@
3033
},
3134
"scripts": {
3235
"asbuild": "npm run asbuild:default && npm run asbuild:legacy",
33-
"asbuild:default": "node ../../bin/asc tests/assembly/index.ts --binaryFile tests/build/default.wasm --exportRuntime",
34-
"asbuild:legacy": "node ../../bin/asc tests/assembly/index.ts --disable mutable-globals --binaryFile tests/build/legacy.wasm --exportRuntime",
36+
"asbuild:default": "node ../../bin/asc tests/assembly/index.ts --binaryFile tests/build/default.wasm --exportRuntime --exportTable",
37+
"asbuild:legacy": "node ../../bin/asc tests/assembly/index.ts --disable mutable-globals --binaryFile tests/build/legacy.wasm --exportRuntime --exportTable",
3538
"build": "npx esm2umd loader index.js > umd/index.js",
3639
"test": "node tests && node tests/umd"
3740
},

lib/loader/tests/assembly/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ export function dotrace(num: f64): void {
6969
trace("The answer is", 1, num);
7070
}
7171

72+
export function getVaraddFunc(): (a: i32, b: i32) => i32 {
73+
return varadd;
74+
}
75+
7276
export const UINT8ARRAY_ID = idof<Uint8Array>();
7377
export const INT16ARRAY_ID = idof<Int16Array>();
7478
export const UINT16ARRAY_ID = idof<Uint16Array>();

lib/loader/tests/build/default.wasm

23 Bytes
Binary file not shown.

lib/loader/tests/build/legacy.wasm

23 Bytes
Binary file not shown.

lib/loader/tests/index.js

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,16 @@ function test(file) {
231231
assert.strictEqual(car.isDoorsOpen, 0);
232232
assert(typeof +car === "number"); // uses Car.prototype.valueOf to obtain `thisPtr`
233233

234+
// should be able to return a function
235+
{
236+
const addFunc = exports.__getFunction(exports.getVaraddFunc());
237+
assert(typeof addFunc === "function");
238+
assert.strictEqual(addFunc(1, 2), 3);
239+
240+
const invalidFunc = exports.__getFunction(0);
241+
assert(invalidFunc == null);
242+
}
243+
234244
// should be able to use trace
235245
exports.dotrace(42);
236246

@@ -261,32 +271,43 @@ function test(file) {
261271
function testInstantiate(file) {
262272
// should be able to instantiate from a buffer
263273
(async () => {
264-
const { exports, instance, module } = await loader.instantiate(fs.readFileSync(__dirname + "/build/" + file), {});
274+
const { exports, instance, module } = await loader.instantiate(
275+
fs.readFileSync(__dirname + "/build/" + file),
276+
{}
277+
);
265278
assert(exports.memory);
266279
assert(instance && instance instanceof WebAssembly.Instance);
267280
assert(module && module instanceof WebAssembly.Module);
268281
})();
269282

270283
// should be able to instantiate from a wasm module
271284
(async () => {
272-
const wasmModule = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/" + file));
273-
const { exports, instance, module } = await loader.instantiate(wasmModule, {});
285+
const { exports, instance, module } = await loader.instantiate(
286+
new WebAssembly.Module(fs.readFileSync(__dirname + "/build/" + file)),
287+
{}
288+
);
274289
assert(exports.memory);
275290
assert(instance && instance instanceof WebAssembly.Instance);
276291
assert(module && module instanceof WebAssembly.Module);
277292
})();
278293

279294
// should be able to instantiate from a promise yielding a buffer
280295
(async () => {
281-
const { exports, instance, module } = await loader.instantiate(fs.promises.readFile(__dirname + "/build/" + file), {});
296+
const { exports, instance, module } = await loader.instantiate(
297+
fs.promises.readFile(__dirname + "/build/" + file),
298+
{}
299+
);
282300
assert(exports.memory);
283301
assert(instance && instance instanceof WebAssembly.Instance);
284302
assert(module && module instanceof WebAssembly.Module);
285303
})();
286304

287305
// should be able to mimic instantiateStreaming under node (for now)
288306
(async () => {
289-
const { exports, instance, module } = await loader.instantiateStreaming(fs.promises.readFile(__dirname + "/build/" + file), {});
307+
const { exports, instance, module } = await loader.instantiateStreaming(
308+
fs.promises.readFile(__dirname + "/build/" + file),
309+
{}
310+
);
290311
assert(exports.memory);
291312
assert(instance && instance instanceof WebAssembly.Instance);
292313
assert(module && module instanceof WebAssembly.Module);

0 commit comments

Comments
 (0)