Skip to content

Commit

Permalink
Implement WASM Instance, function calls
Browse files Browse the repository at this point in the history
`add.wasm` example works!
  • Loading branch information
TooTallNate committed Sep 23, 2023
1 parent 3e79c21 commit e06e2c6
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 24 deletions.
Binary file added apps/wasm/romfs/add.wasm
Binary file not shown.
12 changes: 10 additions & 2 deletions apps/wasm/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@
// which invokes the imported function `imported_func()` with
// a single parameter containing the value 42.
// https://github.com/mdn/webassembly-examples/blob/main/js-api-examples/simple.wat
const wasm = Switch.readFileSync(new URL('simple.wasm', Switch.entrypoint));
//const wasm = require('fs').readFileSync(__dirname + '/../romfs/simple.wasm');
const wasm = Switch.readFileSync(new URL('add.wasm', Switch.entrypoint));
//const wasm = require('fs').readFileSync(__dirname + '/../romfs/add.wasm');

const mod = new WebAssembly.Module(wasm);
console.log(WebAssembly.Module.exports(mod));
console.log(WebAssembly.Module.imports(mod));

WebAssembly.instantiate(mod).then((instance) => {
console.log(Object.keys(instance.exports));
if (typeof instance.exports.add === 'function') {
console.log(instance.exports.add(6, 9));
}
});

//const importObject = {
// imports: {
// imported_func(arg /* @type any */) {
Expand Down
7 changes: 7 additions & 0 deletions packages/runtime/src/switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type CanvasRenderingContext2DState =
export type FontFaceState = Opaque<'FontFaceState'>;
export type ImageOpaque = Opaque<'ImageOpaque'>;
export type WasmModuleOpaque = Opaque<'WasmModuleOpaque'>;
export type WasmInstanceOpaque = Opaque<'WasmInstanceOpaque'>;

export interface Vibration {
duration: number;
Expand Down Expand Up @@ -309,8 +310,14 @@ export interface Native {

// wasm
wasmNewModule(b: ArrayBuffer): WasmModuleOpaque;
wasmNewInstance(b: WasmModuleOpaque, imports: any): WasmInstanceOpaque;
wasmModuleExports(m: WasmModuleOpaque): any[];
wasmModuleImports(m: WasmModuleOpaque): any[];
wasmCallFunc(
b: WasmInstanceOpaque,
name: string,
...args: unknown[]
): unknown;
}

interface Internal {
Expand Down
41 changes: 39 additions & 2 deletions packages/runtime/src/wasm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { SwitchClass, WasmModuleOpaque } from './switch';
import { bufferSourceToArrayBuffer } from './utils';
import type {
SwitchClass,
WasmModuleOpaque,
WasmInstanceOpaque,
} from './switch';

declare const Switch: SwitchClass;

Expand Down Expand Up @@ -81,14 +85,47 @@ export class Global<T extends ValueType = ValueType>
}
}

interface InstanceInternals {
module: Module;
opaque: WasmInstanceOpaque;
}

const instanceInternalsMap = new WeakMap<Instance, InstanceInternals>();

/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance) */
export class Instance implements WebAssembly.Instance {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance/exports) */
readonly exports: Exports;

constructor(module: Module, importObject?: Imports) {
constructor(moduleObject: Module, importObject?: Imports) {
const modInternal = moduleInternalsMap.get(moduleObject);
if (!modInternal) throw new Error(`No internal state for Module`);

const op = Switch.native.wasmNewInstance(
modInternal.opaque,
importObject
);

this.exports = {};
for (const e of Module.exports(moduleObject)) {
if (e.kind === 'function') {
this.exports[e.name] = callFunc.bind(null, op, e.name);
}
}
Object.freeze(this.exports);

instanceInternalsMap.set(this, { module: moduleObject, opaque: op });
}
}

function callFunc(
op: WasmInstanceOpaque,
name: string,
...args: unknown[]
): unknown {
return Switch.native.wasmCallFunc(op, name, ...args);
}

/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory)
*/
Expand Down
231 changes: 211 additions & 20 deletions source/wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,57 @@ static JSClassID nx_wasm_module_class_id;
typedef struct
{
IM3Module module;
bool loaded;
uint8_t *data;
size_t size;
} nx_wasm_module_t;

static nx_wasm_module_t *nx_wasm_module_get(JSContext *ctx, JSValueConst obj)
{
return JS_GetOpaque2(ctx, obj, nx_wasm_module_class_id);
}

