// NODE-JS — ENGINEERING REPORT

node-js v0.1.0 · JavaScript on fusevm · lex/parse → AST → bytecode → Cranelift JIT · JsHost object heap · binary node · MIT · in active development

Docs GitHub
// Color scheme

>_ENGINEERING REPORT

node-js is a compiled JavaScript runtime in Rust, hosted on the fusevm bytecode VM and its Cranelift JIT over a JsHost object heap. This report describes the architecture, the value model, the builtin objects, the parity tooling, the current state of the tree, and the dependency posture. The statements below are facts about the design and the manifest, not aspirational metrics.

JS
fusevm language host
8
builtin namespaces
v0.1.0
version
MIT
license · free / OSS

Summary

V8 compiles JavaScript to its own bytecode and JITs it with its own engine. node-js takes a different path: it lexes and parses JavaScript to an AST, lowers that to fusevm bytecode, and runs it on a compiled VM with a Cranelift JIT. Arithmetic and comparison operators lower to native VM ops so the JIT can trace hot loops; JS-specific behaviour is served by a JsHost object heap through a numbered builtin-call protocol and a strict numeric hook. It joins fusevm alongside zshrs, stryke, awkrs, pythonrs, and rubylang, and reuses that shared bytecode VM and JIT rather than shipping its own.

The binary is node. The current tree implements the lexer/parser (including arrow-function => lookahead and template-literal re-lexing), AST-to-bytecode lowering with function/arrow/try sub-chunks, var/let/const bindings, the full operator surface, all the control-flow constructs (including try/catch/finally and for…of/for…in), functions/arrows/closures/new, array and object literals with spread and template literals, and a broad builtin surface across eight namespaces. Behaviour is checked against the reference node by both a frozen-corpus parity harness and a generative fuzzer. Classes, async, generators, ES modules, and the Node.js standard library are planned; AOT and the persistent bytecode cache are scaffolded but not yet CLI-exposed.


Hosting on fusevm

node-js contains no virtual machine or JIT of its own. The execution path is:

JS source  →  lexer  →  parser → AST  →  lower to fusevm bytecode  →  fusevm VM + Cranelift JIT
                                                │
                                   JsHost heap (objects/arrays/strings, CallBuiltin, numeric hook)

Shared engine

fusevm is pulled from crates.io with the jit, jit-disk-cache, and aot features. JIT and VM improvements land once and benefit zshrs, stryke, awkrs, pythonrs, rubylang, and node-js together.

Native arithmetic

Operators lower to native fusevm ops; a strict numeric hook supplies JS semantics (+ string concat, the == coercion matrix, ToInt32 for bitwise ops, truthiness) only for the operand cases the VM can't compute natively.

Sub-chunk lowering

The compiler emits the main chunk plus a table of function/arrow sub-chunks and try-block chunks; load_merged rebases a freshly compiled program's func/try ids above those already on the host before running (src/lib.rs, src/compiler.rs).

Scaffolded AOT

The crate is built crate-type = ["rlib", "staticlib"] with fusevm's aot feature so an AOT object can later be linked against the runtime staticlib into a standalone executable — the same shape as pythonrs. Not yet wired to a CLI flag.


Value model

Primitive values — number, boolean, null, and undefined — ride through the VM directly as fusevm::Value. The reference types (objects, arrays, strings, and functions) live on the JsHost object heap behind Value::Obj(u32) handles, so mutation is observed through every alias — a.push(x) changes the array the caller still holds, exactly as in JavaScript. Object property insertion order is preserved via an IndexMap, matching what iteration and JSON.stringify observe.

Truthiness, equality, and coercion follow JavaScript, not the VM's default numeric rules: 0, NaN, "", null, undefined, and false are falsy; loose == runs the abstract-equality coercion matrix; + is string concatenation when either operand is a string; bitwise operators apply ToInt32 before operating. These are routed through host operations rather than relying on the VM's native numeric branch.


Builtin objects

Eight builtin namespaces and a broad per-type method surface are registered as fusevm builtins (src/builtins.rs), reachable through the compiler's CallBuiltin lowering and member-access dispatch:

console / JSON

console.log; JSON.stringify / JSON.parse with insertion-ordered round-trips.

Math / Number

