lumbda/docs
russell@unturf.com 192118388f cl-compat: run Zoë Trout's favorites unchanged (ticket 0004)
Zoë Trout's favorites at wedgewack.org/ursa.lisp.txt are Common Lisp:
iterative LOOP macros, setf cascades, defun with &optional, image-
based stone-lisp culture. Her first contribution to lumbda was a
question — "do we care for our programs, and how long are they alive
for?" — and the answer now extends beyond the RNG portal (§7.5) to
iteration style itself.

Four-phase delivery, all under ticket 0004:

  Phase A — idiomatic Scheme ports at examples/ursa-scheme.lsp.
    Every Zoë defun rewritten as named-let + tail recursion + list-
    backed work queue + type-predicate dispatch.

  Phase B — CL compat shim at cl-compat.lsp.
    defun (with &optional), setf (simple vars, multi-pair), flet,
    multiple-value-bind, t / nil (nil=#f so cond/if compose),
    evenp/oddp/plusp/minusp/zerop, mod/ash/logbitp/nreverse,
    cl-when/cl-unless (plain when is a void-returning lumbda special
    form), declare (no-op), cddddr (missing accessor).

  Phase C — cl-loop macro covering 14 patterns.
    while/until/repeat, for VAR from A to/below/downto B, for VAR =
    INIT [then STEP], for VAR across VEC, of-type T, do, when/unless
    return, finally (return VAL). Sequential do*-style stepping via
    gensym + cl-subst. Look-ahead termination so `repeat 4 for s = 4
    then (- (* s s) 2) finally (return s)` returns 37634 (pre-step)
    rather than 1416317954 (post-step). Every expansion ends in a
    named-let tail call — TCO holds for loops of any length.

  Phase D — load examples/ursa.lisp.txt with minimal annotation.
    Preserves Zoë's CL. Minimal edits documented in file header:
    load cl-compat.lsp, loop→cl-loop, when→cl-when, random→random-int,
    &key→&optional. rho/digits omitted (need make-array/CLOS — see
    ticket 0004 for scope boundary).

Defect uncovered along the way (c/types.c env_lookup): a "global
shortcut" checked global env immediately after missing the local
frame, SKIPPING intermediate parent scopes. Broke lexical scoping
whenever a parent scope shadowed a global. Reproduced with
  (define s 4)
  (let ((s 100)) (let ((m 0)) s))  ; returned 4, should return 100
Any nested let whose body referenced a shadowed name silently read
the global. Fix: remove the shortcut, walk the parent chain end-to-
end. 1255 assertions across five suites pass unchanged after fix —
surfaced only because cl-loop iterator names routinely collide with
globals accumulated in a stone-lisp image.

Whitepaper §9.2 documents the CL-in-Scheme design and the guarantees
that survive (TCO, portal determinism, cross-impl reproducibility).
Zoë added to authors + acknowledgments; reacknowledgment reframes
her first contribution as the deeper program-lifetime question, with
RNG portal as a derivative (§7.5) and cl-loop as the follow-up.

Tests: tests/cl-compat.lsp (44 assertions) and tests/ursa.lsp (28
assertions) exercise both paths under Python + C via tests/zoe-
favorites-test.sh, wired into make test-all.

MOAD notes: unmoad flags memq/assq in cl-compat.lsp over cl-loop-
keywords (~30 elements, constant) and var->new (≤4 state vars per
loop). Both are macro-expansion-time, bounded-small-N — not runtime
hot paths. Pre-existing c/types.c findings (strcmp-in-loop for
record-type lookup) are not from this change.
2026-04-24 07:02:21 -04:00
..
tickets cl-compat: run Zoë Trout's favorites unchanged (ticket 0004) 2026-04-24 07:02:21 -04:00
asm-architecture.dot whitepaper: diagrams + stats refresh for GC / meta-GC / hash primitives 2026-04-18 10:46:29 -04:00
asm-architecture.png whitepaper: diagrams + stats refresh for GC / meta-GC / hash primitives 2026-04-18 10:46:29 -04:00
benchmark-ack.dot whitepaper: actually use diagrams — 5 PNGs embedded, .dot sources refreshed 2026-04-17 19:09:16 -04:00
benchmark-ack.png whitepaper: actually use diagrams — 5 PNGs embedded, .dot sources refreshed 2026-04-17 19:09:16 -04:00
benchmark-binary-size.dot whitepaper: diagrams + stats refresh for GC / meta-GC / hash primitives 2026-04-18 10:46:29 -04:00
benchmark-binary-size.png whitepaper: diagrams + stats refresh for GC / meta-GC / hash primitives 2026-04-18 10:46:29 -04:00
benchmark-fib.dot Add benchmark dot diagrams showing performance differences 2026-04-16 13:01:32 -04:00
benchmark-fib.png Add benchmark dot diagrams showing performance differences 2026-04-16 13:01:32 -04:00
benchmark-gc.dot asm-gc: movb-not-orq type patch (kills residual "unbound variable") 2026-04-18 20:28:31 -04:00
benchmark-gc.png asm-gc: movb-not-orq type patch (kills residual "unbound variable") 2026-04-18 20:28:31 -04:00
benchmark-speedup.dot Add benchmark dot diagrams showing performance differences 2026-04-16 13:01:32 -04:00
benchmark-speedup.png Add benchmark dot diagrams showing performance differences 2026-04-16 13:01:32 -04:00
benchmark-sumto.dot whitepaper: actually use diagrams — 5 PNGs embedded, .dot sources refreshed 2026-04-17 19:09:16 -04:00
benchmark-sumto.png whitepaper: actually use diagrams — 5 PNGs embedded, .dot sources refreshed 2026-04-17 19:09:16 -04:00
c-architecture.dot rename: uncommonlisp -> lumbda throughout the repo 2026-04-19 10:20:11 -04:00
c-architecture.png Add architecture docs with dot diagrams, update Makefile and CLAUDE.md 2026-04-15 14:07:59 -04:00
gpu-architecture.md Add GPU architecture notes and JIT header 2026-04-14 19:43:16 -04:00
jit-pipeline.dot Add architecture docs with dot diagrams, update Makefile and CLAUDE.md 2026-04-15 14:07:59 -04:00
jit-pipeline.png Add architecture docs with dot diagrams, update Makefile and CLAUDE.md 2026-04-15 14:07:59 -04:00
meta-gc-policy.dot whitepaper §6.6.3: collaborative adaptive meta-GC results 2026-04-18 11:35:27 -04:00
meta-gc-policy.png whitepaper §6.6.3: collaborative adaptive meta-GC results 2026-04-18 11:35:27 -04:00
python-architecture.dot Add architecture docs with dot diagrams, update Makefile and CLAUDE.md 2026-04-15 14:07:59 -04:00
python-architecture.png Add architecture docs with dot diagrams, update Makefile and CLAUDE.md 2026-04-15 14:07:59 -04:00
README.md rename: uncommonlisp -> lumbda throughout the repo 2026-04-19 10:20:11 -04:00

lumbda Architecture Documentation

"A diagram is worth 10,000 words." — russell@unturf.com

Three implementations of the same Scheme language, sharing the same .lsp test files.

Python Implementation (lumbda.py)

3,324 lines. Bytecode compiler + stack VM + full continuations + portal.

Python Architecture

Execution tiers:

  • Tree-walker (leval): default, handles all forms including macros
  • Bytecode VM (--fast): 40 opcodes + superinstructions, 7-19x faster
  • Python JIT prototype: exec()-based transpilation (labeled as prototype)

Key features:

  • Full multi-shot continuations via explicit frame stack
  • Portal: serialize VM state to JSON, resume on another machine
  • Inline cache, constant folding, peephole optimizer
  • Source maps for error reporting with line numbers
  • Bytecode serialization (.lspc files)

Tests: 571 unit + integration tests (tests.py)


C Implementation (c/)

8,120 lines. Tree-walker + bytecode VM + x86_64 JIT.

C Architecture

Execution tiers:

  • Tree-walker: default, full special form support
  • Bytecode VM (--fast): matching Python's opcodes
  • x86_64 JIT (--jit): 10-24x faster than CPython

Key features:

  • NaN-boxed 64-bit values (zero-alloc numbers)
  • Hash-map environments with parent chain + global shortcut
  • Interned symbols
  • Real JIT: mmap(PROT_EXEC) + raw x86_64 bytes

Tests: 76 unit + integration + JIT tests (test.c)


Assembly Implementation (asm/)

2,592 lines of GNU assembler. 13KB binary. Zero dependencies.

Assembly Architecture

Design:

  • No C. No libc. Only Linux syscalls (read, write, mmap, exit)
  • Tag-in-low-3-bits value representation
  • Bump allocator on 64MB mmap'd page
  • TCO via jmp .eval_top (never grows the stack)
  • 34 builtins, all special forms

Tests: 75 unit + integration + functional tests (test.sh)


JIT Pipeline (c/jit.c)

1,309 lines. Compiles Scheme AST directly to x86_64 machine code.

JIT Pipeline

What gets JIT'd:

  • if, cond, and, or (conditional jumps)
  • +, -, *, =, <, >, <=, >= (native integer ops)
  • let, let* (stack-allocated locals)
  • Named-let loops (native jmp, zero call overhead)
  • car, cdr, cons, null?, pair? (NaN-box pointer ops)
  • Self-recursive calls (call/ret) and tail calls (jmp)

What falls back to interpreter:

  • call/cc, macros, syntax-rules, quasiquote, modules
  • String/vector/hash-table operations
  • Any form the AST analyzer can't verify as integer-safe

Performance Summary

All benchmarks measured in-process (no startup overhead) on the same machine.

Implementation ack(3,4) fib(35) sum-to(50k) Binary
C + x86_64 JIT 0.19ms 0.09ms 0.55ms 171KB
CPython (native) 1.3ms 0.006ms 5.5ms ~5MB
C interpreter 20ms 0.06ms 109ms 171KB
Python bytecode VM 149ms 0.75ms 437ms 3,324 lines
Assembly (13KB) ~8ms* ~0.6ms* ~43ms* 13KB

*Assembly times include process startup + tokenizer + parser.

The JIT runs Scheme faster than CPython runs Python on recursive workloads: ack(3,4) is 7x faster, sum-to(50k) is 10x faster. The JIT compiles Scheme AST directly to x86_64 machine code via mmap(PROT_EXEC).


Test Coverage

943 verified assertions across all implementations:

make test-all
  Python unit/integration:   571 tests
  C unit/integration/JIT:     83 tests
  Assembly unit/int/func:    108 tests
  Shared functional:         181 tests (Python + C)
  Total:                     943 assertions

The language is R7RS Scheme. lumbda is the project name — a play on Common Lisp, since this is decidedly uncommon.