```text
███╗ ██╗ ██████╗ ██████╗ ███████╗ ██╗███████╗
████╗ ██║██╔═══██╗██╔══██╗██╔════╝ ██║██╔════╝
██╔██╗ ██║██║ ██║██║ ██║█████╗█████╗ ██║███████╗
██║╚██╗██║██║ ██║██║ ██║██╔══╝╚════╝██ ██║╚════██║
██║ ╚████║╚██████╔╝██████╔╝███████╗ ╚█████╔╝███████║
╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚════╝ ╚══════╝
```

[](https://menketechnologies.github.io/node-js/)
[](https://github.com/MenkeTechnologies/fusevm)


### `[JAVASCRIPT, COMPILED TO BYTECODE — ON A SHARED CRANELIFT JIT]`
> *"V8 compiles JavaScript to its own bytecode and JITs it with its own engine.
> node-js lowers JavaScript to a shared machine that other languages already run
> on, and lets one Cranelift JIT compile the hot loops."*
**node-js** is JavaScript as a [`fusevm`](https://github.com/MenkeTechnologies/fusevm)
frontend — a lexer/parser and compiler that lowers JavaScript to `fusevm::Chunk`
bytecode running on fusevm's bytecode VM + Cranelift JIT, over a `JsHost` object
heap. There is no bespoke interpreter loop: node-js is a pure front end;
execution and codegen live in `fusevm` — the same engine behind
[`zshrs`](https://github.com/MenkeTechnologies/zshrs),
[`strykelang`](https://github.com/MenkeTechnologies/strykelang),
[`awkrs`](https://github.com/MenkeTechnologies/awkrs),
[`pythonrs`](https://github.com/MenkeTechnologies/pythonrs), and
[`rubylang`](https://github.com/MenkeTechnologies/rubylang).
The binary is `node`.
### [`Read the Docs`](https://menketechnologies.github.io/node-js/) · [`Engineering Report`](https://menketechnologies.github.io/node-js/report.html) · [`Builtin Reference`](https://menketechnologies.github.io/node-js/reference.html) · [`fusevm`](https://github.com/MenkeTechnologies/fusevm)
---
## Table of Contents
- [\[0x00\] Overview](#0x00-overview)
- [\[0x01\] Pipeline](#0x01-pipeline)
- [\[0x02\] Usage](#0x02-usage)
- [\[0x03\] Supported Today](#0x03-supported-today)
- [\[0x04\] Not Yet (Later Waves)](#0x04-not-yet-later-waves)
- [\[0x05\] Parity Harness & Fuzzer](#0x05-parity-harness--fuzzer)
- [\[0x06\] Build](#0x06-build)
- [\[0x07\] Documentation](#0x07-documentation)
- [\[0xFF\] License](#0xff-license)
---
## [0x00] OVERVIEW
node-js keeps JavaScript the language and throws away V8's execution model. It
lexes and parses JavaScript to an AST, lowers the AST to `fusevm` bytecode, and
runs it on the shared bytecode VM with a Cranelift JIT. Arithmetic and
comparisons lower to native ops so the JIT can trace hot loops; JS-specific
behavior — truthiness, `==` coercion, `+` overloading, `ToInt32` bitwise wrap,
number formatting, the builtin objects — is served by the `JsHost` object heap
through fusevm's builtin dispatch and a strict numeric hook.
It carries no VM or JIT of its own. Bug fixes and JIT improvements in `fusevm`
land once and benefit every hosted frontend at the same time.
## [0x01] PIPELINE
```
source ──▶ lexer ──▶ parser ──▶ compiler ──▶ fusevm::Chunk ──▶ fusevm VM + JIT
│ │ │ │
tokens JS AST lower to bytecode callbacks into JsHost
(+ template (funcs, (native ops + CallBuiltin) (builtins + numeric hook)
re-lex) arrows,
try/catch)
```
- **Primitives** (`number`, `boolean`, `null`, `undefined`) ride through the VM
as native `fusevm::Value`s.
- **Objects, strings, and arrays** are heap objects in `JsHost`; they travel as
`Value::Obj(u32)` handles into that heap, and property insertion order is
preserved (observable in iteration and `JSON` round-trips).
- **Locals** that no other chunk can reach are addressed as fusevm frame slots
(`Op::GetSlot`/`SetSlot`) instead of being looked up by name in the scope
chain — see [`src/slots.rs`](src/slots.rs) for the rules that decide which
ones qualify. A name a nested function, a `try` block or a direct `eval` can
reach stays a real binding, because those run as their own chunks and resolve
through the environment.
- **Loops are lowered rotated** — the test emitted once as an entry guard and
once at the bottom as a *conditional* backward branch, which is the shape
fusevm's tracing JIT needs to close a trace. `for (;;)`, having no test to
branch on, closes with a constant-true conditional branch for the same reason:
the trace compiler declines an unconditional `Jump` close. `node --tiers`
reports which loops reached native code.
- **Arithmetic** lowers to native fusevm ops so the JIT can trace hot loops; a
strict **numeric hook** supplies JS coercion for the non-numeric operand cases
(`+` string concat, `==` matrix, `ToInt32` for bitwise ops). An object operand
goes through a real `ToPrimitive` first — which calls the user's
`Symbol.toPrimitive`/`valueOf`/`toString`, so it runs before the host borrow
the numeric hook takes. Everything JS-specific lowers to `CallBuiltin`
handlers.
## [0x02] USAGE
```sh
node script.js # run a file
node -e 'console.log(1 + 1)' # evaluate a one-liner
node -p '6 * 7' # evaluate and print the result
echo 'console.log(6 * 7)' | node # read a script from stdin
node --tiers script.js # run it, then report which fusevm tiers took it
```
Errors go to stderr in terse `node: <reason>` form; nothing else is printed. A
program that ran to completion exits with `process.exitCode` if it set one, and
the `beforeExit`/`exit` events fire as they do in Node.
Runnable `examples/*.js` ship with the crate.
## [0x03] SUPPORTED TODAY
A working core, grown outward from the sibling frontends. Implemented end-to-end
(see `examples/*.js` and `tests/parity.rs`):
- `var` / `let` / `const`; block scoping; `var` and function-declaration
hoisting (a `var` is bound from scope entry, so a read above its
declaration is `undefined`); expression and block statements.
- Full operator surface: arithmetic (`+ - * / % **`), string `+`, comparison
(`== != === !== < > <= >=`), logical (`&& || !`), nullish `??`, bitwise
(`& | ^ ~ << >> >>>`), `typeof` / `void` / `delete` / `instanceof` / `in`,
conditional `?:`, sequence `,`, pre/post `++`/`--`, compound assignment.
- `if` / `else`, `while`, `do … while`, `for`, `for … in`, `for … of`,
`switch`, `break`, `continue`, `return`, `throw`, `try` / `catch` / `finally`.
- `function` declarations and expressions, **arrow functions** (with `=>`
lookahead detection), closures, recursion, `new`.
`Function.prototype.toString` returns each function's and class's own
source text, as V8 does: the lexer records token byte ranges and every
`FuncDef` keeps the span of its definition.
- Unicode source: identifiers may start with any letter (`const é`), the BOM,
NBSP and the other Unicode spaces are whitespace, and a leading `#!` line is
a comment in the entry script and in required modules.
- Array and object literals, member (`a.b`) and index (`a[i]`) access, spread
(`...`), **template literals** (`` `${...}` `` re-lexed from source).
- Builtin objects and methods on the `JsHost` heap: `console` (`log`), `Math`
(`floor`/`ceil`/`round`/`trunc`/`abs`/`sign`/`max`/`min`/`pow`/`sqrt`/`cbrt`/
`random`/`hypot`/`log`/`log2`/`log10`/`exp`/trig, `PI`/`E`), `JSON`
(`stringify`/`parse`), `Object` (`keys`/`values`/`entries`/`hasOwnProperty`),
`Array`, `Number` (`MAX_SAFE_INTEGER`/`EPSILON`/…), `String`, `Boolean`,
`parseInt`/`parseFloat`/`isNaN`/`isFinite`, and a broad array/string method set
(`map`/`filter`/`reduce`/`forEach`/`find`/`every`/`some`/`push`/`pop`/`slice`/
`join`/`concat`/`includes`/`indexOf`/`flat`/`flatMap`/`reverse`/`fill`/`at`,
`charAt`/`charCodeAt`/`padStart`/`padEnd`/`repeat`/`replace`/`replaceAll`/
`startsWith`/`endsWith`, …).
- Strings are indexed by **UTF-16 code unit**, as JS specifies, so a
supplementary-plane character counts as two: `"𝒳".length` is `2` and
`"ab𝒳cd".indexOf("c")` is `4`. `[Symbol.iterator]` still yields code points
(`[..."𝒳"]` is one element). `src/utf16.rs` is the single UTF-8 ⇄ UTF-16
boundary; see BUGS.md for the one remaining gap (a value holding an unpaired
surrogate, which a Rust `String` cannot represent). The same unit count drives
relational comparison and the default `sort` order (an astral character sorts
BELOW every BMP character from `U+E000` up), and the Buffer encodings defined
over code units — `utf16le`/`ucs2` and the low byte each unit contributes to
`latin1`/`ascii`.
- Annex B `escape`/`unescape` and ES2024
`String.prototype.isWellFormed`/`toWellFormed`.
- `class` declarations and expressions: inheritance and `super`, static and
instance fields, getters/setters, private `#` names, and a class body that
evaluates in its own environment (so a static initializer can name its class).
- `async` / `await` and the microtask queue, generators and `yield` /
`yield*`, async generators and `for await`, `Promise` (including
`all`/`allSettled`/`race`/`any`).
- Destructuring patterns (array, object, nested, `...rest`) with defaults;
default and rest parameters; labeled `break`/`continue`.
- `RegExp` (literals and constructor, named groups, the `String.prototype`
regex methods), `Map` / `Set` / `WeakMap` / `WeakSet`, `Symbol`, `BigInt`,
typed arrays and `Buffer`. `Set` carries the ES2025 set operations —
`union`/`intersection`/`difference`/`symmetricDifference`/`isSubsetOf`/
`isSupersetOf`/`isDisjointFrom` — including the set-like operand form
(`{ size, has, keys }`), so a user-written set works on the right of each.
- `Proxy` — all thirteen traps plus `Proxy.revocable` — and the full `Reflect`
namespace. A proxy intercepts the OPERATORS, not just the `Reflect` calls:
reads, writes, `in`, `delete`, `for-in`, spread, `JSON.stringify`, iteration,
calls, `new`, `instanceof`, and use as a prototype or a superclass. `BUGS.md`
maps each trap to the syntax that reaches it, and states the two divergences.
- CommonJS `require` and the Node standard library — see `BUGS.md` for the
module-by-module coverage list and the honest not-implemented set.
- The persistent bytecode cache runs on EVERY invocation (schema-versioned, so
an older cached script never replays incompatible bytecode), and AOT
native-executable emission is on the CLI as `--build`. The shard is read
zero-copy: the archive is indexed in place and only the entry about to run is
decoded, so a lookup costs the same whether the cache holds one script or a
thousand. Measured interleaved against the previous read-and-deserialize-all
design on a 2.3 MB shard (debug build), a cache-hit run went from 78.8 ms to
51.5 ms against a ±2.0 ms same-binary control.
- An LSP server (`--lsp`) and a DAP debug adapter (`--dap`) — source-line and
function breakpoints, stepping, call stack, locals, and expression
`evaluate` — are wired.
- **Running out of stack is a catchable error.** A JS call is a Rust recursion
(each one builds a `fusevm::VM` on the stack), so the program runs on a
dedicated deep-stack thread and every nested run checks the live stack pointer
against the running stack's real bounds. Unbounded recursion — direct, through
a recursive `valueOf`/`toString`, or inside a generator body on its own
coroutine stack — raises `RangeError: Maximum call stack size exceeded`, the
error V8 raises, rather than aborting the process. Depth is a byte budget, not
a frame count, so it tracks the build's real frame size; BUGS.md records the
measured numbers.
## [0x04] NOT YET (LATER WAVES)
ES modules: the static `import`/`export` forms do not parse, so every module
boundary has to go through CommonJS `require`. The file EXTENSION is not
consulted — a `.mjs` file holding only CommonJS-compatible code runs — so what
fails is module syntax, not the suffix. Dynamic `import()` does parse (it lexes
as an ordinary call) and fails at run time with
`ReferenceError: import is not defined`. `Intl` is absent by design rather than by
omission — the reasoning is in `BUGS.md`, which also lists the remaining
behavioural divergences from the reference `node`.
## [0x05] PARITY HARNESS & FUZZER
Two differential tools check node-js against the reference `node`.
**`parity`** runs a fixed corpus through node-js and the reference `node`,
diffing stdout. It is a development tool — generating expectations needs `node`
on `PATH`, so CI never runs it; the frozen outputs live in
`tests/data/parity_expected.txt`, which `tests/parity.rs` replays with no `node`
installed.
**`parity-fuzz`** generates thousands of deterministic-output JS snippets and
diffs `node -e` against the reference `node -e`, delta-debugging every divergence
to a minimal repro. It is subprocess-only (never links the lib), std-only (no
`rand`), and needs `node` on `PATH`, so CI never runs it.
**`gen-arity`** regenerates `src/arity.rs`, which holds four tables: the `name`
and `length` of every ECMAScript intrinsic function, keyed the way this
frontend names its builtins; the own members of every intrinsic prototype —
string-keyed and symbol-keyed alike, the latter under the same internal `@@name`
spelling; which of those members are ACCESSORS; and which are NON-WRITABLE.
The last three are separate questions from the first: `Map.prototype.size` is
an accessor and `Array.prototype[Symbol.iterator]` a symbol-keyed one, and
neither is derivable from a list of functions.
Those values are normative — ECMA-262 gives each intrinsic a `length` — so the
reference engine is a transcription source here rather than an oracle to agree
with, and the generated table is checked in (`cargo run --bin gen-arity >
src/arity.rs`). The core-module surface is deliberately excluded: `fs
.readFileSync.length` is a property of node's own JavaScript, not of any
specification.
The two tools deliberately drive DIFFERENT entry points — the corpus runs each
case as a script FILE, the fuzzer through `-e` — because Node itself answers
differently at each (`__filename`, `module.id`, `process.argv`,
`process.execArgv`, top-level `this`). BUGS.md tabulates the full set; a case
that touches any of it is measuring one entry point, not "node".
```sh
cargo build --bin parity --bin parity-fuzz
./target/debug/parity # run the corpus vs reference node
./target/debug/parity-fuzz --count 5000 # fuzz 5000 cases
./target/debug/parity-fuzz --once --seed 1234 # replay one case, show both sides
```
All three compare the exit STATUS exactly rather than as zero-vs-nonzero, and
run both children with `TZ=UTC` and `LANG=LC_ALL=en_US.UTF-8` pinned rather than
inherited: reference `node` is not locale- or TZ-invariant, and a status
collapsed to a boolean cannot see `process.exitCode`, which prints nothing.
`parity --bless` re-records the frozen snapshot from the REFERENCE process — the
only supported way to regenerate it, and never from node-js's own output.
A third harness, `parity-scripts/run.sh`, byte-compares every
`parity-scripts/**/*.js` file against the reference `node` (stdout AND exit
status) and prints the pass rate:
```sh
bash parity-scripts/run.sh # byte-parity rate over the whole corpus
bash parity-scripts/run.sh -v # plus a diff for each divergence
```
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, JSON round-trips and parse-error
messages, property descriptors and the enumeration surface that depends on them,
`freeze`/`seal` write and `delete` outcomes, builtin identity and prototype-chain
reads, `structuredClone`'s reference graph, error own-property shape, abrupt
completions (`unwind`), and promise-resolution / async-iteration microtask
ordering (`thenable`). Select one with `--mode <name>`.
The run summary reports four counts next to the divergence total: **ref timeout**
(the reference timed out, so the case is skipped entirely), **ref failed** (the
reference exited non-zero), **ref silent** (the reference printed nothing on
stdout) and **ref inert** (both — the only condition under which a case observed
nothing at all). A mode scoring zero divergences while **ref inert** is high is
comparing nothing; `ref failed` on its own is not that signal any more, since
the exit code is itself a compared value and the `exit` mode consists of
programs that print nothing and exit non-zero deliberately.
## [0x06] BUILD
```sh
cargo build
cargo test
```
node-js is a standalone crate (an explicit empty `[workspace]` stops cargo
walking up to the meta parent). `fusevm` is pulled from crates.io with the `jit`,
`jit-disk-cache`, `aot`, and `ffi` features.
## [0x07] DOCUMENTATION
- **Docs hub** — <https://menketechnologies.github.io/node-js/>
- **Builtin reference** — <https://menketechnologies.github.io/node-js/reference.html>
- **Engineering report** — <https://menketechnologies.github.io/node-js/report.html>
- **fusevm** — <https://github.com/MenkeTechnologies/fusevm> (the shared VM)
- **Source** — <https://github.com/MenkeTechnologies/node-js>
## [0xFF] LICENSE
MIT — free and open source. See [LICENSE](LICENSE).