Skip to content

aowljs-engine — a JavaScript engine in nimony ​

aowljs-engine runs JavaScript: a parser, a bytecode interpreter and a baseline x86-64 JIT, written in nimony. (Its sibling aowljs goes the other way and compiles nimony to JavaScript.)


Conformance ​

test262, the ECMAScript conformance suite:

SuitePassed
language + built-ins + annexB48,631 / 48,631
intl4023,365 / 3,365
staging1,481 / 1,483
all53,477 / 53,479

The result is the same with the JIT off, on (hot functions), and forced on for every function (--jit=always). The two failures are SpiderMonkey tests under staging/sm that require the opposite of test262's own annexB/language/function-code/block-decl-func-skip-arguments.js; passing them would fail that test.

Covered: ES2025 plus the stage 3/4 features test262 tests — modules and top-level await, Temporal, ECMA-402 Intl (all constructors), decorators, explicit resource management (using), ShadowRealm, iterator helpers, RegExp v flag with Unicode 17 tables, SharedArrayBuffer and Atomics across agents.

Performance ​

Sixteen programs from tests/engine/perf (loops, calls, closures, objects, arrays, strings, JSON, RegExp, Map/Set, a ray tracer, SHA-256, a functional pipeline). CPU seconds, best of three, one machine, x86-64 Linux (WSL2):

Benchmarkaowljs-engine JITaowljs-engine interpV8 --jitlessQuickJS-ngQuickJSV8
array0.381.070.460.581.640.14
closure0.471.240.420.431.060.06
fib0.080.260.120.100.120.04
functional0.410.480.210.530.810.16
json0.270.290.280.840.810.25
loop0.672.670.740.792.980.12
mapset0.210.210.120.550.190.09
nbody0.572.241.821.621.550.10
objmap0.460.450.380.290.420.34
props0.480.690.250.650.800.05
raytrace0.190.310.180.300.310.08
regexp0.110.110.060.060.110.06
richards_lite0.190.430.230.250.420.04
sha0.591.660.600.761.000.06
sort0.400.560.460.430.480.27
string0.200.270.180.150.230.13
total5.6612.926.498.3212.922.00

Versions: node 24.21.0 (V8), QuickJS 2025-09-13, quickjs-ng at its current main. Each figure includes the engine's own start-up.

Read it as: with the JIT, aowljs-engine is ahead of both QuickJS builds and of V8's interpreter (Ignition) in total, and about 2.8× behind V8 with its optimizing compilers. Its interpreter alone is level with QuickJS. It is weakest, relative to V8's interpreter, on allocation-heavy functional code (object rest, many small callbacks) and on props.

Reproduce:

python3 tests/engine/perf/run.py --runs 3 \
  --config "jit=@$HOME/jsengine --jit=on" --config "qjs=@/path/to/qjs" \
  --config "v8=@/path/to/node"

How it is built ​

  • Values: NaN-boxed 64-bit words. Numbers are doubles; strings are one-byte or UTF-16, and long concatenations are ropes, flattened on first read.

  • Objects: hidden classes (shapes) shared along transition trees rooted per prototype; a delete, an attribute change or more than 128 properties gives an object a dictionary shape of its own. The first four property values live in the object itself.

  • Inline caches on every property read, write and global-name access: own, prototype, getter, setter, add-a-property and absent entries, validated by shape and a prototype epoch.

  • Interpreter: a stack bytecode VM. JavaScript calls do not recurse in the host; generators and async functions suspend by copying their frame out.

  • JIT: a template compiler over the interpreter's own frames, so either tier can hand a function to the other at any instruction. Property and global caches, array elements, closure variables and arithmetic are compiled inline; compiled functions call each other directly. --jit-stats lists what still falls back to the interpreter.

  • GC: stop-the-world mark and sweep over cells, strings and shapes, with weak collections, WeakRef and FinalizationRegistry.

  • Libraries: RegExp is regex, Unicode data comes from unicode, and the JIT's assembler, executable memory and register allocator are extracted as jit.

The invariants behind all of this are in the repo's src/engine/README.md.

Build and run ​

Inside Linux or WSL, with a nimony toolchain at ~/nimony:

tests/engine/build.sh -d:danger          # -> ~/jsengine  (OUTBIN=... to change)
~/jsengine [--jit=off|on|always] [--jit-stats] [--dis] a.js b.js

-d:release keeps nimony's runtime checks on, for development. Scripts given together share one global; --module-last runs the last file as a module.

Tests ​

python3 tests/engine/test262.py language built-ins annexB intl402 staging \
        -j 8 [--args=--jit=always] --fails /tmp/fails.txt   # needs ~/test262

aoughwl — self-hosted platform for things n stuff. Contact / Support on Discord for access to the private backends.