Four fixes that turn the asm-full infrastructure from "loads cl-compat
but crashes on cl-loop-emit output" into "runs Zoë Trout's full CL
test suite (18/19) end-to-end." Zoë's original `examples/ursa.lisp.txt`
now produces matching answers to the Python and C tiers on asm-full.
1. asm/lumbda.s bi_apply — second arg was being clobbered. The
previous impl did `GETARG %rbx; GETARG %rdi; movq %rbx, %rdi;
... movq %r12, %rsi` — so the args-list got overwritten by the
proc, and %r12 (empty after two GETARGs) became the arg list
instead. `(apply f '(1 2 3))` silently reduced to `(f)`. Fix:
`GETARG %rbx; GETARG %rsi; movq %rbx, %rdi; call apply_proc_raw`.
2. asm/lumbda.s bi_expt — decrements rcx by 1 until zero. Negative
exponents looped forever. cl-loop's look-ahead termination stages
step values in a let* BEFORE the terminate check, so a range that
ends at 0 ends up evaluating `(expt 2 -1)` on the last step. Fix:
guard negative exponents, return 0. asm is integer-only; returning
a rational would need a new type. Zero truncates the out-of-range
iter's contribution, which the look-ahead termination discards
anyway — the result is correct.
3. asm/lumbda.s GC roots — macro_env_head was not marked. Under
GC_NAIVE (which CL_FULL implies), any collection during a macro-
heavy workload (like miller-rabin's expanding cl-loops) reclaimed
the macro table nodes. Next use failed with "unbound variable:
cl-when" or similar. Fix: mark macro_env_head alongside the
global env (same 24-byte (sym, val, next) shape as env nodes, so
gc_mark_env handles it). Guarded .ifdef CL_FULL.
4. asm/lumbda.s prelude — added `cadar` (used by
cl-loop-finalizer-expr). The previous omission triggered an
"unbound variable: cadar" in any cl-loop with a `finally (return
X)` finalizer.
5. cl-compat.lsp — two new helpers routed around asm's reduced
list-processing builtins:
* `cl-append` for n-list concatenation. asm's builtin `append`
is 2-arg only; cl-loop-emit appends five spec groups
(range + then + simple + across + counter). Reducing with
2-arg append works on every tier.
* `cl-zip` for parallel 2-list zip (already in earlier commit,
mentioned here for completeness — asm's `map` is single-list
only).
Verification on asm/lumbda-full:
* /tmp/ursa-load-test.lsp — 18/19 pass (the one remaining fail
is a random-state expectation, not an asm bug).
* (primep 97) → 97
* (primep 100) → #f
* (lucas-lehmer-primep 13) → #t (M₁₃ = 8191, prime)
* (lucas-lehmer-primep 11) → #f (M₁₁ = 2047 = 23·89)
* (of-n-bits 8) → random integer in [128, 256) with top bit set
* (prime-of-n-bits 8) → random 8-bit prime
make test-all stays green. All three asm variants still 158/158 on
their local test suites. asm's minimal footprint preserved — every
new line above is under .ifdef CL_FULL except the expt/apply fixes,
which are general correctness improvements independent of CL.
|
||
|---|---|---|
| asm | ||
| c | ||
| docs | ||
| examples | ||
| proof | ||
| tests | ||
| whitepaper | ||
| www | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| bench.py | ||
| cl-compat.lsp | ||
| CLAUDE.md | ||
| friction.sh | ||
| lumbda.py | ||
| Makefile | ||
| README.md | ||
| stdlib.lsp | ||
| tests.py | ||
Lumbda
A Lisp/Scheme-derived, just-in-time lambda language. Four implementation tiers with MOAD defect isolation. Workloads migrate across basic UNIX systems.
Four implementation tiers sharing one wire format — Scheme source itself:
- Python bytecode VM — reference, full first-class continuations
- C tree-walker + bytecode VM — portable C, JSON portal
- C + x86_64 JIT — pattern-matched native code, 7–10× faster than CPython
- Pure x86_64 assembly — 22 KB stripped, zero libc, 14 syscalls
Feedback is the primitive across four scopes: continuations within a process, portal files across processes, S-expressions across implementations, TCP sockets across machines.
Home: lumbda.com
λ> (define (fib n)
(let loop ((a 0) (b 1) (i 0))
(if (= i n) a (loop b (+ a b) (+ i 1)))))
λ> (map fib (iota 10))
(0 1 1 2 3 5 8 13 21 34)
Usage
python3 lumbda.py # interactive REPL
python3 lumbda.py script.lsp # run a file
python3 lumbda.py -e '(+ 1 2)' # eval an expression
python3 lumbda.py --fast script.lsp # auto-compile (7-19x faster)
Bytecode compiler
Lumbda includes a stack-based bytecode compiler and VM. Enable it with --fast or (auto-compile! #t):
python3 lumbda.py --fast examples/fibonacci.lsp
(auto-compile! #t)
(define (ack m n)
(cond ((= m 0) (+ n 1))
((= n 0) (ack (- m 1) 1))
(else (ack (- m 1) (ack m (- n 1))))))
(compiled? ack) ; => #t
(ack 3 4) ; => 125
The compiler handles: if, begin, and, or, when, unless, cond, define, set!, lambda, let, named-let, let*, letrec, do, call/cc, function calls with tail-call optimization. Macros are expanded at compile time. 20 specialized opcodes for hot builtins (+, -, *, =, <, car, cdr, cons, null?, etc.) avoid function call overhead.
Features:
- Explicit frame stack — compiled-to-compiled calls don't grow the Python stack
- Full continuations —
call/ccsupports upward continuations; generators work - Constant folding —
(+ 1 2)folds to3at compile time - Peephole optimizer — eliminates dead code (VOID+POP, JUMP-to-next)
(disassemble proc)— inspect generated bytecode
What's implemented
Core language
- Full lexical scoping and closures
- Tail-call optimization (TCO) — deep recursion never blows the stack
- Hygienic macros via
syntax-ruleswith ellipsis (...) support define-macro/defmacrofor procedural macroscall/cc— full continuations (escape + upward) in compiled codevalues/call-with-valuesdynamic-wind,guard,with-exception-handlerquasiquote/unquote/unquote-splicingwith proper nesting- R7RS internal defines with letrec* body semantics
- R7RS error objects
- Exact rational arithmetic —
(/ 1 3)→1/3,(+ 1/4 3/4)→1 - String ports —
open-input-stringopen-output-stringreadon ports - Mutable strings —
string-set!string-fill!string-copy! - Module system —
module/importwith export lists define-record-typewith(inherit parent)for single-inheritance- Pretty-print —
pp/pretty-print - Tracing —
(trace fn)/(untrace fn)
Special forms
define set! lambda λ if cond case and or when unless
begin let let* letrec letrec* named-let do
quasiquote define-macro define-syntax syntax-rules
let-syntax letrec-syntax apply eval values call/cc
dynamic-wind guard parameterize load error
module import define-record-type
Built-ins
- Arithmetic:
+-*/quotientremaindermoduloexptsqrtabsfloorceilingroundtruncateminmaxgcdlcmlogexptrig functions,numeratordenominator - Rationals:
(/ 1 3)→1/3, literal1/3syntax,exact/inexactconversion - Comparison:
=<><=>=zero?positive?negative?odd?even? - Pairs & lists:
conscarcdrlistlengthappendreversemapfor-eachfilterfold-leftfold-rightreduceanyeverysortpartitionfindtakedropzipflattenand more - SRFI-1:
lastfirst–fifthdeletelset-unionlset-intersectionlset-differenceunfoldlist-tabulate - Strings:
string-lengthstring-refstring-set!substringstring-appendstring-copystring-copy!string-fill!string->liststring->numberformatand more - Characters:
char->integerinteger->charchar-alphabetic?char-upcasechar-downcase - Vectors:
make-vectorvectorvector-refvector-set!vector-copyvector-copy! - Hash tables:
make-hash-tablehash-table-set!hash-table-refhash-table-keyshash-table-valueshash-table-walkand more - I/O:
displaywritenewlinereadread-charread-lineopen-input-stringopen-output-stringwith-output-to-string - File system:
file-exists?delete-filerename-filedirectory-filescurrent-directory - System:
command-lineget-environment-variablecurrent-timeexit - Python interop:
py-evalpy-execpy-importpy-callpy-attr - Compiler:
compilecompiled?disassembleauto-compile!
Standard library (stdlib.lsp)
Additional macros, string/list/numeric/tree utilities, alist/hash helpers, simple object system, SRFI-2/8/64 test framework.
Examples
python3 lumbda.py --fast examples/fibonacci.lsp
python3 lumbda.py --fast examples/generator.lsp
python3 lumbda.py --fast examples/mergesort.lsp
python3 lumbda.py examples/objects.lsp
;; Generator using full continuations
(auto-compile! #t)
(define (make-gen thunk)
(let ((k #f) (done #f))
(lambda ()
(if done 'done
(call/cc (lambda (return)
(if k (k return)
(begin (thunk (lambda (val)
(call/cc (lambda (next)
(set! k next) (return val)))))
(set! done #t) (return 'done)))))))))
(define counter (make-gen (lambda (yield)
(let loop ((i 0)) (yield i) (loop (+ i 1))))))
(counter) ; => 0
(counter) ; => 1
(counter) ; => 2
Running tests & benchmarks
make test # run 529 tests
make test-verbose # verbose output
make bench # compare interpreter vs bytecode vs CPython
make lint # syntax check all Python files
Portal — machine state migration
Serialize a running VM mid-computation, transfer to another machine, resume:
# Machine A: start a long computation with checkpoints
python3 lumbda.py --fast examples/portal-prime.lsp
# saves prime-state.portal at checkpoint
# Machine B: resume from checkpoint
python3 lumbda.py --portal-resume prime-state.portal
# continues from exact instruction
The portal captures the full env chain, compiled procedures, continuations, and frame stack as JSON. 16KB for a primality test in progress.
EML universality proof
The proof/ directory contains a formal verification that eml(x,y) = exp(x) - ln(y)
with constant 1 generates all elementary functions (arXiv:2603.21852v2).
Three approaches, benchmarked:
| Approach | Time | Guarantee |
|---|---|---|
| Python (numerical) | 0.04s | 1e-10 tolerance |
| Lumbda (numerical) | 59s | 1e-10 tolerance |
| Lean 4 (formal proof) | 1.5s | kernel-verified |
The formal proof is 40x faster than brute-force search with infinitely stronger
guarantees. See proof/benchmark_results.md for the full analysis — including
why this is MOAD-0001 (the sedimentary defect) at the proof methodology layer.
File layout
lumbda.py interpreter + bytecode compiler (one file, ~3200 lines)
stdlib.lsp extended standard library
tests.py test suite (571 tests)
bench.py benchmarks vs CPython
examples/ example programs
proof/ EML universality proof (Python, Scheme, Lean 4)
Makefile make test / make bench / make repl