Commit a52bdc8
Compile KL to a bytecode VM (~15× faster tak, ~60× faster tail loops) (#50)
* Phase 0+2: bytecode VM compiler, float comparison fix
Phase 0 — Correctness fix:
- Fix <, <=, >, >= to use mustNumber() instead of mustInteger(), so float
comparisons like (< 1.5 1.8) return true correctly. Regression test added.
Phase 2 — Bytecode VM:
- kl/compiler.go: KL → bytecode compiler. Handles defun, lambda, freeze,
let, if, and/or, cond, do, type, trap-error, function calls (tail + non-tail).
Closures capture upvalues by value at closure-creation time.
- kl/vm.go: stack-based VM with per-call flat locals[] frame. Opcodes:
LOAD_CONST, LOAD_LOCAL, STORE_LOCAL, LOAD_GLOBAL, LOAD_UPVAL, CALL,
TAIL_CALL, RETURN, JUMP, JUMP_FALSE, MAKE_CLOSURE, POP.
- eval.go: defun special form now compiles to bytecode (CompileFunc).
apply() has a fast path for scmBytecodeFunc. Try() no longer requires
scmNative, accepting any callable (enables trap-error in compiled code).
scmHeadBytecodeFunc is self-evaluating.
- primitives.go: primDefun compiles scmProcedure → bytecode on binding.
PrimStr handles scmBytecodeFunc.
- library.go: equal() handles scmBytecodeFunc (pointer equality).
- types.go: ObjString handles scmBytecodeFunc.
Benchmark (tak 18 12 6):
Baseline (tree-walker): 0.088s
Phase 2 (VM): 0.013s → 6.6× speedup
Tail-call loop (sum 0 5000000):
Baseline: ~3.0s
Phase 2: ~0.24s → 12.5× speedup
All kl unit tests pass (29/29).
https://claude.ai/code/session_01RFe4YsuWLhZMdG5XDUewiq
* Phase 3+5: arithmetic fast paths and self-tail-call loop
Phase 3 — Arithmetic intrinsics:
- Compiler detects calls to +, -, *, <, <=, >, >=, =, not and emits
specialized opcodes (OP_ADD, OP_SUB, OP_MUL, OP_LT, OP_LE, OP_GT,
OP_GE, OP_EQ, OP_NOT) that bypass the trampoline entirely.
- Fixnum fast paths in numAdd/numSub/numMul/numCmp/numCmpLE avoid
float64 boxing for the common integer case.
- Symbols for each intrinsic are interned at startup (symAdd, symSub, …).
Phase 5 — Self-tail-call loop:
- OP_SELF_TAIL_CALL: when the compiler detects a recursive call to the
same function in tail position (with the same arity), it emits args
onto the stack then OP_SELF_TAIL_CALL N. The VM copies the new args
into locals[0..N-1] and resets pc=0 — no trampoline round-trip, no
new locals allocation.
Benchmark update:
tak(18,12,6): 0.006s (was 0.088s baseline → 14.7×)
sum(0, 5M) loop: 0.05s (was 2.99s baseline → 60×)
fib(30): 0.29s
All 29 unit tests pass.
https://claude.ai/code/session_01RFe4YsuWLhZMdG5XDUewiq
* Fix three semantic regressions from code review + cleanup
P1: compileDefun now threads the outer compiler so nested defuns can
close over lexical variables from the enclosing scope.
P1: OP_JUMP_FALSE panics on non-boolean values instead of treating
anything non-False as truthy, matching KL strict boolean semantics.
P2: In call position, a symbol with a global function binding now takes
precedence over a same-named local variable, matching the interpreter's
evalFunction lookup order.
Cleanup: remove unused isBytecodeFunc, drop unused upvals param from
vmPartialApply, replace numCmp(x,y,-1) with a dedicated numCmpLT,
fix OP_EQ comment ("numeric only" → "structural equality").
Tests: add 6 new TestBytecodeVM cases covering each regression and
multi-level closure chains, over-application, and float comparisons
through compiled defuns.
https://claude.ai/code/session_01RFe4YsuWLhZMdG5XDUewiq
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7a6a67a commit a52bdc8
11 files changed
Lines changed: 1290 additions & 20 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
0 commit comments