Skip to content

Commit

Permalink
Implement initial WebAssembly.Table export functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
TooTallNate committed Oct 9, 2023
1 parent ebac615 commit 9ef02b1
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 48 deletions.
5 changes: 5 additions & 0 deletions .changeset/stupid-buckets-tickle.md
@@ -0,0 +1,5 @@
---
'nxjs-runtime': patch
---

Implement initial `WebAssembly.Table` export functionality
Binary file added apps/tests/romfs/table.wasm
Binary file not shown.
34 changes: 34 additions & 0 deletions apps/tests/src/wasm.ts
Expand Up @@ -308,6 +308,40 @@ test('compute.wasm', async () => {
);
});

test('table.wasm', async () => {
const { module, instance } = await WebAssembly.instantiateStreaming(
fetch('table.wasm')
);
assert.equal(WebAssembly.Module.imports(module).length, 0);
assert.equal(WebAssembly.Module.exports(module), [
{ name: 'tbl', kind: 'table' },
]);

const tbl = instance.exports.tbl as WebAssembly.Table;
assert.instance(tbl, WebAssembly.Table);

assert.equal(tbl.length, 2);

const fn0 = tbl.get(0);
assert.equal(fn0(), 13);

const fn1 = tbl.get(1);
assert.equal(fn1(), 42);

let err: Error | undefined;
try {
tbl.get(2);
} catch (_err: any) {
err = _err;
}
assert.ok(err);
assert.instance(err, RangeError);
assert.equal(
err.message,
'WebAssembly.Table.get(): invalid index 2 into funcref table of size 2'
);
});

test('Imported function throws an Error is propagated', async () => {
const e = new Error('will be thrown');
const { instance } = await WebAssembly.instantiateStreaming(
Expand Down
5 changes: 4 additions & 1 deletion packages/runtime/src/$.ts
@@ -1,8 +1,11 @@
import type { MemoryDescriptor, Memory } from './wasm';

export interface Init {
wasmCallFunc(f: any, ...args: unknown[]): unknown;
wasmMemNew(descriptor: MemoryDescriptor): Memory;
wasmMemProto(mem: any): void;
wasmTableGet(t: any, i: number): Memory;
wasmInitMemory(c: any): void;
wasmInitTable(c: any): void;
}

export const $: Init = (globalThis as any).$;
Expand Down
5 changes: 0 additions & 5 deletions packages/runtime/src/switch.ts
Expand Up @@ -321,11 +321,6 @@ export interface Native {
wasmModuleImports(m: WasmModuleOpaque): any[];
wasmGlobalGet(g: WasmGlobalOpaque): any;
wasmGlobalSet(g: WasmGlobalOpaque, v: any): void;
wasmCallFunc(
b: WasmInstanceOpaque,
name: string,
...args: unknown[]
): unknown;
}

interface Internal {
Expand Down
31 changes: 20 additions & 11 deletions packages/runtime/src/wasm.ts
Expand Up @@ -151,7 +151,7 @@ function unwrapImports(importObject: Imports = {}) {
val = v;
} else {
// TODO: Handle "table" type
throw new Error(`Unsupported import type`);
throw new LinkError(`Unsupported import type for "${m}.${n}"`);
}
return {
module: m,
Expand All @@ -164,18 +164,25 @@ function unwrapImports(importObject: Imports = {}) {
);
}

function wrapExports(op: WasmInstanceOpaque, ex: any[]): Exports {
function wrapExports(ex: any[]): Exports {
const e: Exports = Object.create(null);
for (const v of ex) {
if (v.kind === 'function') {
e[v.name] = callFunc.bind(null, op, v.name);
const fn = callFunc.bind(null, v.val);
Object.defineProperty(fn, 'name', { value: v.name });
e[v.name] = fn;
} else if (v.kind === 'global') {
const g = new Global({ value: v.value, mutable: v.mutable });
bindGlobal(g, v.val);
e[v.name] = g;
} else if (v.kind === 'memory') {
Object.setPrototypeOf(v.val, Memory.prototype);
e[v.name] = v.val;
} else if (v.kind === 'table') {
Object.setPrototypeOf(v.val, Table.prototype);
e[v.name] = v.val;
} else {
throw new LinkError(`Unsupported export type "${v.kind}"`);
}
}
return Object.freeze(e);
Expand All @@ -201,17 +208,16 @@ export class Instance implements WebAssembly.Instance {
unwrapImports(importObject)
);
instanceInternalsMap.set(this, { module: moduleObject, opaque });
this.exports = wrapExports(opaque, exp);
this.exports = wrapExports(exp);
}
}

function callFunc(
op: WasmInstanceOpaque,
name: string,
func: any, // exported func
...args: unknown[]
): unknown {
try {
return Switch.native.wasmCallFunc(op, name, ...args);
return $.wasmCallFunc(func, ...args);
} catch (err: unknown) {
throw toWasmError(err);
}
Expand All @@ -235,7 +241,7 @@ export class Memory implements WebAssembly.Memory {
throw new Error('Method not implemented.');
}
}
$.wasmMemProto(Memory);
$.wasmInitMemory(Memory);

interface ModuleInternals {
buffer: ArrayBuffer;
Expand Down Expand Up @@ -281,15 +287,17 @@ export class Module implements WebAssembly.Module {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table) */
export class Table implements WebAssembly.Table {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/length) */
readonly length: number;
declare readonly length: number;

constructor(descriptor: TableDescriptor, value?: any) {
throw new Error('Method not implemented.');
}

/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/get) */
get(index: number) {
throw new Error('Method not implemented.');
get(index: number): any {
// Only function refs are supported for now
const fn = $.wasmTableGet(this, index);
return callFunc.bind(null, fn);
}

/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/grow) */
Expand All @@ -302,6 +310,7 @@ export class Table implements WebAssembly.Table {
throw new Error('Method not implemented.');
}
}
$.wasmInitTable(Table);

/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compile)
Expand Down
10 changes: 10 additions & 0 deletions source/types.h
Expand Up @@ -22,6 +22,16 @@
// programatically, so it's hard-coded here
#define QUICKJS_VERSION "2021-03-27"

// Useful for functions defined on class `prototype`s
#define JS_PROP_C_W (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)

#define NX_DEF_GETTER(THISARG, NAME, FN) \
atom = JS_NewAtom(ctx, NAME); \
JS_DefinePropertyGetSet(ctx, THISARG, atom, JS_NewCFunction(ctx, FN, "get " NAME, 0), JS_NULL, JS_PROP_C_W); \
JS_FreeAtom(ctx, atom);

#define NX_DEF_FUNC(THISARG, NAME, FN, LENGTH) (JS_DefinePropertyValueStr(ctx, THISARG, NAME, JS_NewCFunction(ctx, FN, NAME, LENGTH), JS_PROP_C_W))

typedef int BOOL;

typedef struct nx_work_s nx_work_t;
Expand Down

0 comments on commit 9ef02b1

Please sign in to comment.