From bf4869b9feb2bb2a2a041682678f9f49d61b2c05 Mon Sep 17 00:00:00 2001 From: Loi Nguyen Date: Fri, 31 Jul 2026 02:53:26 +0700 Subject: [PATCH 1/3] test(examples): define AssemblyScript guest ABI --- .../guest_assemblyscript/package-lock.json | 61 +++++++++++++++++++ examples/guest_assemblyscript/package.json | 11 ++++ examples/guest_assemblyscript/test.mjs | 36 +++++++++++ 3 files changed, 108 insertions(+) create mode 100644 examples/guest_assemblyscript/package-lock.json create mode 100644 examples/guest_assemblyscript/package.json create mode 100644 examples/guest_assemblyscript/test.mjs diff --git a/examples/guest_assemblyscript/package-lock.json b/examples/guest_assemblyscript/package-lock.json new file mode 100644 index 0000000..3fdcd7e --- /dev/null +++ b/examples/guest_assemblyscript/package-lock.json @@ -0,0 +1,61 @@ +{ + "name": "numax-guest-assemblyscript", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "numax-guest-assemblyscript", + "devDependencies": { + "assemblyscript": "0.28.8" + } + }, + "node_modules/assemblyscript": { + "version": "0.28.8", + "resolved": "https://npm.vexere.net/assemblyscript/-/assemblyscript-0.28.8.tgz", + "integrity": "sha512-RtnvzuplmvWeOlYLgjb61jE/Uvj0LYxQ8KxfO+ARu2zOH69NNnS4gfSRDSq0CbM1qZw91kVd7QKmGZFxUlfQ8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "binaryen": "123.0.0-nightly.20250530", + "long": "^5.2.4" + }, + "bin": { + "asc": "bin/asc.js", + "asinit": "bin/asinit.js" + }, + "engines": { + "node": ">=20", + "npm": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/assemblyscript" + } + }, + "node_modules/binaryen": { + "version": "123.0.0-nightly.20250530", + "resolved": "https://npm.vexere.net/binaryen/-/binaryen-123.0.0-nightly.20250530.tgz", + "integrity": "sha512-d1zPHBN5YlOd3Ff+OUxvVExuFeh8heSnqe+X3bjItFxGLvn4VGBKmrvv7pgy/cRhrIUGuPW138iaWfDhwjyDqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "wasm-as": "bin/wasm-as", + "wasm-ctor-eval": "bin/wasm-ctor-eval", + "wasm-dis": "bin/wasm-dis", + "wasm-merge": "bin/wasm-merge", + "wasm-metadce": "bin/wasm-metadce", + "wasm-opt": "bin/wasm-opt", + "wasm-reduce": "bin/wasm-reduce", + "wasm-shell": "bin/wasm-shell", + "wasm2js": "bin/wasm2js" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://npm.vexere.net/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + } + } +} diff --git a/examples/guest_assemblyscript/package.json b/examples/guest_assemblyscript/package.json new file mode 100644 index 0000000..fb5f508 --- /dev/null +++ b/examples/guest_assemblyscript/package.json @@ -0,0 +1,11 @@ +{ + "name": "numax-guest-assemblyscript", + "private": true, + "scripts": { + "build": "asc assembly/index.ts --config asconfig.json --target release", + "test": "node test.mjs" + }, + "devDependencies": { + "assemblyscript": "0.28.8" + } +} diff --git a/examples/guest_assemblyscript/test.mjs b/examples/guest_assemblyscript/test.mjs new file mode 100644 index 0000000..5856d4a --- /dev/null +++ b/examples/guest_assemblyscript/test.mjs @@ -0,0 +1,36 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; + +const wasm = await readFile(new URL("./guest.wasm", import.meta.url)); +const module = await WebAssembly.compile(wasm); + +assert.deepEqual(WebAssembly.Module.imports(module), [ + { module: "nx", name: "host_log_v2", kind: "function" }, +]); +assert.ok( + WebAssembly.Module.exports(module).some( + ({ name, kind }) => name === "memory" && kind === "memory", + ), +); +assert.ok( + WebAssembly.Module.exports(module).some( + ({ name, kind }) => name === "run" && kind === "function", + ), +); + +let memory; +const messages = []; +const { instance } = await WebAssembly.instantiate(module, { + nx: { + host_log_v2(pointer, length) { + messages.push( + new TextDecoder().decode(new Uint8Array(memory.buffer, pointer, length)), + ); + return 0; + }, + }, +}); +memory = instance.exports.memory; +instance.exports.run(); + +assert.deepEqual(messages, ["Hello from AssemblyScript!"]); From 599cbea7b87ffbe40a98d913be0a38034cb08fbf Mon Sep 17 00:00:00 2001 From: Loi Nguyen Date: Fri, 31 Jul 2026 02:55:10 +0700 Subject: [PATCH 2/3] feat(examples): add AssemblyScript guest --- examples/guest_assemblyscript/.gitignore | 3 +++ examples/guest_assemblyscript/asconfig.json | 13 +++++++++++++ examples/guest_assemblyscript/assembly/index.ts | 12 ++++++++++++ examples/guest_assemblyscript/test.mjs | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 examples/guest_assemblyscript/.gitignore create mode 100644 examples/guest_assemblyscript/asconfig.json create mode 100644 examples/guest_assemblyscript/assembly/index.ts diff --git a/examples/guest_assemblyscript/.gitignore b/examples/guest_assemblyscript/.gitignore new file mode 100644 index 0000000..8065cba --- /dev/null +++ b/examples/guest_assemblyscript/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +guest.wasm +guest.wat diff --git a/examples/guest_assemblyscript/asconfig.json b/examples/guest_assemblyscript/asconfig.json new file mode 100644 index 0000000..e30debd --- /dev/null +++ b/examples/guest_assemblyscript/asconfig.json @@ -0,0 +1,13 @@ +{ + "targets": { + "release": { + "outFile": "guest.wasm", + "textFile": "guest.wat", + "runtime": "stub", + "optimizeLevel": 3, + "shrinkLevel": 0, + "converge": false, + "noAssert": true + } + } +} diff --git a/examples/guest_assemblyscript/assembly/index.ts b/examples/guest_assemblyscript/assembly/index.ts new file mode 100644 index 0000000..b86c45e --- /dev/null +++ b/examples/guest_assemblyscript/assembly/index.ts @@ -0,0 +1,12 @@ +@external("nx", "host_log_v2") +declare function hostLogV2(message: usize, length: i32): i32; + +const MESSAGE = memory.data([ + 72, 101, 108, 108, 111, 32, 102, 114, 111, 109, 32, 65, 115, 115, 101, 109, 98, 108, 121, 83, + 99, 114, 105, 112, 116, 33, +]); +const MESSAGE_LENGTH: i32 = 26; + +export function run(): void { + hostLogV2(MESSAGE, MESSAGE_LENGTH); +} diff --git a/examples/guest_assemblyscript/test.mjs b/examples/guest_assemblyscript/test.mjs index 5856d4a..b80972c 100644 --- a/examples/guest_assemblyscript/test.mjs +++ b/examples/guest_assemblyscript/test.mjs @@ -20,7 +20,7 @@ assert.ok( let memory; const messages = []; -const { instance } = await WebAssembly.instantiate(module, { +const instance = await WebAssembly.instantiate(module, { nx: { host_log_v2(pointer, length) { messages.push( From dc386cd2a78181caa4de67f780e922f8666946c2 Mon Sep 17 00:00:00 2001 From: Loi Nguyen Date: Fri, 31 Jul 2026 02:57:29 +0700 Subject: [PATCH 3/3] docs(examples): document AssemblyScript guest --- .github/workflows/ci.yml | 11 +++++ examples/guest_assemblyscript/README.md | 55 +++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 examples/guest_assemblyscript/README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb17bcb..d8da47c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: examples/guest_assemblyscript/package-lock.json - uses: actions-rust-lang/setup-rust-toolchain@9d7e65c320fdb52dcd45ffaa68deb6c02c8754d9 # v1.10.1 with: toolchain: stable @@ -98,6 +103,12 @@ jobs: run: cargo build --release --target wasm32-unknown-unknown --manifest-path examples/distributed_chat/Cargo.toml - name: Build vote_tally_tls run: cargo build --release --target wasm32-unknown-unknown --manifest-path examples/vote_tally_tls/Cargo.toml + - name: Build and test AssemblyScript guest + working-directory: examples/guest_assemblyscript + run: | + npm ci + npm run build + npm test cli-smoke: name: CLI Multi-process Smoke diff --git a/examples/guest_assemblyscript/README.md b/examples/guest_assemblyscript/README.md new file mode 100644 index 0000000..4a71232 --- /dev/null +++ b/examples/guest_assemblyscript/README.md @@ -0,0 +1,55 @@ +# AssemblyScript guest + +This example calls the raw Numax WebAssembly ABI from AssemblyScript. It exports +`run`, imports `nx.host_log_v2`, and logs `Hello from AssemblyScript!`. + +The example was tested with Node.js 22 and AssemblyScript 0.28.8. The compiler is +pinned in `package-lock.json`, so no global AssemblyScript installation is needed. + +## Build and test + +From this directory: + +```sh +npm ci +npm run build +npm test +``` + +The build produces `guest.wasm` and a readable `guest.wat`. The smoke test checks +the module's imports and exports, instantiates it with a small JavaScript host, and +asserts the exact logged message. + +## Run with Numax + +Build the CLI from the repository root: + +```sh +cargo build --release -p nx-cli +``` + +Then return to this directory and run: + +```sh +../../target/release/nx run guest.wasm +``` + +The output includes: + +```text +Hello from AssemblyScript! +``` + +## ABI notes + +Numax logging uses a pointer and byte length into the guest's exported linear +memory. The message is therefore stored as static UTF-8 bytes with +`memory.data` and passed directly to `host_log_v2`. + +Using `String.UTF8.encode` would allocate managed AssemblyScript memory and pull +in the standard `env.abort` import for allocation checks. Numax does not provide +that namespace, so this deliberately minimal guest uses static data and imports +only `nx.host_log_v2`. + +This is a raw ABI example, not a generated AssemblyScript binding. The resulting +module does not depend on WASI; Node.js and npm are only build and test tools.