Skip to content

Add JZ WebAssembly kernel example#76

Open
dy wants to merge 1 commit into
wasmerio:mainfrom
dy:jz-kernel-example
Open

Add JZ WebAssembly kernel example#76
dy wants to merge 1 commit into
wasmerio:mainfrom
dy:jz-kernel-example

Conversation

@dy

@dy dy commented May 4, 2026

Copy link
Copy Markdown

Add JZ WebAssembly kernel example

Adds examples/jz-kernel. jz compiles a JavaScript-subset numeric kernel to standard WebAssembly; EdgeJS loads it through WebAssembly.Module / WebAssembly.Instance. JZ is used as a kernel compiler, not as a JavaScript engine for EdgeJS.

Reproduce

From examples/jz-kernel:

npm ci
edge bench.mjs

Local EdgeJS native result (4x4 Float64Array matrix kernel, 200k iterations, 9 runs after 5 warmups):

imports: 0
wasmBytes: 919
checksum: 50495773 (raw JS == JZ WASM)
raw JS median: ~12.6 ms
JZ WASM median: ~8.3 ms

Limits

  • Not an EdgeJS engine integration.
  • No safe-mode claim; local safe-mode is blocked before user code by missing Wasmer N-API features (napi_v10 / napi_extension_wasmer_v0).
  • No CI; the example would need a networked install of jz@^0.1.1.

@dy dy force-pushed the jz-kernel-example branch from 76b3441 to 70fbfb7 Compare May 4, 2026 12:30
@dy dy force-pushed the jz-kernel-example branch from 70fbfb7 to 9e9a51c Compare May 4, 2026 12:48

@syrusakbary syrusakbary left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, great first step.
Ideally I'd like to see JZ as another engine supported by Edge.js. But perhaps that might be better done in another PR. Thoughts @dy ?

@dy

dy commented May 6, 2026

Copy link
Copy Markdown
Author

@syrusakbary so how do you see JZ integration the best way? I lean to option 2, I could fix compat layer.
Some node modules can be added via WASI: fs, time, crypto. Some host env import can enable simplified fetch.
Also I am finishing metacircularity (jz.wasm) - that needs to be done before integration.

Integrating jz as an alternative JS engine in edgejs is fundamentally different from integrating V8, JavaScriptCore, or QuickJS.

The Core Mismatch