static void finalizer_wasm_module(JSRuntime *rt, JSValue val)
{
nx_wasm_module_t *m = JS_GetOpaque(val, nx_wasm_module_class_id);
if (m)
{
if (m->module)
m3_FreeModule(m->module);
js_free_rt(rt, m);
}
}

static JSClassID nx_wasm_instance_class_id;

typedef struct
{
IM3Runtime runtime;
IM3Module module;
bool loaded;
} nx_wasm_instance_t;

static nx_wasm_instance_t *nx_wasm_instance_get(JSContext *ctx, JSValueConst obj)
{
return JS_GetOpaque2(ctx, obj, nx_wasm_instance_class_id);
}

static void finalizer_wasm_instance(JSRuntime *rt, JSValue val)
{
nx_wasm_instance_t *i = JS_GetOpaque(val, nx_wasm_instance_class_id);
if (i)
{
if (i->module)
{
// Free the module, only if it wasn't previously loaded.
if (!i->loaded)
m3_FreeModule(i->module);
}
if (i->runtime)
m3_FreeRuntime(i->runtime);
js_free_rt(rt, i);
}
}

static JSValue nx_wasm_new_module(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
nx_context_t *nx_ctx = JS_GetContextOpaque(ctx);
Expand All @@ -37,6 +85,8 @@ static JSValue nx_wasm_new_module(JSContext *ctx, JSValueConst this_val, int arg

JSValue obj = JS_NewObjectClass(ctx, nx_wasm_module_class_id);
nx_wasm_module_t *m = js_mallocz(ctx, sizeof(nx_wasm_module_t));
// TODO: OOM error handling

JS_SetOpaque(obj, m);

size_t size;
Expand All @@ -50,34 +100,52 @@ static JSValue nx_wasm_new_module(JSContext *ctx, JSValueConst this_val, int arg
return nx_throw_wasm_error(ctx, "CompileError", r);
}

m->data = buf;
m->size = size;

return obj;
}

static void finalizer_wasm_module(JSRuntime *rt, JSValue val)
static JSValue nx_wasm_new_instance(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
nx_wasm_module_t *m = JS_GetOpaque(val, nx_wasm_module_class_id);
if (m)
nx_context_t *nx_ctx = JS_GetContextOpaque(ctx);

JSValue obj = JS_NewObjectClass(ctx, nx_wasm_instance_class_id);
nx_wasm_instance_t *i = js_mallocz(ctx, sizeof(nx_wasm_instance_t));
// TODO: OOM error handling

JS_SetOpaque(obj, i);

nx_wasm_module_t *m = nx_wasm_module_get(ctx, argv[0]);

M3Result r = m3_ParseModule(nx_ctx->wasm_env, &i->module, m->data, m->size);
// CHECK_NULL(r); // Should never fail because we already parsed it. TODO: clone it?

/* Create a runtime per module to avoid symbol clash. */
i->runtime = m3_NewRuntime(nx_ctx->wasm_env, /* TODO: adjust */ 512 * 1024, NULL);
if (!i->runtime)
{
if (!m->loaded)
{
// printf("freeing module\n");
m3_FreeModule(m->module);
}
js_free_rt(rt, m);
JS_FreeValue(ctx, obj);
return JS_ThrowOutOfMemory(ctx);
}
}

// static JSClassID nx_wasm_instance_class_id;
//
// typedef struct {
// IM3Runtime runtime;
// IM3Module module;
// bool loaded;
// } nx_wasm_instance_t;
// TODO: add imports

r = m3_LoadModule(i->runtime, i->module);
if (r)
{
JS_FreeValue(ctx, obj);
return nx_throw_wasm_error(ctx, "LinkError", r);
}

i->loaded = true;

return obj;
}

