A tracing JIT for Wren, with SLJIT underneath.
It is small on purpose. Hot loops become typed SSA traces. Exact recursive kernels become native functions. Every speculation has a guard and every guard has an interpreter snapshot. If the proof stops holding, Wren continues.
Apple M4 Pro, best of five self-timed runs, 2026-08-07. Lower is better. Each Wren benchmark has a Lua version doing the same work. The Wren timer includes JIT warmup and compilation; process startup is excluded.
| benchmark | WrenJIT | LuaJIT | Wren/Lua |
|---|---|---|---|
| sum | 0.641 ms | 1.092 ms | 0.59× |
| range for | 0.074 ms | 0.584 ms | 0.13× |
| fib | 39.4 ms | 51.9 ms | 0.76× |
| tak | 2.224 ms | 6.361 ms | 0.35× |
| ack | 0.024 ms | 1.066 ms | 0.02× |
| mutual recursion | 0.098 ms | 0.321 ms | 0.31× |
| deep recursion | 0.625 ms | 6.724 ms | 0.09× |
| binary trees | 8.807 ms | 19.8 ms | 0.45× |
| method call | 0.087 ms | 0.448 ms | 0.19× |
These are narrow kernels, not a language-wide victory lap. They are useful because they make bad machinery impossible to hide. See the full report for charts and methodology.
The loop path is conventional:
- Count backward
LOOPbytecodes per function and PC. - Record one hot iteration as typed SSA.
- Optimize it.
- Allocate GP and FP registers with linear scan.
- Emit native code through SLJIT.
- Run until a guard fails, restore a snapshot, and resume the interpreter.
The optimizer does loop-variable and numeric-stack promotion, box elimination, constant folding, mutable-memory-safe GVN, LICM, guard hoisting, strength reduction, bounds-check elimination, field forwarding, DCE, integer induction inference, comparison/guard fusion, and guarded recurrence fast-forwarding.
The recursive path is intentionally stricter. It recognizes complete bytecode shapes, validates constants, symbols, arity, receiver class, and constructors, then emits a native implementation. Current kernels cover binary Fibonacci, linear recursion, mutual parity, Ackermann, Takeuchi, and guarded binary-tree construction/traversal. Anything else stays in Wren.
Two details matter:
- Mutable loads are never commoned without an alias proof.
- Native code uses Wren's actual NaN-box tags; copied tag numbers are bugs.
Requires CMake 3.16+, a C99 compiler, and Git submodules.
git clone --recurse-submodules https://github.com/Lulzx/WrenJIT.git
cd WrenJIT
./scripts/setup.sh
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failurescripts/setup.sh applies the guarded VM hooks from
patches/0001-wren-jit-hooks.patch to the pinned Wren checkout. Re-running it
is safe. -DWREN_JIT=OFF compiles the hooks out.
Pinned dependencies:
| dependency | commit |
|---|---|
| wren-lang/wren | 99d2f0b |
| zherczeg/sljit | d9902b1 |
./build/wrenjit_cli program.wren --jit
./build/wrenjit_cli program.wren --no-jit
WREN_JIT_DEBUG=1 ./build/wrenjit_cli program.wren --jit
WREN_JIT_DUMP_IR=1 ./build/wrenjit_cli program.wren --jitpython3 bench/run_benchmarks.py \
--benchmarks bench_sum,bench_for,bench_fib,bench_tak,bench_ack,bench_mutual,bench_deep,bench_trees,bench_method_call \
--binary ./build/wrenjit_cli --modes both --luajit luajit --trials 5Regenerate the published report:
python3 bench/run_benchmarks.py \
--benchmarks bench_sum,bench_for,bench_fib,bench_tak,bench_ack,bench_mutual,bench_deep,bench_trees,bench_method_call \
--binary ./build/wrenjit_cli --modes both --luajit luajit --trials 5 \
--json --output bench/results.json
python3 bench/gen_report.py bench/results.json --output docs/index.htmlRun the ten-program Computer Language Benchmarks Game comparison:
python3 bench/run_benchmarks.py \
--suite benchmarksgame --binary ./build/wrenjit_cli \
--modes both --luajit luajit --trials 5These are paired, scaled local ports rather than submissions measured by the Benchmarks Game host. Output-heavy programs emit checksums so terminal speed does not dominate. Since Wren has no regex or bignum module, regex-redux uses the same explicit mask matcher in both languages and pidigits uses the same array spigot in both languages.
src/jit/wren_jit_trace.c bytecode recorder
src/jit/wren_jit_ir.c SSA IR
src/jit/wren_jit_opt*.c optimizer
src/jit/wren_jit_regalloc.c linear scan
src/jit/wren_jit_codegen.c SLJIT backend
src/jit/wren_jit_recursion.c guarded recursive kernels
patches/ Wren VM integration
bench/ paired Wren/Lua workloads
docs/ generated performance report
- One trace per loop PC; no side traces or trace chaining.
- Non-numeric user methods are widened only for proven bytecode shapes.
- Recursive compilation is shape-based, not a general method JIT.
- ARM64 and x86-64 are the tested targets.
MIT.