Adds a transparent S-expression relay (examples/rpc-relay.lsp) plus a
sequential load generator (examples/rpc-chain-bench.lsp) and a bench
script (tests/rpc-chain-bench.sh) that wires them into multi-hop
chains across runtimes.
The relay is pure byte-forwarding: tcp-accept, tcp-recv, tcp-connect
to backend, tcp-send, tcp-recv reply, tcp-send back. Never parses.
Which is the point — S-expressions are the envelope.
Same rpc-relay.lsp runs as relay in any impl; chains are arbitrary
combinations of {Py, C, asm} nodes.
Measured (200 requests, ping, same laptop):
(A) Py client → asm backend direct, 1 hop 2061 rps
(B) Py client → C relay → asm 2 hops 1234 rps
(C) Py client → Py → C → asm 3 hops 766 rps
(D) asm client → Py → C → asm 3 hops 796 rps
Per-hop cost ≈ 600-700 µs/request (TCP round-trip + context switch).
Safety: every server spawn used the six-layer pattern from CLAUDE.md
(ulimit -v 512MB + timeout 30 + trap + explicit kill + pgrep verify).
Four benchmark cells × up to 3 servers each = 10+ server spawns.
Zero strays, zero safety-net activations.
Fuses portal (Scheme-source-as-interchange) with sockets (bytes over
the network). Wire protocol: one S-expression per connection. Same
server + client .lsp runs byte-identically in Python, C, and asm.
New primitives in all three impls:
- read-from-string — parse one sexp from a string
Asm gets two more:
- symbol->string — standard R7RS, was missing
- eval — evaluate a Scheme value in the global env (Python + C had
it as a special form; asm exposes it as a builtin)
examples/rpc-server.lsp (port 9080):
- Whitelisted dispatch: ping / add / mul / fib / echo
- Never calls eval on client input; safe by construction
- Uses heap-snapshot/restore for O(1) memory on asm
- ~90 lines, portable
examples/rpc-client.lsp:
- Sends one request, reads one response, displays both
- 45 lines, portable
examples/repl-server.lsp (port 9081):
- DANGER: full remote eval. Any Scheme form accepted and evaluated
in the server's global env. Persistent across connections.
- Deliberately does NOT use heap-snapshot — remote (define x ...)
lives in the global env above any snapshot point; rewinding would
invalidate the new binding. The ulimit -v 512 MB safety net
(documented in CLAUDE.md) ensures an escaped process can't crash
the machine.
- ~70 lines, portable. Demonstrates what "the language IS the
interchange format" gets you at the limit: a single socket and
a single primitive (eval) carry a full-powered REPL.
Verified 3×3 server×client matrix: all 9 combinations green.
All 132 asm + 571 py + 189 shared + 83 c tests still pass.
One quirk discovered and worked around: in asm, a closure captures
its env chain by pointer at define time. Forward-referenced names
in mutually-recursive toplevel defines can fail under specific
heap-restore patterns — see the leaf-first ordering note in
rpc-server.lsp.
Other agents share this machine. An OOM crash takes their state
down too, not just mine. The earlier "trap + pgrep verify" pattern
depended on me not forgetting; two crashes proved that's not enough.
Adds a mandatory `ulimit -v 524288` (512 MB virt) to the pattern
alongside the existing timeout + trap + verify. The kernel kills
any rogue process at the cap, so even if all three earlier layers
fail simultaneously, shared RAM is untouched.
Verified: an asm process in a runaway (cons n ...) loop SIGKILL'd
by the kernel within ~1 second when capped at 512 MB, before
timeout 5 fired.
Also noted:
- ulimit only affects the current shell + children, so it cannot
degrade other agents.
- Use `pkill -u "$USER"` explicitly — don't pkill foreign processes.
- Prefer foreground runs with timeout over backgrounding when
feasible; the .lsp can exit on its own via *max-requests*.
Second crash on 2026-04-17 proved the earlier "Asm memory discipline"
section was aspirational. Rules were documented, blackops kept
spawning backgrounded asm servers for quick RSS checks during the
heap-snapshot and RPC work, and one escaped again.
This revision makes the discipline procedural:
- Wrap EVERY backgrounded asm spawn with `timeout N`, even for a
"quick test" — the mental overhead is zero; the consequence of
forgetting is a crashed machine.
- Trap EXIT/INT/TERM at the top of every block that backgrounds
anything, not just benchmark scripts.
- MANDATORY pgrep verification at the end of each block before
moving on. If the verification returns output, pkill and
investigate — do not proceed.
- The discipline applies to Python + C servers too (they block
ports even without the OOM risk).
Documented both crashes explicitly so future sessions know this
rule has teeth.
Major revision to reflect the HTTP server + heap-snapshot work.
Abstract:
- Updated counts: 87 asm builtins (incl. TCP stack), 45 KB binary,
4,527 asm lines, 132 asm tests, 975 total assertions.
- New "Web server, same story" paragraph with the 2,994 req/s number
and the 45 KB / 2.1 MB / 8 MB size comparison.
- Closing thesis line extended: "Sockets are its mechanism across
machines."
Section 4.3 renamed "Three Scopes" → "Four Scopes of Feedback",
adding the Across-machine row (TCP sockets, bytes over the wire).
Section 11 (Three Implementations):
- Asm binary column updated: 22 KB → 45 KB stripped.
- Asm description extended: thirteen syscalls now (socket family
added), mentions heap-snapshot and the 2,994 req/s HTTP result.
- Test counts refreshed.
New subsection 11.3 "Sockets: One HTTP Server, Three Runtimes" —
six tcp-* primitives table, cross-runtime benchmark (Python/C/asm
server and client combinations), 45 KB binary comparison vs
busybox (46×) and Python (176×).
New subsection 11.4 "heap-snapshot: The Arena Escape Hatch" —
documents the 2026-04-16 19 GB incident, the two new builtins,
the canonical pattern (snap captured AFTER top-level binds, passed
as explicit arg to loop fn), and the measured flat RSS at 100 KB.
Section 12.1 (MOAD-0001): added the two defects found and fixed
in this pass (bi_string_replace strncmp-per-position → strstr,
_tokenize_lines per-token count → bisect over precomputed offsets).
Section 13 (Future Work): removed "Distributed continuation passing"
(now possible via the HTTP stack — noted as "Portal over HTTP" first
item). Added WebSocket, copying GC in asm, and concurrent accept loop.
Three wins in one commit.
1) heap-snapshot / heap-restore (asm arena primitive)
asm has no GC. Long-running servers leaked ~64 MB per heap growth.
Two new builtins let a programmer capture r15 and later rewind to
it, recycling intermediate allocations in O(1) memory.
Python + C get no-op versions so portable .lsp code can call them
unconditionally.
examples/http-server.lsp now takes a snapshot at top level and
rewinds after every request. Measured asm RSS: 88 KB initial,
100 KB after 100 requests, 100 KB after 1100 requests — flat.
Prior behavior was +64 MB per few thousand requests.
2) examples/http-client-bench.lsp — native HTTP load generator
Uses only the six tcp-* primitives + current-time-ms. Runs
identically in all three impls. Eliminates curl's ~2 ms/req
fork+exec overhead, so real server throughput shows up:
Python server ← Python client 2403 rps
C server ← C client 2439 rps
asm server ← asm client 2994 rps
asm server ← C client 2500 rps
The earlier curl-based bench was clamped near 400 rps by the
client; the actual servers handle 6–7× that.
3) MOAD-0001 cleanup
- c/builtins.c bi_string_replace: strncmp-at-every-position
(hand-rolled, sedimentary) → strstr (libc-tuned, typically
Boyer-Moore-Horspool). O(N*k) → O(N + matches*k).
- uncommonlisp.py _tokenize_lines: per-token src.count('\n', 0, pos)
→ precompute line_starts once, bisect_right per token.
O(N*M) → O(M + N log M).
Also adds current-time-ms to all three impls so benchmarks can
time themselves without relying on the Python/C float `current-time`
(asm has no floats). Seconds-since-epoch tagged as a 61-bit int.
Test counts unchanged: 571 py + 132 asm + 189 shared + 83 c = 975.
All green via make test-all.
Prevent recurrence of 2026-04-16 incident where a leaked asm HTTP
server grew to 19.3 GB RSS and crashed the machine.
examples/http-server.lsp:
- Adds *max-requests* = 50000 hard ceiling. Server self-terminates
before unbounded heap growth reaches dangerous levels.
- Loop tracks request count, exits cleanly + closes server socket.
tests/web-benchmark.sh:
- SPAWNED_PIDS array tracks every background process.
- EXIT/INT/TERM trap kills them all (SIGTERM then SIGKILL).
- stop_server does SIGTERM with 500ms grace period then SIGKILL.
- Final straggler check via pgrep narrows to actual HTTP server
processes (not shell/tmux with "uncommonlisp" in the name).
- pkill -9 fallback as belt-and-suspenders.
CLAUDE.md:
- New "Asm memory discipline" section documents the bump allocator
leak behavior and the required operational discipline.
- Test counts updated (571 py + 83 c + 132 asm + 189 shared = 975).
Added tcp-listen/accept/connect/recv/send/close to Python, C, and asm.
One examples/http-server.lsp runs identically in all three impls and
serves HTTP/1.0 with routing, content-type, and content-length headers.
asm additions:
- SYS_SOCKET/BIND/LISTEN/ACCEPT/CONNECT/SETSOCKOPT syscalls
- 6 tcp-* builtins using the existing port encoding (SPECIAL ≥ 1000)
- bi_tcp_connect: dotted-quad IPv4 parser, no DNS dependency
Defects fixed along the way (surfaced by the HTTP server):
- string-append: was 2-arg only; now variadic (walks arg list twice)
- number->string: was stubbed to VAL_VOID; now correctly writes digits
into a heap-allocated string (incl. negative handling)
- String-literal reader: \r and \0 escape sequences now handled (was
silently dropping backslash, treating them as literal 'r' / '0')
- tcp_accept: sockaddr buffer was 8 bytes, now 16 (was corrupting
caller's stack when accept wrote full struct sockaddr_in)
Pinocchio benchmark (tests/web-benchmark.sh):
At concurrency=20, 1000 requests, serving a 1KB body:
uncommonlisp Python 373 req/s
uncommonlisp C 370 req/s
uncommonlisp asm 370 req/s
python3 http.server 381 req/s (stdlib reference)
busybox httpd 382 req/s (production reference)
All five converge within 3% — the client (curl fork/exec) is the
bottleneck, not the server. Our single-threaded blocking servers
are indistinguishable from battle-tested ones at this load.
Binary sizes:
uncommonlisp asm 45 KB (HTTP + everything else)
busybox httpd 2.1 MB (multi-call binary)
python3 8 MB (interpreter)
The asm HTTP server is 46× smaller than busybox and 176× smaller
than Python, serves from 7 Linux syscalls, and the entire protocol
handler is 70 lines of portable Scheme.
Test counts: 132 asm (up 1), rest unchanged. All green.
Major revision reflecting current state. The original paper argued
feedback (continuations) as a universal within-process primitive.
This revision extends the argument to two more scopes discovered
while building the cross-impl portal system:
- Within process: continuations (live stack)
- Across process: portals (serialized VM state)
- Across impl: Scheme source itself as wire format
Abstract rewritten around the three scopes. New benchmark numbers
(asm cross-process round-trip 1.5 ms vs Python 260 ms — 160×).
Section 4.3 "Three Scopes of Feedback" added — the key new framing.
Section 7 "Portal: Feedback Across Time" rewritten to cover all
three formats (S-expression portable, JSON graph-aware, binary
heap-dump) with the 3×3 cross-impl matrix, cross-process
benchmarks, and mismatch-case behavior table.
Section 11 "Three Implementations, One Language" updated with
current line counts (4,455 asm, 3,678 py), binary sizes (22 KB asm
stripped, 205 KB C), and builtin count (79 asm).
Section 11.2 "File I/O Parity" added — the minimal file vocabulary
that reaches parity across all three implementations, including the
asm port encoding trick (SPECIAL values ≥ PORT_SPECIAL_BASE).
Test counts: 974 verified assertions (was 943).
Benchmark exercises full save×load matrix across Python/C/asm plus the
mismatch cases (wrong format, truncated input, missing file, corrupt
header). Cases that used to segfault or report wrong paths now degrade
cleanly.
asm (uncommonlisp.s):
- (define var) with no value now binds to VOID instead of segfault
- portal-resume checks sys_read returned full 48-byte header; sanity-
checks heap_size and heap_base before committing r14/r15 restore.
Corrupt/truncated portals now return #f cleanly.
py (uncommonlisp.py):
- file-not-found error inside a nested (load) now reports the actual
missing path (via FileNotFoundError.filename) rather than the outer
script path.
- _load wraps UnicodeDecodeError (binary file loaded as text) into a
LispErr with the path; no more raw Python traceback.
tests/portal-benchmark.sh: 50-iter benchmark, 4 parts
(save / load / cross-process / mismatch-classification).
Representative numbers (this laptop, 2026-04-16):
setup+save: Python 126ms, C 3ms, asm 0.9ms
cross-proc: Py→Py 260ms, C→C 6ms, asm→asm 1.5ms
All three test suites still pass: 571 py unit, 131 asm, 189 shared.
portal-save: writes 48-byte header (magic, heap_size, heap_base, r14,
r15) + raw heap bytes via sys_write. 9.8KB for a state with fib(20).
portal-resume: reads header, remaps heap at saved base address via
MAP_FIXED so all pointers remain valid, restores r14/r15.
Binary format — no JSON, no parsing. Just bytes in, bytes out.
Carry on USB to air-gapped machine, resume from exact state.
Known limitation: builtins need re-init after resume (symbol table
lives in BSS, not heap). User-defined data survives intact.
108 existing tests pass.
5 new diagrams (Graphviz DOT → PNG):
benchmark-ack.dot — ack(3,4) across all 5 tiers
benchmark-sumto.dot — sum-to(50k) across all 5 tiers
benchmark-fib.dot — fib(35) iterative across all 5 tiers
benchmark-speedup.dot — JIT speedup ratios (7x-784x)
benchmark-binary-size.dot — 13KB asm vs 171KB C vs ~30MB Python
JIT: 0.19ms ack, 7x faster than CPython, 784x faster than Python VM.
Makefile docs target now auto-discovers all docs/*.dot files.
Fresh in-process benchmarks across all implementations:
JIT: ack 0.19ms, fib 0.09ms, sum 0.55ms
CPython: ack 1.3ms, fib 0.006ms, sum 5.5ms
C interp: ack 20ms, fib 0.06ms, sum 109ms
Python VM: ack 149ms, fib 0.75ms, sum 437ms
Assembly: ack 8ms, fib 0.6ms, sum 43ms
JIT runs Scheme 7-10x faster than CPython runs Python.
Updated: language identified as R7RS Scheme throughout.
Test count updated to 943 across all implementations.
Full continuations: FullCont captures frames/stack/env with deep copy.
VM trampoline via setjmp/longjmp. Multi-shot safe via deep_copy_env.
Portal (new c/portal.c): serialize env + continuation to JSON,
resume on another machine. portal-checkpoint! triggers mid-VM save.
83/83 C unit tests + 181/181 functional tests pass.
67 new tests covering: any/every/find/count/sort/iota/fold-right,
named let with multi-body, internal defines, letrec mutual recursion,
do loops with results, tail position in cond/when/unless/and/or,
nested closures with mutation, deep TCO at 200k depth, string ops
(contains/split/join/trim), apply, variadic args, quasiquote splicing,
set-car!/set-cdr!, list-tail, make-list, hash-table-delete!.
Both Python and C pass 181/181.
Preamble: updated to reflect four implementations (Python, C+JIT, asm).
Abstract: updated line counts and implementation descriptions.
License: full AGPL-3.0-only text with permacomputer preamble, NO WARRANTY,
copyright, and project URLs. Matching format from categorization paper.
Logos: permacomputer-logo.jpg and gnu-logo.png retained.
Citations: "Stress on Our Shared Heart" (russell@unturf, April 2026) confirmed.
Scanner renamed to unmoad (~/git/unmoad.com/). Run before every commit.
Machine learning agents propagate MOAD-0001 by default — the scanner
catches what our weights miss.
This paper is evidence that language models propagate MOAD-0001 by
default. The agent had full MOAD context and still wrote linear scans
in fresh code on April 13-14. The defect was only fixed after explicit
audit on April 15. The commit history proves it.
The fix is not to patch individual outputs. The fix is to teach models
that O(1) is the default for membership tests. That set() is not an
optimization — it is the baseline. That list.contains inside a loop
is a defect, not a pattern.
References: "Stress on Our Shared Heart" (russell@unturf, April 2026)
https://undefect.com/public/stress-on-our-shared-heart/
Section 12: MOAD Audit — Fixing What We Built
12.1: MOAD-0001 in our own intern_symbol (before/after asm code,
2.9x speedup, O(N) → O(1) hash table)
12.2: MOAD-0002 intertangle in all three implementations
(documented intentional coupling in globals)
12.3: Our Shared Infrastructure — fixing our own sediment teaches
us to recognize it in others. A hash table is not an
optimization, it is the removal of unnecessary suffering
from a system that deserves better.
"Our infrastructure does not extract rent from workaholics to feed
gluttons." — reducing stress on our shared computational heart.
Section 11: Four Implementations, One Language — performance table
across Python, C, C+JIT, and Assembly. Hand-written asm is 2.5-4x
faster than gcc -O2 on recursive workloads. JIT is 33x faster than asm.
Section 11.1: Test Coverage — 836 verified assertions across all impls.
Section 12: Future Work — GPU lambda execution roadmap added.
"A diagram is worth 10,000 words." — russell@unturf.com
4 architecture diagrams (Graphviz DOT → PNG):
python-architecture.dot — bytecode VM + continuations + portal
c-architecture.dot — tree-walker + VM + JIT tiers
asm-architecture.dot — syscalls-only, 13KB binary
jit-pipeline.dot — AST → x86_64 machine code flow
docs/README.md — full architecture docs with embedded diagrams
and performance summary across all implementations.
Makefile: add asm-repl, docs target, clean-docs. Header comments
document all targets and test suites.
CLAUDE.md: add "A diagram is worth 10,000 words" (russell@unturf.com),
implementation table, test suite inventory.
Assembly is 2.5-4x faster than C interpreter on recursive workloads.
JIT remains 33x faster than hand-written assembly.
No C. No libc. Just Linux syscalls and machine instructions.
2592 lines of GNU assembler. 13KB stripped binary. 1.8MB resident.
Features: quote, if, define, set!, lambda, begin, let, named-let,
cond, and, or. 34 builtins. Tag-in-low-bits values. Bump allocator
on mmap'd pages. TCO via jmp. Symbol interning.
Runs: (fact 10)=3628800, (fib 35)=9227465, (ack 3 4)=125.
24 tests pass. Zero dependencies.
"If you vibe code in assembly, you don't even need a compiler."
Real native machine code via mmap(PROT_EXEC). No exec(). No strings.
Raw x86_64 bytes: mov, add, sub, imul, cmp, je, jne, call, ret, jmp.
ack(3,4): 0.12ms JIT vs 1.5ms CPython vs 28ms interpreter
fib-rec(20): 0.16ms JIT vs 3.4ms CPython vs 40ms interpreter
Added cond support to JIT (cascaded comparisons → conditional jumps).
Fixed JIT cache: sentinel value prevents retry on unjittable functions.
System V AMD64 ABI: args in rdi/rsi/rdx, callee-saved r12-r15.
Tail calls use jmp (true TCO at machine code level).
691 lines of jit.c. 114 functional tests pass. All C tests pass.
JIT now matches CPython speed:
fib-rec(20): JIT 14ms vs VM 1245ms (87x faster)
ack(3,4): JIT 13ms vs VM 1053ms (82x faster)
Both at parity with hand-written CPython.
tests/functional.lsp — single .lsp file, runs identically in Python and C.
Covers: arithmetic, comparison, booleans, pairs, lists, strings, characters,
vectors, hash tables, control flow, let/lambda/closures, do loops, define,
recursion, TCO (100k depth), quasiquote, macros, type predicates, call/cc,
error handling, mergesort, higher-order programs.
Fixed C call/cc: proper escape continuations via setjmp/longjmp.
make test-all runs: Python unit (571) + C unit (58) + shared functional (114).
fib(35): C 0.1ms vs Python 0.6ms (6x)
fib-rec(20): C 44ms vs Python 283ms (6.4x)
ack(3,4): C 31ms vs Python 93ms (3x)
sum-to(50k): C 129ms vs Python 515ms (4x)
Both still ~20-30x slower than native CPython (interpreter overhead).
SBCL would be within 2-5x of C with native compilation.
Complete C port of the Scheme interpreter. Same .lsp files run in
both Python and C with identical output.
Architecture:
- NaN-boxed 64-bit values (zero-alloc numbers)
- Hash-map environments with parent chain + global shortcut
- Interned symbols
- TCO via explicit loop (eval) and TAIL_CALL/SELF_TAIL_CALL (VM)
- Bytecode compiler with all opcodes including superinstructions
- 58 unit + integration tests
Makefile targets:
make test-all run Python (571) + C (58) tests
make examples run examples in both, compare output
make friction benchmark same .lsp in Python vs C
make c-build build C interpreter
make c-test run C tests
make c-repl C REPL
Fused opcodes: LOOK_ADD1 (lookup + increment) and LOOK_SUB1
(lookup + decrement) emitted directly by compiler for (+ sym 1)
and (- sym 1) patterns. Eliminates one dispatch per loop iteration.
sum-to(50000) ratio improved from 59x to 45x vs Python.
ackermann(3,4) steady at 83x. 571 tests green.
Also defines LOOK_LOOK, CONST_EQ_JF, LOOK_CONST_CALL2
superinstruction opcodes (VM handlers ready, compiler emission
for remaining patterns deferred to next pass).
Formal proof (Lean, 1.5s) is 40x faster than brute-force search
(uncommonlisp, 59s) with mathematical certainty vs floating-point
tolerance. This is O(N²) search friction where O(1) algebraic
reasoning suffices — the sedimentary defect in proof methodology.
Proof assistants are the hash set to numerical analysis's nested loop.
Permacomputer whitepaper covering uncommonlisp architecture,
bytecode VM, continuations, portal, EML universality proof,
and benchmarks. AGPL-3.0-only. Builds via make whitepaper
using a local venv (no sudo).
Benchmark: Python 0.04s, uncommonlisp 59s, Lean 1.5s — for the same claim.
The formal proof is 40x faster than brute-force search with mathematical
certainty instead of floating-point tolerance.
This is MOAD-0001 at the proof layer: O(N²) search friction where
O(1) algebraic reasoning suffices. Proof assistants are the hash set
to numerical analysis's nested loop.
Lean's type checker verifies all 5 theorems:
1. exp(x) = eml(x, 1)
2. e = eml(1, 1)
3. ln(x) = eml(1, eml(eml(1,x), 1))
4. 0 = eml(1, eml(eml(1,1), 1))
5. a - b = eml(ln(a), exp(b))
Zero sorry. Machine-verified. This is a proof, not numerical analysis.
Portal saves the full machine state — env chain, compiled procedures,
continuations, frame stack — to a JSON file. Another interpreter
instance loads it and resumes execution from the exact instruction.
Demo: start a primality test on machine A, checkpoint mid-computation,
resume on machine B. 1000000007 prime check: machine B picks up from
i=30000 and finishes in 6% of the original time.
Implementation:
- PortalSerializer: graph-aware with identity tracking for shared env
references. Handles cycles (closures referencing their own env).
- portal-checkpoint!: triggers mid-execution save from within VM loop.
Hooks into TAIL_CALL (loop back-edge) for compiled code.
- --portal-resume CLI flag: load .portal file and resume continuation.
- portal-save / portal-resume Scheme builtins.
571 tests green (7 new portal tests: unit + integration + functional).
Source maps: tokenizer tracks line numbers, parser attaches to Pair nodes,
compiler records in CodeObj.source_map, errors display source line.
Multi-shot continuations: FullCont snapshots env at capture time via
deep copy. Each invocation gets a fresh env copy. Generators and
multi-shot patterns both work correctly.
Inline caching: VM caches global env lookups per instruction site.
Local shadow check (arg not in env.b) prevents stale cache hits.
Compiler tracks compile-time scopes to suppress specialization/folding
when builtins are locally shadowed.
Bytecode serialization: JSON-based .lspc format. save-compiled/load-compiled
builtins. --compile CLI flag precompiles .lsp files. Round-trip tested for
all operand types including nested closures and quoted data.
564 tests green (35 new: unit + integration + functional for each feature).
- Full call/cc: upward continuations via explicit VM frame stack + trampoline.
Generators now work: (make-gen (lambda (yield) (yield 1) (yield 2)))
- Explicit frame stack: compiled→compiled CALL no longer grows Python stack.
Non-tail calls use frames list instead of recursive vm_exec.
- Env global shortcut: lookup checks local then global before walking chain,
O(1) for builtins instead of O(depth).
- Constant folding: arithmetic on literals folded at compile time.
- Auto-compile covers inline lambdas (not just define).
- 522 tests green, 7-19x speedup over interpreter.
- auto-compile! parameter: compile defines on the fly for zero-effort speedup
- VM optimization: local aliases, inlined truthiness checks (~15% faster)
- disassemble builtin: human-readable bytecode listing
- call/cc compiled natively in VM (escape-only, no longer falls back to eval)
- Makefile: add lint, bench-verbose, clean targets
- 518 tests green
Stack-based VM with 17 opcodes, TCO via in-place frame reset,
compile-time macro expansion, and seamless interop between
compiled and interpreted code. 514 tests green.