static JSValue nx_wasm_module_exports(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
nx_wasm_module_t *m = JS_GetOpaque2(ctx, argv[0], nx_wasm_module_class_id);
nx_wasm_module_t *m = nx_wasm_module_get(ctx, argv[0]);
if (!m)
return JS_EXCEPTION;

Expand Down Expand Up @@ -105,7 +173,7 @@ static JSValue nx_wasm_module_exports(JSContext *ctx, JSValueConst this_val, int

static JSValue nx_wasm_module_imports(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
nx_wasm_module_t *m = JS_GetOpaque2(ctx, argv[0], nx_wasm_module_class_id);
nx_wasm_module_t *m = nx_wasm_module_get(ctx, argv[0]);
if (!m)
return JS_EXCEPTION;

Expand All @@ -132,22 +200,145 @@ static JSValue nx_wasm_module_imports(JSContext *ctx, JSValueConst this_val, int
return imports;
}

static JSValue nx__wasm_result(JSContext *ctx, M3ValueType type, const void *stack)
{
switch (type)
{
case c_m3Type_i32:
{
int32_t val = *(int32_t *)stack;
return JS_NewInt32(ctx, val);
}
case c_m3Type_i64:
{
int64_t val = *(int64_t *)stack;
if (val == (int32_t)val)
return JS_NewInt32(ctx, (int32_t)val);
else
return JS_NewBigInt64(ctx, val);
}
case c_m3Type_f32:
{
float val = *(float *)stack;
return JS_NewFloat64(ctx, (double)val);
}
case c_m3Type_f64:
{
double val = *(double *)stack;
return JS_NewFloat64(ctx, val);
}
default:
return JS_UNDEFINED;
}
}

static JSValue nx_wasm_call_func(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
nx_wasm_instance_t *i = nx_wasm_instance_get(ctx, argv[0]);
if (!i)
return JS_EXCEPTION;

const char *fname = JS_ToCString(ctx, argv[1]);
if (!fname)
return JS_EXCEPTION;

IM3Function func;
M3Result r = m3_FindFunction(&func, i->runtime, fname);
if (r)
{
JS_FreeCString(ctx, fname);
return nx_throw_wasm_error(ctx, "RuntimeError", r);
}

JS_FreeCString(ctx, fname);

int nargs = argc - 2;
if (nargs == 0)
{
r = m3_Call(func, 0, NULL);
}
else
{
const char *m3_argv[nargs + 1];
for (int i = 0; i < nargs; i++)
{
m3_argv[i] = JS_ToCString(ctx, argv[i + 2]);
}
m3_argv[nargs] = NULL;
r = m3_CallArgv(func, nargs, m3_argv);
for (int i = 0; i < nargs; i++)
{
JS_FreeCString(ctx, m3_argv[i]);
}
}

if (r)
return nx_throw_wasm_error(ctx, "RuntimeError", r);

// https://webassembly.org/docs/js/ See "ToJSValue"
// NOTE: here we support returning BigInt, because we can.

int ret_count = m3_GetRetCount(func);

if (ret_count == 0)
{
return JS_UNDEFINED;
}

uint64_t valbuff[ret_count];
const void *valptrs[ret_count];
memset(valbuff, 0, sizeof(valbuff));
for (int i = 0; i < ret_count; i++)
{
valptrs[i] = &valbuff[i];
}

r = m3_GetResults(func, ret_count, valptrs);
if (r)
return nx_throw_wasm_error(ctx, "RuntimeError", r);

if (ret_count == 1)
{
return nx__wasm_result(ctx, m3_GetRetType(func, 0), valptrs[0]);
}
else
{
JSValue rets = JS_NewArray(ctx);
for (int i = 0; i < ret_count; i++)
{
JS_SetPropertyUint32(ctx, rets, i, nx__wasm_result(ctx, m3_GetRetType(func, i), valptrs[i]));
}
return rets;
}
}

static const JSCFunctionListEntry function_list[] = {
JS_CFUNC_DEF("wasmNewModule", 1, nx_wasm_new_module),
JS_CFUNC_DEF("wasmNewInstance", 1, nx_wasm_new_instance),
JS_CFUNC_DEF("wasmModuleExports", 1, nx_wasm_module_exports),
JS_CFUNC_DEF("wasmModuleImports", 1, nx_wasm_module_imports),
JS_CFUNC_DEF("wasmCallFunc", 1, nx_wasm_call_func),
};

void nx_init_wasm(JSContext *ctx, JSValueConst native_obj)
{
JSRuntime *rt = JS_GetRuntime(ctx);

/* WebAssembly.Module */
JS_NewClassID(&nx_wasm_module_class_id);
JSClassDef nx_wasm_module_class = {
"WebAssembly.Module",
.finalizer = finalizer_wasm_module,
};
JS_NewClass(rt, nx_wasm_module_class_id, &nx_wasm_module_class);

/* WebAssembly.Instance */
JS_NewClassID(&nx_wasm_instance_class_id);
JSClassDef nx_wasm_instance_class = {
"WebAssembly.Instance",
.finalizer = finalizer_wasm_instance,
};
JS_NewClass(rt, nx_wasm_instance_class_id, &nx_wasm_instance_class);

JS_SetPropertyFunctionList(ctx, native_obj, function_list, countof(function_list));
}

0 comments on commit e06e2c6

Please sign in to comment.