JetScheme
git clone https://github.com/pinecone/JetScheme
cd JetScheme && make
A small, fast Scheme interpreter in ~10k lines of C++. It has lambdas, proper tail calls, escape continuations, coroutines, structs, tuples, hash sets and maps, and mutation-safe iteration. JetScheme is in pre-alpha. It is thus unfit for any particular use (other than working on JetScheme).
Run make ab-cross-bench to compare Jet with popular language
runtimes on your machine:
python
2.7×
lua
2.3×
jet
1×
jsc
2.6×
← slowerfaster →
| benchmark | LLInt | python3.14 | lua5.5 | jet | jsc | |||||
|---|---|---|---|---|---|---|---|---|---|---|
| 🍎 | 🎮 | 🍎 | 🎮 | 🍎 | 🎮 | 🍎 | 🎮 | 🍎 | 🎮 | |
| astar | 76 | 273 | 153 | 452 | 61 | 215 | 50 | 121 | 33 | 96 |
| basic | 3921 | 14643 | 3190 | 7426 | 2678 | 7486 | 998 | 3045 | 250 | 892 |
| cdjs | 98 | 311 | 160 | 357 | 109 | 337 | 48 | 121 | 22 | 51 |
| deltablue | 240 | 825 | 231 | 569 | 124 | 566 | 62 | 199 | 27 | 73 |
| havlak | 241 | 1204 | 277 | 825 | 319 | 1496 | 166 | 794 | 64 | 254 |
| json | 331 | 1146 | 147 | 312 | 135 | 397 | 67 | 185 | 34 | 107 |
| nbody | 415 | 1174 | 597 | 1299 | 334 | 899 | 162 | 368 | 23 | 60 |
| raytrace | 632 | 2471 | 515 | 1308 | 513 | 1488 | 119 | 443 | 42 | 112 |
| richards | 117 | 373 | 191 | 406 | 120 | 287 | 53 | 140 | 18 | 34 |
| splay | 25 | 94 | 39 | 68 | 43 | 139 | 19 | 46 | 15 | 32 |
| total | 6096 | 22514 | 5500 | 13022 | 4436 | 13310 | 1744 | 5462 | 528 | 1711 |
🍎MacBook Air (M5)
🎮Steam Deck OG (Zen 2)
ms, median of 20 runsmake ab-cross-bench at a751992↗
Impl
JetScheme is built on techniques described in:
- Tail-threaded dispatch (Bell, 1973) — each opcode is its own small function, and each function ends by tail-calling the next one directly. There is no central dispatch loop and no callee-save spills between opcodes, so hot VM state stays in registers across the whole run.
- Inline caching (Deutsch & Schiffman, 1984) — the first time through a call site, the slow path patches the opcode in place to point at a specialized fast version of itself. Later passes through that site skip the lookup, key compare, and type check entirely.
- Flat closures (Dybvig, 1987) — captures live in an inline array on the closure. Immutable ones are copied by value; a capture that is both mutated and shared gets a heap indirection.
- A-normal form (Flanagan et al., 1993) — every operand is a variable or a literal, and every intermediate result gets an explicit name. The later passes — inlining, register allocation, instruction selection — then rewrite one flat shape instead of arbitrary nested trees.
- NaN-boxing (Gudeman, 1993) — every value fits in 8 bytes. Numbers are raw doubles, while immediates and tagged heap pointers hide in the IEEE 754 quiet-NaN payload. Type tests are cheap bit ops, and any value passes in a single register.
- Superinstructions (Proebsting, 1995) — common back-to-back opcode pairs are emitted as one fused opcode. That is a single chain step instead of two, and the body can skip the intermediate register move.
- Opcode replication for inline caches (Ertl & Gregg, 2003) — each cached opcode has several near-identical copies that rotate per call site. Distinct sites then land on distinct branch-target-predictor entries instead of all colliding on one.