Edge.js is built around N-API (Node-API). It boots up by evaluating complex dynamic JavaScript (lib/internal/bootstrap/*.js) using napi_run_script, creating dynamic objects, closures, and managing Garbage Collection.

jz is a static compiler for a numeric/array subset of JS with no GC, no object prototypes. It outputs raw WebAssembly.
Therefore, jz cannot act as a full napi/jz provider to boot Node.js core modules (fs, http, etc.). It would fail to process EdgeJS's own internal Javascript.

How Integration Is Possible

Despite the mismatch, jz can be integrated at a different architectural level. Here are the realistic paths:

1. The "Sub-Engine" (AOT Kernel Compiler)

EdgeJS retains V8/QuickJS as its primary runtime (to handle N-API and Node compatibility) but embeds jz natively to accelerate pure math/DSP/array workloads.

  • EdgeJS intercepts imports (e.g. files with "use jz" or .jz extensions).
  • EdgeJS automatically runs the jz compiler on the script.
  • It instantiates the resulting WASM via Wasmer instantly and returns standard JS wrappers.
  • Pros: Seamless DX. Users get native Node.js capabilities + instant WASM speeds for hot routines.

2. The "Bare-Metal WASI" Engine Mode (edge --engine=jz)

EdgeJS bypasses standard Node initialization entirely if jz is selected.

  • Command: edge run --engine=jz compute.js
  • Under the hood, EdgeJS runs the jz compiler (either using a tiny embedded QuickJS instance or pre-compiled JZ-in-WASM).
  • It takes the generated .wasm, hooks it up to Wasmer with WASI context, and runs it.
  • Pros: Pure, blazing-fast sandbox. No Node.js overhead.
  • Cons: The script cannot use require('node:fs') or general JS APIs. It is limited to exactly what jz supports + WASI.

3. A Minimal napi/jz Facade (Not Recommended)

You could implement a napi/jz layer where napi_run_script compiles to WASM and primitive types (napi_create_double, etc.) map to WASM memory.

  • Cons: You'd have to stub out 90% of N-API. EdgeJS wouldn't be able to boot its normal environment.

Summary

As a drop-in replacement for V8 to run standard Node apps that's practically impossible without rewriting JZ into a fully dynamic JIT/interpreter.

However, if the goal is to make EdgeJS the first runtime to natively support "JS-subset to WASM execution" out of the box (either dynamically accelerating hot functions via Option 1, or treating Edge.js as a WASI runner for JS via Option 2), that provides massive value and perfectly fits Edge.js's vision of WASM computing.

rbbydotdev added a commit to rbbydotdev/edgejs that referenced this pull request Jun 22, 2026
Edge's node:sqlite (sqlite-via-wasm) only supported :memory: — a file path
SQLITE_CANTOPEN'd because @sqlite.org/sqlite-wasm is freestanding (no POSIX
VFS). Route file DBs to the native opfs-sahpool block VFS: durable, block-level
(reads/writes only needed blocks — the right shape for a large DB), survives
reload, no COOP/COEP, keyed by path (opfs://-style), bypassing sab-vfs. This is
the plug-and-play storage thesis: a DB wants durable block storage (OPFS), not
the live-coherent RAM fs (sab-vfs is for editable app files; its own OPFS
persistence is the separate wasmerio#76).

- vendor-adapters/sqlite-wasm.ts: installOpfsSAHPoolVfs at load (best-effort;
  name has NO leading slash — getDirectoryHandle rejects it), stash on sqlite3.
- presets/sqlite-via-wasm.runtime.js: __edgeOpenInternal opens file paths via
  sahPool.OpfsSAHPoolDb(path); :memory: keeps the default in-memory VFS.
- Both Vite-live, no wasm rebuild.

Verified (edge-sqlite-probe.js): :memory: CRUD ok; file DB write={n:42} →
reopen-count={c:1} DURABLE=true. Surfaced by running opencode in-edge
(OPENCODE_EDGE_GAPS.md GAP-7).
rbbydotdev added a commit to rbbydotdev/edgejs that referenced this pull request Jun 22, 2026
…oad (opt-in ?durable=1)

The sab-vfs disk is RedoxFS (copy-on-write) over a SharedArrayBuffer (RAM) —
ephemeral. Persist the block image to OPFS so the in-edge filesystem survives a
page reload. The bridge worker is the single owner of the disk SAB (peers write
to the shared SAB + bump a GEN counter); it loads the image in before mount and
flushes it out when GEN advances.

- sab-vfs-durable.ts: ping-pong pair of OPFS sync-access-handle files
  (img.0/img.1) + a 1-byte ptr for an atomic flip (crash mid-flush leaves the
  previous complete image active). restore() reads the active image at its
  stored size (it IS the live disk = prebuilt + writes, so it's authoritative);
  attach() debounce-flushes on GEN change.
- bridge-worker.ts: load-in precedence OPFS > prebuilt host image > format-fresh;
  attach the flush-out after mount.
- main.ts: forward ?durable=1 to the bridge worker.

Pure TS, no Rust/wasm rebuild (disk bytes live in sab-vfs.ts's diskSab;
crate/src/disk.rs is a test scaffold). Default OFF → no change to existing
studios. Verified: sabvfs-durable-test.mjs (2 pages / 1 OPFS origin) →
durable-reload-survival=true. Whole-image flush + best-effort snapshot is the
first cut; dirty-block + locked-snapshot crash-consistency is the documented
hardening follow-up (NOTES wasmerio#76).
rbbydotdev added a commit to rbbydotdev/edgejs that referenced this pull request Jun 22, 2026
…, node:sqlite surface

Bake opencode-discovery patches into edge core as general fixes (Node-faithful
behavior in the runtime). VERIFIED non-regressing: opencode GUI renders, next
studio E2E PASS, wasmerio#76 durable-reload-survival=true.

- GAP-2 (verified): vite.config.ts /app handler sets Content-Type by extension
  (.mjs/.js->text/javascript, .wasm->application/wasm, ...) instead of
  octet-stream, so a NATIVE import() of an /app URL no longer MIME-rejects.
  Safe/general: handler responds before Vite transform, bytes stay raw.
- wasmerio#76 hardening (verified, durable test passes): sab-vfs-durable.ts now flushes
  only changed 4KiB blocks (bridge-owned shadow diff) + crash-consistent
  data->header->ptr ordering (v2 block-structured image), replacing the
  whole-128MiB-image flush. Crash-recovery fallback confirmed.
- GAP-8 (get/all verified via opencode; surface completed): setReadBigInts now
  returns real BigInt (sqlite3_column_int64), iterate() is a real generator,
  shared readRow() helper.
- GAP-1 (HONEST: unverified, NOT claimed fixed): added a synthetic
  BuiltinModule('sqlite') registration as a foundation for native ESM-import,
  but the -e/vm probe still fails (it uses vm's importModuleDynamically, a
  SEPARATE gap, not the esm-via-blob-import short-circuit). CJS path works;
  opencode keeps its createRequire shim. See OPENCODE_EDGE_GAPS.md GAP-1.

run-wait.mjs: generic wait-for-marker harness (runs past the _start sentinel to
capture async probe output).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants