From ec79b199adb683d5f36a80b8c602dbe5b90176aa Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 10 Oct 2023 11:06:33 -0500 Subject: [PATCH] Implement `WebAssembly.Memory#grow()` --- .changeset/gentle-garlics-fry.md | 5 +++ apps/tests/romfs/grow.wasm | Bin 50 -> 75 bytes apps/tests/src/wasm.ts | 11 ++++++- packages/runtime/src/wasm.ts | 5 ++- source/wasm.c | 51 +++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 .changeset/gentle-garlics-fry.md diff --git a/.changeset/gentle-garlics-fry.md b/.changeset/gentle-garlics-fry.md new file mode 100644 index 00000000..4e6ed5e9 --- /dev/null +++ b/.changeset/gentle-garlics-fry.md @@ -0,0 +1,5 @@ +--- +'nxjs-runtime': patch +--- + +Implement `WebAssembly.Memory#grow()` diff --git a/apps/tests/romfs/grow.wasm b/apps/tests/romfs/grow.wasm index 62e0ccca09db0d3c70f7a7c29084affc323d10db..057e96c58e0e5e8aed2cd1a9e2d564935a5a2230 100644 GIT binary patch literal 75 zcmV~$K?;B%5J1sCj!|TEfS|Kfbb*eb3vp#a87;ed?-A}SfON)#M?O`9G%83tt4&vV bVMwtaA;rBsXGJu}Z0JYRvdPNiEDP=jv)T)| literal 50 zcmZQbEY4+QU|?WmWlUgTtY>CoWME}xWME|HV`9!t&1GU>NiWJTXJFvsU}R%(WOQKQ F1^}6g1^xg4 diff --git a/apps/tests/src/wasm.ts b/apps/tests/src/wasm.ts index a11512d0..1a534de5 100644 --- a/apps/tests/src/wasm.ts +++ b/apps/tests/src/wasm.ts @@ -230,21 +230,25 @@ test('grow.wasm', async () => { assert.equal(WebAssembly.Module.exports(module), [ { name: 'grow', kind: 'function' }, + { name: 'getPageCount', kind: 'function' }, { name: 'mem', kind: 'memory' }, ]); const grow = instance.exports.grow as Function; + const getPageCount = instance.exports.getPageCount as Function; const mem = instance.exports.mem as WebAssembly.Memory; assert.instance(mem, WebAssembly.Memory); const buf = mem.buffer; assert.equal(buf.byteLength, 65536, 'Initially, page size = 1'); + assert.equal(getPageCount(), 1); // TODO: make work //assert.ok(buf === mem.buffer, 'Same size, same instance (size = 1)'); grow(); const buf2 = mem.buffer; assert.equal(buf2.byteLength, 65536 * 2, 'Page size = 2'); + assert.equal(getPageCount(), 2); assert.ok(buf !== buf2, 'Different size, different instance (size = 2)'); // TODO: make work //assert.ok(buf2 === mem.buffer, 'Same size, same instance (size = 2)'); @@ -252,11 +256,16 @@ test('grow.wasm', async () => { grow(); const buf3 = mem.buffer; assert.equal(buf3.byteLength, 65536 * 3, 'Page size = 3'); + assert.equal(getPageCount(), 3); assert.ok(buf2 !== buf3, 'Different size, different instance (size = 3)'); // TODO: make work //assert.ok(buf3 === mem.buffer, 'Same size, same instance (size = 3)'); - // TODO: `mem.grow(1)` test, once that function is implemented + // Now using `mem.grow()` from the JavaScript side + mem.grow(2); + const buf4 = mem.buffer; + assert.equal(buf4.byteLength, 65536 * 5, 'Page size = 5'); + assert.equal(getPageCount(), 5); }); test('compute.wasm', async () => { diff --git a/packages/runtime/src/wasm.ts b/packages/runtime/src/wasm.ts index 13398405..4dc27571 100644 --- a/packages/runtime/src/wasm.ts +++ b/packages/runtime/src/wasm.ts @@ -237,9 +237,8 @@ export class Memory implements WebAssembly.Memory { declare readonly buffer: ArrayBuffer; /** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/grow) */ - grow(delta: number): number { - throw new Error('Method not implemented.'); - } + // @ts-expect-error This is a native function + grow(delta: number): number {} } $.wasmInitMemory(Memory); diff --git a/source/wasm.c b/source/wasm.c index a53e453b..fbd519c3 100644 --- a/source/wasm.c +++ b/source/wasm.c @@ -982,6 +982,56 @@ static JSValue nx_wasm_memory_buffer_get(JSContext *ctx, JSValueConst this_val, return buf; } +// `Memory#grow()` function +static JSValue nx_wasm_memory_grow(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + nx_wasm_memory_t *data = nx_wasm_memory_get(ctx, this_val); + if (!data) + return JS_EXCEPTION; + + IM3Memory memory = data->mem; + if (!memory) + { + JS_ThrowTypeError(ctx, "Memory not set"); + return JS_EXCEPTION; + } + + M3MemoryHeader *mallocated = memory->mallocated; + if (!mallocated) + { + JS_ThrowTypeError(ctx, "Memory not allocated"); + return JS_EXCEPTION; + } + + i32 numPagesToGrow; + if (JS_ToInt32(ctx, &numPagesToGrow, argv[0])) + return JS_EXCEPTION; + + if (numPagesToGrow < 0) + { + JS_ThrowTypeError(ctx, "WebAssembly.Memory.grow(): Argument 0 must be non-negative"); + return JS_EXCEPTION; + } + + JSValue prevSize = JS_NewUint32(ctx, memory->numPages); + + if (numPagesToGrow > 0) + { + IM3Runtime runtime = m3MemRuntime(mallocated); + if (!runtime) + { + JS_ThrowTypeError(ctx, "WebAssembly.Memory.grow(): Memory not bound to an instance"); + return JS_EXCEPTION; + } + u32 requiredPages = memory->numPages + numPagesToGrow; + M3Result r = ResizeMemory(runtime, requiredPages); + if (r) + return nx_throw_wasm_error(ctx, "RuntimeError", r); + } + + return prevSize; +} + // `Table#get()` function static JSValue nx_wasm_table_get_fn(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -1034,6 +1084,7 @@ static JSValue nx_wasm_init_memory_class(JSContext *ctx, JSValueConst this_val, JSAtom atom; JSValue proto = JS_GetPropertyStr(ctx, argv[0], "prototype"); NX_DEF_GETTER(proto, "buffer", nx_wasm_memory_buffer_get); + NX_DEF_FUNC(proto, "grow", nx_wasm_memory_grow, 1); JS_FreeValue(ctx, proto); return JS_UNDEFINED; }