Mathfloor/ceil/round/trunc/abs/sign/max/min/pow/sqrt/cbrt/random/hypot/log/log2/log10/exp/trig, PI/E; Number constants (MAX_SAFE_INTEGER, EPSILON, …).

Object / Array

Object.keys/values/entries/hasOwnProperty; array methods map/filter/reduce/forEach/find/every/some/push/pop/slice/join/concat/includes/indexOf/flat/flatMap/reverse/fill/at.

String / global

String methods charAt/charCodeAt/padStart/padEnd/repeat/replace/replaceAll/startsWith/endsWith/…; globals parseInt/parseFloat/isNaN/isFinite, Boolean, globalThis.


Differential parity

Correctness is measured, not asserted, by two tools that diff node-js against the reference node. Both are development tools that need node on PATH, so CI never runs them.

Frozen-corpus harness

parity (src/bin/parity.rs) runs a fixed corpus through node-js and the reference node, diffing stdout. The expected outputs are frozen into tests/data/parity_expected.txt, which tests/parity.rs replays with no node installed — so the regression check runs in CI without a Node.js toolchain.

Generative fuzzer

parity-fuzz (src/bin/parity_fuzz.rs) generates thousands of deterministic-output JS snippets and diffs node -e against the reference, delta-debugging each divergence to a minimal repro. Subprocess-only (never links the lib) and std-only (no rand).

Fuzz generators are biased toward where a JS frontend is likely to disagree with the reference: float representation and the exponential-notation threshold, ToInt32 bitwise wrap, the == coercion matrix, + coercion, string/array methods, toFixed/toPrecision rounding, and JSON round-trips.


Component status

ComponentStateNotes
lexer / parser → ASTImplementedNewline-aware tokenizer; arrow => lookahead; template ${...} re-lex (src/lexer.rs, src/parser.rs).
AST → fusevm bytecodeImplementedNative arithmetic + CallBuiltin; func/arrow/try sub-chunks; id rebasing (src/compiler.rs, src/lib.rs).
bindings & operatorsImplementedvar/let/const; arithmetic/comparison/logical/bitwise/nullish, typeof/void/delete/instanceof/in, ?:, updates, compound assign.
control flowImplementedif/else, while, do…while, for, for…in, for…of, switch, break/continue/return, throw, try/catch/finally.
functions / arrows / closuresImplementedDeclarations and expressions, arrow functions, closures, recursion, new (src/host.rs).
literals / spread / templatesImplementedArray/object literals, member/index access, spread ..., template literals.
builtin objects & methodsImplementedconsole/Math/JSON/Object/Array/Number/String/Boolean + broad array/string methods (src/builtins.rs).
parity harness + fuzzerImplementedFrozen corpus replayed no-node (tests/parity.rs); generative fuzzer (src/bin/parity_fuzz.rs). Dev tools, never in CI.
AOT native-exe & bytecode cacheScaffoldedstaticlib + fusevm aot/jit-disk-cache features enabled (mirrors pythonrs); not yet exposed on the CLI.
classes / async / modules / stdlibPlannedclass, async/await, generators, ES modules, destructuring, default/rest params, RegExp, Map/Set/Promise, Node.js stdlib.

Dependency posture

Dependencies are kept foundational and durable. Direct dependencies from Cargo.toml:

CrateRole
fusevmLanguage-agnostic bytecode VM + Cranelift JIT (jit, jit-disk-cache, aot)
clapCLI argument parsing (derive, cargo)
thiserrorError type derivation
rustc-hashFast hashing in the frontend
indexmapInsertion-ordered object property storage (observable in iteration / repr)

The parity and parity-fuzz binaries are subprocess-only and add no runtime dependencies; the fuzzer is std-only (no rand), deriving determinism from a seeded generator.


Compatibility & longevity

Reference semantics

Behaviour tracks the reference node as the compatibility target, checked by a frozen-corpus harness and a generative fuzzer over the coercion and number-formatting edge cases.

Ordered objects

Property insertion order is preserved, matching what JS programs observe in iteration and JSON.stringify.

Cross-architecture

macOS aarch64 and Linux x86_64 / aarch64 via the Cranelift JIT; portable bytecode underneath.

Standalone crate

An explicit empty [workspace] keeps node-js buildable on its own, independent of the meta repo.


Links