JetScheme
… is a fast Scheme interpreter.
git clone https://github.com/pinecone/JetScheme.git
cd JetScheme
make test
build/jet some-file.ss
build/jet --help
Benchmarks
make ab-cross-bench # jet and rivals all built with -O2
| runtime in ms commit 0e60a80โ | ๐ MacBook Air (Apple M5) | ๐ฎ Steam Deck OG (AMD Zen 2) | ||||
|---|---|---|---|---|---|---|
| jet | lua5.5 | python3.14 | jet | lua5.5 | python3.14 | |
| cdjs | 60 | 111 | 163 | 158 | 357 | 354 |
| deltablue | 84 | 137 | 234 | 252 | 559 | 559 |
| richards | 70 | 128 | 197 | 188 | 284 | 401 |
| nbody | 249 | 364 | 602 | 527 | 898 | 1300 |
| tak | 120 | 259 | 444 | 299 | 618 | 863 |
| json | 88 | 143 | 150 | 259 | 396 | 312 |
| fib | 135 | 248 | 507 | 364 | 706 | 1103 |
| splay | 26 | 44 | 42 | 61 | 134 | 68 |
| overall jet isโฆ | 1.75ร faster | 2.60ร faster | 1.90ร faster | 2.06ร faster | ||
Docs
Language
The language tracks R7RS-small with the following deviations:
All numbers are 64-bit doubles. No exact/inexact tower, no rationals, no bignums. exact? is true for integral doubles. integer? likewise.
Strings and characters are byte-level. Storage is UTF-8; string-length, string-ref, string-set!, char->integer are byte-indexed. Bytewise compare gives correct codepoint order on valid UTF-8. Char predicates, char-upcase/-downcase, string-upcase/-downcase are ASCII-only.
SRFI
SRFI 1. first, second, third, fourth, fifth, take, drop, last, concatenate, fold, fold-right, reduce-right, filter.
SRFI 60. bitwise-and, bitwise-ior, bitwise-xor, bitwise-not, arithmetic-shift. Operate on integral doubles.
Structs
struct, isa?. Each (struct ...) call yields a distinct type, even when invoked twice with the same name and field list.
(define point (struct 'point '(x y))) ; create a fresh type
(define p (point 3 4)) ; call the type to construct
(isa? p point) ; => #t
(ref p 'x) ; => 3
(setf! (ref p 'x) 10) ; mutate field
Places
ref. (ref v i) is a polymorphic accessor. It works on vectors and strings (integer index) and structs (symbol field name).
setf!. Mutates the place denoted by its first argument:
(setf! (ref v 3) 'x) ; vector-set!
(setf! (ref s 0) #\H) ; string-set!
(setf! (ref pt 'x) 99) ; struct field mutation
OS env
argv. Vector of strings holding the script's command-line arguments (excluding the binary and script path).
$JET_PRELUDE/lib/prelude.ss is the bundled prelude, auto-loaded unless --no-prelude is given.
Impl
JetScheme leans heavily on techniques described in:
- Tail-threaded dispatch (Bell, 1973) โ each opcode is its own small function; each function ends by tail-calling the next opcode's function directly. There's no central dispatch loop and no callee-save spills between opcodes; hot VM state stays in registers across the whole run.
- Inline caching (Deutsch & Schiffman, 1984) โ on the first time through a call site, the slow path patches the opcode in place to point at a specialized fast version of itself. Subsequent passes through the same site skip the lookup, key compare, and type check entirely.
- Flat closures (Dybvig, 1987) โ captures live in an inline array on the closure; immutable captures by value, mutated and shared ones get 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, so the later passes (inlining, stackification, instruction selection) rewrite one flat shape instead of arbitrary nested trees.
- NaN-boxing (Gudeman, 1993) โ every value fits in 8 bytes: numbers are raw doubles; immediates (bool, char, empty list) and tagged heap pointers all 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 a single fused opcode: one chain step instead of two, and the body can skip the intermediate push and pop on the value stack.
- Opcode replication for ICs (Ertl & Gregg, 2003) โ each cached opcode has several near-identical copies that rotate per call site, so distinct sites land on distinct branch-target-predictor entries instead of all colliding on one.