Lumbda names a Lisp/Scheme-derived language carrying four independently implemented execution backends — one surface syntax, one test suite. A Python tree-walker with an optional bytecode VM, a C implementation that adds an x86_64 JIT, and a pure x86_64 GNU asm interpreter (~6,600 lines, ~23 KB stripped, zero external dependencies). Every backend runs a shared .lsp source byte-identically, with full first-class continuations, exact rationals, records, and hygienic macros.
Get it
git clone https://git.unturf.com/engineering/unturf/lumbda.git
cd lumbda
make test-all
Run a program in any tier:
python3 lumbda.py --fast examples/fibonacci.lsp
./c/lumbda examples/fibonacci.lsp
./asm/lumbda < examples/fibonacci.lsp
Four tiers, one language
| Tier | Lines | Binary | What a tier buys |
|---|---|---|---|
| Python interpreter + bytecode VM | 3,743 | — | REPL hackability, debugging, reference |
| C tree-walker + bytecode VM | 9,164 | ~215 KB | deep recursion, production workloads |
| C + x86_64 JIT | +patches | ~215 KB | 7–10× faster than CPython on recursive workloads |
| Pure x86_64 GNU asm | 6,645 | ~23 KB | zero-dependency boot, auditability, embedded |
| GNU asm + naive mark-sweep GC + meta-GC arena | (same source, GC_NAIVE=1) | ~27 KB | bounded memory without manual arena discipline |
Portal: feedback across time
A continuation carries feedback within a process. A portal carries feedback across processes. Same primitive, different scope: capture machine state, serialize, reload elsewhere, resume. Lumbda ships three portal formats with distinct trade-offs:
- S-expression portal — Scheme source as a wire protocol. 16 of 16 producer×consumer cells green across Python, C, asm no-GC, and asm GC.
- JSON portal — graph-aware, preserves closures and live continuations (Python, C).
- Binary heap dump — asm only. 1.5 ms save+resume between two processes.
bend: dispatch to a GPU without rewriting your code
Wrap any registered GPU-able form in (bend …) and lumbda decides per call whether to run it locally or ship it to a CUDA worker over our wire protocol. The decision uses a cost estimator on the argument shape, not the operation name. Tiny inputs stay local; heavy inputs bend to a worker that holds a warm CUDA context across requests.
Start a GPU worker
# On any host with nvcc + a CUDA-capable GPU:
make gpu-worker
# → builds examples/cuda-fanout/shake256-fanout
# → builds the C tier (~10× faster wire orchestration than Python)
# → launches gpu-worker.lsp on port 9091
# Override tier or port:
make gpu-worker LUMBDA=python PORT=9001 # easier debugging
make gpu-worker LUMBDA=asm # smallest footprint
Call it from any tier
;; bend works on every tier — Python, C, asm — through the same
;; tcp-* + portal primitives lumbda already ships.
(load "examples/cuda-fanout/wire.lsp")
(load "examples/cuda-fanout/bend.lsp")
(load "examples/cuda-fanout/bend-macros.lsp") ; Python/C only — asm uses bend-call
;; Tiny — cost below threshold, evaluates locally
(bend (cuda-shake-fanout '("00" "01" "deadbeef") 32))
;; Heavy — cost above threshold, ships to the GPU worker
(bend (cuda-shake-fanout one-million-inputs 32))
On a single RTX 3090 with a warm daemon, fan-out matched hashlib.shake_256 byte-for-byte and won by 1.5–10× across the workloads we measured. Below the break-even (~100 MB of bulk hash work) host CPU stays faster — the cost estimator picks correctly.
Tier choice for the worker host
The CUDA kernel runs inside the leaf binary; what the tier choice affects is wire orchestration. The wire has two modes: S-expression text (the default — hex strings inside a Scheme list) and binary (magic BSHK header + raw bytes, identical layout to the daemon's binary portal). Binary mode bypasses S-expression parsing entirely:
| workload | Py S-exp | Py binary | C S-exp | C binary |
|---|---|---|---|---|
| 100 × 16 B | 3.43 ms | 0.74 ms | 0.40 ms | 0.15 ms |
| 1k × 16 B | 23.24 ms | 0.76 ms | 2.77 ms | 0.22 ms |
| 10k × 16 B | 218.82 ms | 1.27 ms | CLIFF | 0.88 ms |
| 100k × 16 B | 2,219 ms | 10.18 ms | CLIFF | 10.35 ms |
| 1M × 16 B | 23,811 ms | 159 ms | CLIFF | 157 ms |
Binary mode wins by 30–200× over S-expression mode at scale. At 1 M × 16 B inputs, C tier binary is 157 ms end-to-end versus 23,811 ms for the S-exp path — a 150× speedup. The CUDA kernel itself on this 3090 runs in ~47 ms; binary wire adds ~110 ms of file I/O + framing on top, a 2.5× multiplier instead of the 500× multiplier the S-exp path imposed.
Critically, at huge workloads bend now beats host hashlib: host SHAKE256 over 1 M tiny inputs is ~2 s on a single Python core; bend via binary worker is 157 ms — a 12× speedup of host. The cost estimator in bend.lsp should be updated to know about the binary path so the routing decision picks GPU at this scale instead of staying local.
Binary mode lives behind the BSHK magic byte in the wire payload. S-expression callers see no change; binary callers prepend the magic and send raw bytes. See examples/cuda-fanout/bench_tiers.py --binary for the protocol implementation.
Three tiers, three operating points (S-expression mode): C tier wins at small & medium scales (8× faster than Python); asm tier hits 0.21 ms at very small inputs (~30% behind C, 6× faster than Python, 70 KB statically linked, zero libc); Python tier scales linearly (~22 µs per input) all the way through 1 M inputs but runs slowly on a single core. The S-exp CLIFFs at 10k (C) and 1k (asm) are tier-internal reader limits — binary mode bypasses them entirely.
The CUDA toolchain stays isolated to the leaf binary the worker spawns. No tier links libcudart; no tier requires nvcc at build time. Asm tier hosts workers through hand-written pipe2 + fork + execve syscalls — no libc anywhere on the chain.
See examples/cuda-fanout/ for the wire contract, daemon protocol, bench data, and per-tier integration sketch.
EML universality proof
A single operator eml(x, y) = exp(x) − ln(y) with a constant 1 generates all elementary functions: exp, ln, arithmetic, negation, complex-plane access, trigonometry. Verified numerically in Python, verified in Lumbda's own bytecode, proven formally in Lean 4 with zero sorry. Lumbda's native symbolic-rewrite checker runs five theorems in 46 ms cold or 7 ms cached — roughly 16× faster than Lean's cold rebuild on identical hardware.
Whitepaper
Full language reference, tier-by-tier architecture, benchmarks, meta-GC design, universality proof.
License
AGPL-3.0-only. Public domain for whitepapers, proofs, and disclosures through undefect.com. Companion to unturf.com’s permacomputer project.