Folds 76 commits of substance since 2026-04-24 into the whitepaper
without losing any of the novel cross-domain glue the language earns
its keep on.
Additions
- §4.3 extended from four scopes to five, with bend as feedback across
heterogeneous compute (host ↔ GPU). New diagram
diagrams/five-scopes-feedback.png stacks the boundaries.
- §11.8 Bend: cross-tier GPU dispatch. Wire modes (S-exp text, BSHK
binary), worker hosting per tier, spawn-process-stdio + flush-port
cross-tier IPC, worker health heartbeat with VRAM-ranked pick,
6-row catalog of live forms (cuda-shake-fanout, cuda-secp256k1-mul,
cuda-bignum-cgbn, cuda-radix-sort, cuda-blake3-tree,
cuda-sim-ops-bin). New diagram diagrams/bend-dispatch.png.
- §2.1 C-tier bignum: arbitrary-precision integers (Boehm-GC managed),
which unblocked the secp256k1 widths the GPU forms need on the host
side.
- §12.1 post-cycle audit: recv-exact O(n²)→O(n), asm scheme_read
overflow, asm gc_sweep page-fault, C-tier JIT cur_code restore
across CALL/RETURN.
- §13 GPU Phase 1 moved from future to shipped; Phase 2 trampolining
and Phase 3 interaction combinators sharpened.
- Source repo link (git.unturf.com/engineering/unturf/lumbda) added
to the cover page band and the Citation block.
Compressions (no novel glue dropped — audited per fox's constraint)
- §8.2-§8.5 EML derivation prose collapsed into a single §8.2
derivation chain code block; §8.6 renumbered to §8.3.
- §6.6.3 adaptive-meta-GC narrative compressed; result table kept.
- §7.4.1 GC-build S-expression portal tightened to two paragraphs;
"language is its own wire format" insight kept.
Preserved in full per audit
- §7.5 portable RNG state across tiers
- §7.5.1 one-side kernel entropy + portable bit-identical continuation
- §11.7 sendfile + adaptive preload (27 KB asm-gc within 2% of Caddy)
- §6.6.1 collaborative arena + mark verifier
- §6.6.5 precise block typing
- §8.1 + §8.3 Lumbda hosts its own EML proof checker, ~16× faster
than Lean cold
Net RST: 1622 → 1655 lines.
inject-whitepaper-css.py adds CSS for a fixed 280px left sidebar plus a
DOMContentLoaded script that walks `section[id] > h2, > h3` and builds a
chapter/section nav. IntersectionObserver tracks active section as you
scroll; the rail scrolls itself to keep the current entry in view. Below
1100px the rail hides behind a hamburger toggle with backdrop dismiss.
Cancels docutils responsive.css `body > *` blanket padding on the rail
via the same `all: revert` pattern already used for the uncloseai
floating button. 59/59 source sections present in rendered DOM.
§3 (tier description) expanded — the asm tier now ships three build
flavors (lumbda / lumbda-gc / lumbda-full) each an additive superset
of the previous, each guarded by assemble-time flags so the minimal
tier keeps its 22 KB shape. Lists the new CL_FULL features explicitly:
auto-loaded Scheme prelude, #(…) vector literals, `/,/,@ quasiquote,
define-macro + macro table (GC-rooted), and the seven new builtins
(gensym, exit, values, call-with-values, cadr, sort, let*).
§9.2 (CL compatibility) updated to reflect the work this session:
* Portal determinism paragraph now names asm/lumbda-full alongside
Python and C as a CL-compat tier.
* Replaced the single-defect paragraph with a five-defect sweep.
Four were asm-side bugs the prior 158-assertion suite never
exercised: bi_apply clobber, bi_expt infinite loop on negative
exponent, macro_env_head missing from GC roots, prelude missing
cadar. The fifth — C env_lookup's global shortcut — remains as
before. All five landed as separate commits in ticket 0005's
follow-up drop.
* Tests paragraph now names three test files (cl-compat.lsp at 45
assertions including multiple-value-bind, ursa.lsp at 28, ursa-
scheme.lsp at 15) and both test runners — make zoe-favorites-test
for the offline suite and make prove-ursa-runs for the network-
live proof that fetches wedgewack.org/ursa.lisp.txt fresh every
run and spot-checks Zoë's defuns on asm/lumbda-full.
PDF and HTML regenerated.
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.
New subsection under Language Coverage covers the isqrt primitive that
shipped in 6e9d3ea across Python + C + asm. Documents:
- Semantics: (isqrt n) -> floor(sqrt(n)), integer in / integer out,
negative argument raises; matches Python 3.8+ math.isqrt and R7RS
exact-integer-sqrt.
- Why integer: no FPU drift, no libm platform variance, portal replays
stay bit-identical.
- Algorithm: bit-by-bit digit recurrence, O(log n), no multiply/divide,
no FPU. Asm variant runs in three integer registers.
- Domain limits: Python unbounded, asm 61-bit, C 48-bit (NaN-boxed).
Portal round-trips bit-identical within the smaller tier's window.
- MOAD-0001 note: O(log n) per call, no hidden linear scan.
21 RST lines. Rebuilt PDF and HTML from source.
Citation block previously linked to git.unturf.com/books/feedback-is-all-you-need,
a placeholder URL that was never live. Replaced with the verified public PDF at
lumbda.com/lumbda-whitepaper.pdf (HTTP 200, application/pdf, 2.7 MB).
Rebuilt PDF and HTML so all three artifacts carry the corrected URL.
Previous revision mentioned (random-seed-from-os!) in one sentence; this
treatment does not match the weight fox assigned — 'a random seed from os
on at least one side is very important.'
New sub-subsection 7.5.1 'Kernel Entropy on One Side' spells out:
- why hand-rolled /dev/urandom seeding is a reproducibility-defect farm
- why we picked /dev/urandom over /dev/random and getrandom(2)
- the real-world Machine A -> portal -> Machine B flow as concrete Scheme
- the audit property: a captured run-id replays the trajectory exactly
- kernel entropy enters the system once, under user control, then never
again — every resume reads the same stream off the portal
Ticket: docs/tickets/0002-os-entropy-seed.md
Ticket 0002 — reads 8 bytes from /dev/urandom (little-endian u64) and
seeds xoshiro256**. Opt-in kernel entropy for stochastic runs; the
default stays deterministic (k=0 at startup), so ticket 0001's
portal-reproducibility contract is unchanged.
Real-world flow now one call away:
Machine A: (random-seed-from-os!) + run simulation + portal-save
Machine B: portal-resume — same stream, bit-for-bit
All three impls fail loud on /dev/urandom trouble (LispErr in Python
and C, stderr + exit(1) in asm) — no silent fallback to a weak seed.
Tests:
- tests/functional.lsp: 2 new shared asserts (entropic + replay)
- asm/test.sh: 2 new asm-local checks (149 total, was 147)
- make test-all green across Python (205), C (205), asm (149)
Whitepaper §7.5 gains one sentence noting the OS-seed path.
unmoad: zero new findings in added code.
Adds a new subsection documenting xoshiro256** across all three tiers
and the portal-format extensions that preserve its state across
processes. Renumbers subsequent subsections 7.5→7.6, 7.6→7.7, 7.7→7.8;
updates the §7.6 cross-reference in the reproducibility map.
New Acknowledgments section before Citation/References/License credits
Zoë Trout — whose question 'does the portal tech keep the random list
seed which will allow transferring random entropy between processes
when continuing a simulation?' surfaced the gap. The earlier claim
'portal = portable machine state' carried a silent asterisk for
stochastic code; that asterisk is now gone.
Ticket: docs/tickets/0001-portal-rng.md
Implementation: 27f468c (Python + C) + 54c4c65 (asm + tests)
Previous commit used `all: revert` on the floating button to cancel
docutils's blanket `body > *` rule. Too aggressive — it also removed
uncloseai's own `position: fixed`, `bottom`, `right`, `width`,
`background`, `color`, `border` declarations, making the button
disappear entirely.
Replace with a tight override: only the five properties docutils's
blanket rule sets (background-color, line-height, padding, margin,
max-width) get reset with !important. Everything else falls through
to .uncloseai-floating-button's own stylesheet, so the button
re-appears in its designed 121px pill, bottom-right, black-on-white.
Any <img> inside <main>/<section> now opens in a full-viewport
overlay (92% black bg, image centered at max-width/max-height). Click
the overlay or press Esc to close. Pure vanilla JS + CSS, injected
inline via inject-whitepaper-css.py — keeps the HTML self-contained
with no external lightbox dependency. cursor: zoom-in signals
interactivity; the overlay uses cursor: zoom-out.
Three whitepaper-HTML-only fixes:
* docutils's responsive.css blanket-pads every `body > *` with
`padding: 0.5rem calc(29% - 7.2rem)`. The uncloseai floating
button gets appended to <body>, picks up that padding, and blows
from its designed 121 px to nearly half the viewport. Scoped
override resets the cascade on the button selector with
`all: revert` + explicit zero padding/margin.
* whitepaper h1/h2 titles now use ChunkFive (same face as the
homepage logo) instead of docutils's default serif. Font embedded
as a base64 data: URI inside a <style> block so the HTML stays
self-contained — no external font fetch.
* RST layout: λ logo now sits UNDER the "Lumbda" heading instead
of above it. Permacomputer logo still follows below the λ.
New helper: whitepaper/inject-whitepaper-css.py runs after
embed-images.py, reads ChunkFive from www/fonts/..., base64-encodes,
writes the <style> block before </head>. Makefile wires it in.
Source PNG from the MPS lumbda-logos product is already an inverted λ
(hooks at top-left + bottom-right, V-body). My prior commit applied
vertical + horizontal flips on top of that, which undid the inversion
and produced a right-side-up calligraphic λ — the opposite of what
the artwork was drawn to express.
Regenerate both PNG variants (lumbda-logo.png black,
lumbda-logo-green.png #227842) directly from the source with no
flips. Only transform: alpha-from-brightness so anti-aliased edges
survive over any background.
Homepage img src picks up ?v=2 so any browser with the flipped
version in disk cache re-fetches. Logo size stays 3.33× the
wordmark font (10.66rem). PDF + HTML rebuilt — embed-images.py
base64-inlines the new PNG into the HTML automatically.
Fetched the lumbda-logos artwork from
media.unturf.com/c/fbd3d473-.../lumbda-logos (a MakePostSell product),
applied both flips (vertical + horizontal, = 180°) at bake time so the
PNG ships oriented correctly without any CSS transform dance, and
recolored non-background pixels to brand green (#227842) with alpha
derived from pixel brightness so anti-aliased edges stay smooth.
Two PNG variants ship under whitepaper/diagrams/:
* lumbda-logo.png dark ink for print contexts
* lumbda-logo-green.png #227842 for web
www/ carries symlinks to both.
Homepage (www/index.html): replaces the CSS-rendered λ with an
<img class="lambda-mark"> element sized 9.6rem — 3× the 3.2rem
wordmark font — stacked below "lumbda." on its own line.
Whitepaper (whitepaper/lumbda-whitepaper.rst): adds the logo as the
first figure above the permacomputer-logo, 24% width, centered.
Regenerated PDF + HTML; embed-images.py base64-inlines the new file
automatically so the HTML stays single-file.
CI (.gitlab-ci.yml): generalizes the symlink resolver from two
explicit `cp -L` calls into a `find www -type l` loop, so every
current and future symlinked asset deploys without per-file CI edits.
Previously the HTML whitepaper referenced diagrams via relative paths
(src="diagrams/X.png"), which 404'd because the docroot does not carry
a mirror of whitepaper/diagrams/. The "HTML whitepaper" thus showed
broken image stubs instead of the architecture figures.
Fix: a post-processor (whitepaper/embed-images.py, stdlib Python) runs
after rst2html5. Every <img> with a relative src gets base64-inlined
as a data: URI with inferred MIME. Any .svg reference would splice in
as an inline <svg> element, surfacing alt text as <title>; the current
RST only uses the .png variant of gnu-logo so no SVGs inline for now,
but the path works when we switch.
Makefile's whitepaper-html target chains the embed step after the
existing sed passes for the uncloseai.js script tag and the meaningful
<title>. Title tweaked to "feedback as a primitive" to match the
homepage tagline.
Result: whitepaper/lumbda-whitepaper.html grows from 180 KB to 4.6 MB
(base64 inflation on ~2 MB of diagrams), and it now opens offline as
a single file — no network fetches for figures, no docroot mirror
needed.
CLAUDE.md rewritten to avoid the verb "to be" and to prefer "a"/"our"
over "the" when a thing counts as one of many or as shared. Same rule
applied to www/index.html (tagline now "feedback as a primitive", body
prose cleaned of is/are/be, table header now "What a tier buys",
section heads trimmed of definite articles). Monospace body stays,
ChunkFive titles stay.
Whitepaper now ships in two formats:
* Makefile target `make whitepaper-html` runs rst2html5 with
embedded minimal.css + responsive.css (docutils bundled), then
injects the same uncloseai.js module script that the homepage
carries, and stamps a meaningful <title>. 180 KB self-contained.
* PDF build target renamed to `make whitepaper-pdf`;
`make whitepaper` now builds both.
www/lumbda-whitepaper.html symlinks to the generated HTML (same
pattern as the PDF symlink). .gitlab-ci.yml resolves both symlinks to
real files before rsync so the proxy gets byte-identical copies.
Homepage now exposes both formats side-by-side via two .cta buttons
and the footer lists HTML + PDF.
asm-gc gains (tcp-sendfile socket path) → builtin (90 lines) that issues
SYS_SENDFILE(40) in a loop, streaming a file from fd → socket with no bounce
through the Lumbda heap. Zero-copy kernel path for large responses.
examples/http-static-server-sendfile.lsp (hybrid): small assets
(≤ 16 KB) stay inline-cached as full HTTP responses; large assets cache
only headers and stream the body via tcp-sendfile. 4-way race on
i5-8350U, 100 PDF requests (2.56 MiB), concurrency 8:
uncached 159 req/s 406 MiB/s 15.5 MB RSS
cached 238 req/s 603 MiB/s 7.2 MB RSS
sendfile 480 req/s 1226 MiB/s 4.2 MB RSS
caddy 485 req/s 1238 MiB/s 37.1 MB RSS
sendfile lands within 2% of caddy on throughput with 9x less peak RSS in
a 27 KB binary vs caddy's 38 MB (1400x smaller).
examples/http-static-server-adaptive.lsp (learning preload): per-URL hit
counter persisted to www.hits every N requests. At boot, ranks and
preloads top *cache-max* URLs from the prior run's data (cold-start
falls back to a seed list). Cold requests beyond the seed promote on
first hit. Drops heap-restore arena pattern since the server mutates
persistent state every request; relies on GC build's mark-sweep.
tests/bench-www-race.sh: adds sendfile variant on port 8083, auto-sizes
PDF byte count from the on-disk whitepaper so a whitepaper rebuild
doesn't desync the MiB/s calc.
Whitepaper §11.7 "Static File Serving: Cache, Sendfile, and Adaptive
Preload" documents the four variants, benchmark table, and the
arena-vs-mutation tradeoff. §13 Future Work adds DAG-of-hot-paths
predictive preload as the direction for > 1000-resource deployments
where frequency-only ranking is too narrow.
Historical internal name "uncommonlisp" retired in favor of the
public name "lumbda" ahead of lumbda.com going live. Scope of
this commit:
Source files renamed:
uncommonlisp.py -> lumbda.py
asm/uncommonlisp.s -> asm/lumbda.s
c/uncommonlisp.h -> c/lumbda.h
whitepaper/uncommonlisp-whitepaper -> whitepaper/lumbda-whitepaper (.rst + .pdf)
Binaries renamed (tracked ones; c/ was always gitignored):
asm/uncommonlisp, asm/uncommonlisp-gc, asm/uncommonlisp.o,
asm/uncommonlisp-gc.o -> asm/lumbda(-gc)(.o)
c/.gitignore -> ignores lumbda
Internal string updates (sed pass ordered longest-first):
asm/uncommonlisp -> asm/lumbda
c/uncommonlisp -> c/lumbda
uncommonlisp.py -> lumbda.py
UNCOMMONLISP_BIN -> LUMBDA_BIN (asm/test.sh env var)
"uncommonlisp> " -> "lumbda> " (asm REPL prompt baked into binary)
UNCOMMONLISP -> LUMBDA (macros, comments)
uncommonlisp -> lumbda (prose)
Binary portal magic updated:
"ULPORTAL" -> "LUMBDAB1" # "Lumbda Binary v1"
Old portal files are not backward-compatible — this is a deliberate
break since it's the rename moment. S-expression portals already
carry their own ";; lumbda-portal v1" header and remain cleanly
versioned.
WHITEPAPER.pdf / WHITEPAPER.rst symlinks repointed to the renamed
files. Makefile's whitepaper target targets lumbda-whitepaper.pdf.
Not changed (intentional, separate phases):
- Filesystem directory /home/fox/git/uncommonlisp itself
(fox renames locally and the gitlab repo URL in a follow-up)
- tests.py hardcoded cwd=/home/fox/git/uncommonlisp
(matches the current on-disk location; will flip when the
directory rename ships)
- Git history (immutable; old commits still say uncommonlisp,
which is correct — that's what they were)
Verified:
137 asm no-GC + 137 asm GC + 571 Python + 83 C + 189 shared
functional tests all pass under the new names.
bench-gc-http (2000 req): all 4 cells behave as expected
(cells 1/2 flat, 3 leaks, 4 bounded at 1 chunk).
Python REPL, C REPL, asm REPL all start cleanly.
GC-build portal files now start with ";; lumbda-portal v1\n". The
line is a Scheme comment the reader already skips, so loading a
v1 file via bi_load works unchanged. What's new is that
portal-resume actively validates the header before delegating to
bi_load:
file starts with ";; lumbda-portal v1\n" -> load normally
file starts with ";;" but different text -> return #f (rejected)
file does not start with ";;" at all -> load as legacy (back-compat)
The check reads the first 32 bytes of the file, compares the
first two bytes against ";;", and on match compares the full
20-byte v1 prefix. Closes the versioning-friction concern a SEW
reviewer raised after reading §7.4.1: we can now add a v2 format
with new syntax (complex numbers, records, whatever) without
older consumers silently parsing new files into garbage — they
will cleanly return #f.
Verified:
v1 portal -> resume loads all bindings, returns #<void>
v2 portal -> resume returns #f without evaluating any forms
legacy portal (no ;; header) -> resume loads normally
missing file -> resume returns #f
The deeper framing worth writing down: this is a migration format
for handoff across process / tier / machine, not an archive format.
If archival becomes a real use case it earns its own format with
proper schema evolution and a builtin-rename table. The v1 tag is
the minimum hook that lets v2 happen cleanly when someone needs it.
137 asm no-GC + 137 asm GC still pass. §6.6.4 HTTP cells still
green at 10K: GC + no-snapshot at 543 req/s, peak 1.1 MB, 972 KB
growth (one chunk, steady state).
Root-cause fix for the residual crashes I had documented as known
issues in §6.6.4. Every heap_alloc call site was setting its type
byte with `orq $(HT_X << 8), -8(%rax)` — but OR merges with the
stale type byte from a free-list-reused block. A pair previously
used as a vector (type 5 = 0b101) re-allocated as pair (type 1 =
0b001) ends up with merged type 0b101 = still vector. Walker then
treats the pair as a vector, reads the pair's car as a "length",
and walks off the block end — hence the hash-set bench's "unbound
variable: t", memory bench's "unbound variable: lst", arena bench's
"unbound variable: k".
Fix: overwrite the byte instead of OR-ing. 18 sites converted from
`orq $(HT_X << 8), -8(%rax)` to `movb $HT_X, -7(%rax)`. Every
previously-residual crash gone on first rerun.
Refreshed benchmark numbers throughout §6.6:
§6.6 Memory table: 122× less memory at 26% slowdown (was 124×,
30%). Range shifted because the fix also accelerated the common
paths; ratio stable.
§6.6.4 HTTP soak at 50,000 requests × 16 concurrent × 4 cells:
no-GC + snapshot 630 req/s peak 100 KB growth 4 KB
GC + snapshot 633 req/s peak 120 KB growth 4 KB
no-GC + no snapshot 625 req/s peak 458 MB OOM at cap
GC + no snapshot 610 req/s peak 1,092 KB growth 852 KB
Cell 4 now sustains 50K requests with steady-state 1-chunk memory.
Previous residual edge at 50K (cell 4 failing to start) was a
manifestation of the same type-byte bug, now gone.
§6.6.3 Adaptive numbers collapsed to within ~1% across all three
workloads (was 6% / 7% / 17% deltas). Paper updated to honestly
report adaptive as a null experiment on these shapes — neutral
cost, same stats surface, default on.
§6.6 diagram: bench-gc.png refreshed to match new numbers.
137 asm no-GC + 137 asm GC + 189 shared functional all pass.
Hash-set / memory / arena / adaptive / HTTP benches all clean.
Extended the HTTP-under-GC validation from the original 5,000
requests to 20,000 requests per cell. All four cells still hold:
asm no-GC + snapshot ~240 req/s peak 96 KB growth 0 KB
asm GC + snapshot ~235 req/s peak 120 KB growth 0 KB
asm no-GC + no snap ~470 req/s peak 185 MB growth 185 MB
asm GC + no snap ~450 req/s peak 1,084 KB growth 972 KB
Cell 4 (GC + no snapshot) is the real validation target. 972 KB of
growth over 20,000 requests = one chunk filled once, after which
the collector cycles through reclaimed space. No monotonic leak,
no OOM, no crash. Naive mark-sweep is now a correct (not optimal)
allocator for long-running asm servers that don't manage arenas.
The paper's soak commentary explicitly calls this out and also
notes two remaining rough edges we've observed but not yet
debugged: (a) at 50k requests with the no-GC + no-snap case
saturating the 512 MB vcap right before cell 4 starts, cell 4's
server sometimes fails to initialize — looks process-environment
rather than GC, no clean explanation yet; (b) the hash-set
benchmark on the GC build still surfaces an occasional
unbound-variable error at ~1 MB/iter workloads.
Reproduce: make bench-gc-http defaults to 5k now; soak uses
`REQUESTS=20000 CONCURRENCY=16 bash tests/bench-gc-http.sh`.
Binary heap dump can't work under GC because the heap is a linked
chunk list with typed block headers and a free list. Raw-byte
serialization would lose structure. Rather than invent portal v2
with chunk tables and pointer relocation, the GC build uses the
S-expression format that already works across all other tiers:
# bi_portal_save in GC build:
walk %r14 (env chain); for each non-builtin, non-closure binding,
emit `(define <sym> (quote <val>))` to the opened file via
scheme_print with output_fd redirected to that fd.
# bi_portal_resume in GC build:
jmp bi_load — read every form from the file, eval each in %r14.
The quote wrapper makes data values round-trip cleanly: lists,
vectors, strings, symbols, numbers, pairs all re-read as literals.
Closures and builtins are explicitly skipped — closures can't
faithfully re-read from their printed form; builtins reconstruct
from the target's prelude. Same treatment the JSON portal gives.
The no-GC build keeps the binary portal format unchanged (wrapped
in .ifndef GC_NAIVE). Users get the fast format on the fast build,
the portable format on the safe build. Same API, different wire
format by build.
Cross-tier verified: GC-asm producer -> Python consumer passes
with `x=42`, `nums=(1 2 3 4 5)`. The §7.2 cross-impl matrix
expands from 9 to 16 cells, all green.
Whitepaper §7.4 now notes it's the no-GC format; new §7.4.1
documents the GC build's S-expression portal with the trade-off
(slower than binary dump, stricter about what round-trips, but no
architecture constraint and no "same binary" requirement).
137 asm no-GC + 137 asm GC + 189 shared functional tests pass.
All four HTTP cells from §6.6.4 still bounded under sustained
load (GC + no-snapshot at ~630 req/s peak, 1.1 MB steady state).
Replaces header format from [size:63 | mark:1] with
[size:48 | type:8 | flags:8 (mark in bit 0)]. Every heap_alloc
call site in the GC build now sets its type byte via one extra
`orq $(HT_X << 8), -8(%rax)` after return. Ten types defined:
HT_PAIR, HT_CLOSURE, HT_STRING, HT_SYMBOL, HT_VECTOR,
HT_HASHTABLE, HT_HASHSET, HT_ENVNODE, HT_CHAINNODE, HT_PADDING.
The mark / sweep / arena-escape walkers now dispatch on the
type byte instead of heuristically guessing from block size.
Deletes the special-case "negative sentinel at offset 0" branch
in gc_mark_drain (hash-table vs hash-set vs vector discrimination
was encoded there), the "size == 24 and TAG_SYM at offset 0"
check in gc_mark_env, and the "length fits block" sanity check
in the vector walker. All that logic collapses into a single
compare on the type byte.
Also routed the remaining direct-%r15-bump allocators
(bi_strref, bi_vector, bi_makevec, bi_listtovec, bi_substr)
through heap_alloc so they get proper headers + type bytes.
These had been silently broken under the GC build because they
bypassed the header-emitting path entirely; any direct-bump'd
data appeared to the sweep walker as garbage headers.
§6.6.4 cell 4 (asm GC + no snapshot) was crashing at first GC
before this change. After: serves 5,000 HTTP requests at ~410
req/s, peak RSS 1,088 KB (one chunk), growth 972 KB — the
collector hit its natural steady state. First time we've
validated "naive GC as replacement for snapshot discipline"
under real traffic.
New §6.6.5 "Precise Block Typing" in the whitepaper documents
the old heuristic bugs, the new header format, and the cost
(one orq per alloc, 16 header bits) vs benefit (class of bugs
eliminated). Updated §6.6.4 to reflect cell 4 passing.
Remaining known issue: the hash-set bench on the GC build under
very heavy sustained allocation still surfaces an occasional
unbound-variable error. The precise-type fix addressed the
observed HTTP crash; a deeper root-scan edge case remains.
Tracked for Fix 2 work.
137 asm no-GC + 137 asm GC + 189 shared functional tests all
pass.
New infra:
- examples/http-server-noarena.lsp: same HTTP server minus the
heap-snapshot/heap-restore arena loop. Isolates whether the GC
build actually holds memory under real traffic, independent
of the portable snapshot pattern.
- tests/bench-gc-http.sh: drives 5,000 concurrent requests per
cell across the full 2x2 matrix {no-GC, GC} x {snapshot, no}.
- Makefile: new `bench-gc-http` target.
Extended benches to exercise both asm binaries:
- tests/bench-hashset.sh now runs against both asm/uncommonlisp
and asm/uncommonlisp-gc, with set +e so a GC-build crash on
one workload doesn't abort the other.
- tests/web-benchmark.sh adds a dedicated asm-gc row (and prints
its stripped binary size) so the HTTP throughput comparison
reports both.
Whitepaper updates:
- §6.6.4 "Validation: HTTP Server Under Sustained Load" — the
4-cell memory matrix. 3/4 cells green; cell 4 (GC + no
snapshot) crashes at first GC trigger — another instance of
the conservative-scan type-confusion class we already fixed
once at the env/string boundary. Logged as a known issue
rather than shipping a partial fix under time pressure.
heap-snapshot + heap-restore remains the recommended pattern
for production asm code; the naive GC is diagnostic + control
group, not a replacement for the arena discipline.
- §6.5 hash-set speedup table slightly softened to ~15-20x (was
15-21x) since run-to-run noise on a shared laptop shifts the
per-phase ratio by a few percent. Ratio is stable to first
order.
- §8.6 narrative references the ~1280x symbolic-vs-brute-force
figure instead of the stale 40x.
- §6 reproducibility list now lists `make bench-gc-http`.
All 137 asm no-GC + 137 asm GC + 189 shared functional tests
still pass.
Old table only showed three rows, one of which (Lumbda numerical
brute-force at 59s) had been superseded months ago by the native
symbolic rewriter and cached-replay path already described earlier
in §8. §8.6 lagged and kept claiming "40x faster than brute-force"
when the symbolic-vs-brute-force win is actually ~1,280x.
New six-row table contrasts:
- Python numerical 0.04 s
- Lumbda brute-force 59 s (kept for historical scale)
- Lumbda symbolic cold 46 ms (~1,280x over brute-force)
- Lumbda symbolic cached 7 ms
- Lean 4 cold rebuild 722 ms
- Lean 4 cached 5 ms
Three wins compound: symbolic over numerical (~1280x, MOAD-0001
at proof-methodology layer), asm over Lean's cold binary startup
(~16x), and cached replay over cold on both sides (~100x).
Explicitly notes that Lean's kernel TCB stays smaller even when
timings equalize (~3 KLOC audited elaborator vs ~6.6 KLOC asm
interpreter) — right tool for different assurance levels.
References existing make bench-proof target rather than adding
new plumbing; the numbers already come from proof/benchmark.sh.
Adds §6.6.3 "Collaborative Meta-GC: From Greedy to Adaptive" with
the three-workload benchmark (friendly / hostile / mixed × greedy
/ adaptive). Honest read of the numbers:
friendly greedy 732 ms 1000 resets, 0 escapes
friendly adaptive 691 ms 1000 resets, 0 escapes (-6%)
hostile greedy 568 ms 0 resets, 1000 escapes
hostile adaptive 607 ms 0 resets, 1000 escapes, 11 skipped (+7%)
mixed greedy 1981 ms 17 resets, 1983 escapes
mixed adaptive 1694 ms 14 resets, 1986 escapes, 2 skipped (-17%)
Adaptive wins on friendly (-6%) and mixed (-17%, the policy's
design target). On fully hostile workloads implicit GC fires 982
of 1000 arenas before the dispatcher sees them, so the signal is
drowned and greedy happens to edge adaptive by ~7%. Section
explicitly calls out the collaborative-but-local structure
(shared state on arena_active + EMA + countdown, decisions made
locally by each component) and credits the benchmark work with
surfacing two real correctness bugs in the conservative stack
scan — 24-byte strings misread as env nodes, 40-byte strings
misread as 25-element vectors — both now fixed.
Also:
- meta-gc-policy.dot rewritten to show the adaptive gate
(rate > 50% + probe countdown) before the greedy verify path;
new skip branch, new EMA annotations on edges.
- §6 reproducibility list + Makefile bench-gc-adaptive target.
- PDF rebuilt at 2.64 MB.
137 asm no-GC + 137 asm GC + 189 shared functional tests pass
against the new asm.
Adds §6.6 "Memory Management and the Meta-GC", covering:
- Bump-only default: why asm leaks, when that's fine, when
it isn't (two-crash anecdote links to the CLAUDE.md safety
envelope).
- Naive mark-sweep control group: GC_NAIVE assemble flag,
per-block header, stop-the-world mark + first-fit free list.
Control-group numbers: bump 134 MB / 1097 ms vs naive 1.1 MB /
1431 ms → 124x less memory at ~30% throughput cost.
- Meta-GC layer (§6.6.1): (with-arena thunk) fast path with
three-way policy — implicit-GC-fired / no-mark-in-range /
mark-in-range → skip / bulk-reset / sweep-fallback.
Bench: 2000 arena calls on truly-transient workload: 2000
resets / 0 escapes / 205 MB reclaimed, full GCs drop from
200 (Phase A) to 1 (Phase B). Escape case tested: 20/20 caught,
data remains live.
- §6.6.2 What the control group tells us: three co-resident
strategies, two stats surfaces (gc-stats, arena-stats), a
concrete floor (124x memory, 30% time, 100% arena hit on
scoped code) that any future proposal must beat.
Also calls out GNU assembler (GAS, AT&T syntax) + as + ld + GNU
binutils explicitly in the tier list (§intro) and the §11 asm tier
summary, and updates the stale 4,968 LOC to the current 6,645
across three locations (intro list, §6.6 narrative, §11 table,
§11 narrative). Reproducibility list in §6 now references
make bench-gc and make bench-gc-arena.
PDF rebuilt; all test suites (Python + C + asm no-GC + asm GC +
shared functional) still green against this revision.
New subsection documents the intra-asm benchmark: same chained-hash
algorithm, same 64 buckets, same hash function; only difference is
whether the bucket walk runs in Scheme (tree-walker) or in asm
(straight-line machine code).
Numbers (N=5,000 integers, i5-8350U):
insert 129 ms -> 6 ms (21x)
hit-lookup 124 ms -> 8 ms (15x)
miss-lookup 238 ms -> 12 ms (19x)
Also registers the bench in §6 reproducibility list.
Hunted the C --fast compiler bug that was hanging on the EML proof.
Narrowed to a specific pattern:
(let loop ((t start))
(let ((next (fn t)))
(if next (loop next) t)))
A named-let whose body is (let ((x (...))) (if x (recurse x) base)).
The recursive call inside the inner let+if branch never reaches the
loop closure — hangs or segfaults.
Reproducible with a 4-line test case; filed as
c/TODO-named-let-bytecode.md with minimal repro, suspected cause
(env-chain mismatch between PUSH_ENV and TAIL_CALL), and a known-
good workaround.
Workaround landed in proof/eml_proof_in_lumbda.lsp's `normalize`:
replaced the named-let with an internal recursive `define`, which
compiles correctly under --fast. Same logic, different surface
syntax. All four Lumbda tiers now verify the proof.
Benchmark refreshed (make bench-proof):
cold cached
Lumbda asm 46 ms 7 ms
Lumbda C --fast 65 ms 9 ms
Lumbda C (tree-walker) 87 ms 12 ms
Lumbda Python --fast 651 ms 232 ms
Lean 4 722 ms 5 ms
All four tiers now green. Asm still fastest (46 ms cold vs Lean's
722 ms — ~16× faster). Cached Lumbda asm 7 ms vs Lean 5 ms (within
1.5×). The C --fast tier went from "hangs" to 65 ms cold — competitive
with asm once the compiler bug is dodged.
Whitepaper §8.6 table updated; prior "(hangs)" row is gone;
footnote on the named-let workaround links the TODO file.
Mirror Lean's behavior: a first run verifies the proof by rewriting
all five EML theorems, then writes a small artifact to
/tmp/lumbda-eml.cache with a magic header and the PASS lines.
Subsequent runs detect the artifact, check the magic, and echo the
cached output without re-running the rewriter. `rm -f
/tmp/lumbda-eml.cache` forces a cold re-check (analogous to `lake
clean`).
The whitepaper §8.6 now shows BOTH axes side by side:
cold cached
Lumbda asm 44 ms 4 ms <-- fastest tier
Lumbda C (tree-walker) 64 ms 5 ms
Lumbda Python --fast 619 ms 185 ms
Lumbda C --fast (hangs) (hangs) <-- known bug
Lean 4 726 ms 2 ms reference
Two comparisons matter:
- Cold vs cold: Lumbda asm verifies in 44 ms, Lean in 726 ms —
16× faster end to end on the same five theorems.
- Cached vs cached: Lumbda asm 4 ms, Lean 2 ms — within 2× on
what's essentially "read a file, print five lines."
The cached path in Lumbda reads, validates a magic header, and
echoes the stored PASS lines. No term rewriting. Matches what
Lean's `lake build` does on a warm cache — a metadata check, not
a proof.
tests/bench-proof.sh now measures both paths via bestof_cold
(rm cache before each run) and bestof_cached (prime once, then
measure 3 cache hits). `make bench-proof` regenerates the table.
The proof file itself is unchanged semantically — same rewriter,
same axioms, same five theorems. The cache wraps the body in a
cache-hit shortcut so the common case is a read, not a rewrite.
Addresses fox's framing: EML isn't a language design invariant; it's
a well-executed demonstration. Strengthen the demonstration by making
Lumbda self-verify the proof with no external Lean binary — and
benchmark that against Lean's own pipeline.
proof/eml_proof_in_lumbda.lsp (~150 lines, portable Scheme):
- Term-rewriting engine: pattern variables (?x), structural match,
substitution, leftmost-innermost normalization with a 500-step
cap for termination safety.
- Seven axioms: definition of eml, exp/ln inverses, ln(1)=0, and
the four algebraic identities needed for the five theorems.
- All five Lean theorems (eml_is_exp, eml_is_e, eml_is_ln,
eml_is_zero, eml_is_sub) verified by symbolic rewriting alone.
No numerical evaluation. Same abstract-exp/ln axioms Lean uses.
Full coverage: all 5 of 5 Lean theorems reproduce in Lumbda.
Cross-impl: 5/5 pass in Python --fast, C default, and asm.
(C --fast hits the known cumulative-state compiler bug and is
tracked — does not affect the other three tiers.)
tests/bench-proof.sh + `make bench-proof`:
EML proof verification (best of 3 runs, i5-8350U):
Lumbda Python --fast 363 ms
Lumbda C (tree-walker) 42 ms
Lumbda C --fast (bytecode VM) crashes (known bug)
Lumbda asm 29 ms <-- fastest live check
Lean 4 (cached replay) 1 ms (artifact re-read)
Lean 4 (cold rebuild) 374 ms (fair end-to-end)
Lumbda asm is 13× faster than Lean's cold rebuild at verifying
the same five theorems. Lean's cached replay is still much faster,
but that's re-reading an already-checked artifact — not re-running
the kernel against the proof text.
Whitepaper §8.6 gains a new verification approach (#4 "Native
Lumbda proof checker") plus a full Lean-vs-Lumbda comparison
table. README/tagline already dropped EML from the main pitch
(it's a demonstration, not a design invariant, per earlier turn).
MOAD isolation is now the only spec-level claim in the subtitle.
EML is the chapter that shows Lumbda can host its own
formal-methods proof when the proof is simple enough — 17× faster
than Lean on the same five theorems on this hardware.
Sharpen the tagline per fox. Lumbda is not a "new" language; it is
a Lisp/Scheme-derived language whose two distinctive claims are
(a) EML mathematical universality (single-operator foundation,
machine-checked in Lean 4) and (b) MOAD defect isolation — each
of the four implementation tiers audited against the canonical
Mother-of-All-Defects patterns and hardened independently, so a
defect in one tier never propagates through shared infrastructure.
Whitepaper:
- Title subtitle now: "A Lisp/Scheme-derived, just-in-time lambda
language. Four implementation tiers with EML mathematical
universality and MOAD defect isolation. Workloads migrate
across basic UNIX systems."
- Abstract opens by naming the two invariants (EML, MOAD) before
getting to the feedback-primitive story. The bullet list now
shows four tiers: Python VM, C tree-walker, C bytecode VM, C
x86_64 JIT, pure assembly. The C binary bundles three tiers
under one executable, flag-selectable.
- §11 renamed from "Three Implementations" to "Four Implementation
Tiers" and opens with a paragraph framing MOAD isolation as the
architectural contract between them.
README gets the same framing up top so clones see the positioning
immediately.
No code changes, no test reruns, still 975 assertions green.
Language gets a proper name. Tagline per fox:
Lumbda — a just-in-time lambda language. Fast from first
principles, workloads migratable across basic UNIX systems.
Phase 1 scope: prose mentions of the language in the whitepaper,
README, and CLAUDE.md. File paths, binary names, and the repo
directory still use the historical "uncommonlisp" identifier —
those are Phase 2 (needs GitLab coordination + build-path edits).
- Whitepaper title "Feedback Is All You Need" → "Lumbda", with
the prior title preserved as a subtitle thread. New header
linkblock lists lumbda.com first, then uncloseai.com and
permacomputer.com.
- README.md opens with the tagline, points at lumbda.com.
- CLAUDE.md banner clarifies Lumbda-the-language vs the historical
repo/binary names.
- ~25 prose mentions of "uncommonlisp" in the paper are now
"Lumbda"; file-path refs (python3 uncommonlisp.py, ./c/uncommonlisp,
uncommonlisp.py, asm/uncommonlisp.s) unchanged.
- Benchmark methodology table widened slightly to fit the new
6-char label.
No behavior change, no benchmarks rerun, 975 tests still pass.
Fox flagged that the "A diagram is worth 10,000 words" quote
appeared twice in the paper but nothing was actually illustrated.
Fixed by:
1. Refreshing every .dot source to match current reality:
- docs/asm-architecture.dot: 22 KB (was "13 KB"), 14 syscalls
(was 4), 91 builtins (was 34), djb2 hash (was "linear scan"),
TCP stack + heap-snapshot + portal boxes added.
- docs/benchmark-sumto.dot: sum-to(1M) i5-8350U numbers; C
--fast 238 ms, asm 670 ms, Python --fast 5,136 ms. Was
sum-to(50k) with stale numbers.
- docs/benchmark-ack.dot: ackermann(3,8) i5-8350U numbers. Was
ack(3,4) with stale numbers.
- docs/benchmark-binary-size.dot: asm 22 KB, C 205 KB, busybox
2.1 MB, python3 8.0 MB. Was comparing against different
baselines.
2. Regenerated all PNGs via `make docs`.
3. Embedded in the paper at meaningful points:
- §2 Architecture (Python): python-architecture.png
- §6.4 Three-way bench: benchmark-sumto.png, benchmark-ack.png
- §11 Three Implementations: c-architecture.png, asm-
architecture.png
- §11.3 HTTP + sockets: benchmark-binary-size.png
4. Removed the redundant quote from §12.3; the one in §11
remains because §11 now follows it with two real diagrams.
Prerequisite fox noted: "make sure diagrams are up to date before
using them to code." Done — every embedded figure has the current
numbers/topology, not the old ones.
Every benchmark in the whitepaper now has a Makefile target and
each in-paper result is tagged with its reproduce command.
New / refactored Make targets:
make bench Python tree-walker vs bytecode (§6.1-6.3)
make bench-3way 3-way Python/C/asm head-to-head (§6.4)
make bench-portal portal save+load timings (§7.5)
make bench-portal-cross 3x3 cross-impl portal matrix (§7.2)
make bench-web HTTP vs busybox / python http.server (§11.3)
make bench-rpc-chain Python → C relay → asm chain (§11.4)
make bench-all runs every bench above
bench-3way is a new script (tests/bench-3way.sh) that drives each
impl in its recommended high-performance mode and prints a clean
best-of-two comparison table matching §6.4.
Every script uses the six-layer safety envelope from CLAUDE.md
(ulimit -v + trap + timeout + explicit kill + pgrep verify).
Documented in the whitepaper's §6 Methodology block.
Whitepaper additions:
- §6 Methodology paragraph adds a "Reproducibility" block listing
every Makefile target alongside the section it backs.
- §12 MOAD Audit now cites the canonical MOAD taxonomy:
https://undefect.com/moad-cheat-sheet/
(MOAD-0001 through MOAD-0005) so readers can look up the defect
classes the paper references.
- §6.4, §7.2, §7.5, §11.3, §11.4 each end with a "Reproduce: make
bench-<name>" pointer tying the number to the script that
produces it.
Ran bench-3way on the i5-8350U:
Python --fast: sum-to(100k)=555ms, sum-to(1M)=5038ms, ack(3,8)=18740ms
C --fast: sum-to(100k)= 27ms, sum-to(1M)= 255ms, ack(3,8)= 1465ms
asm: sum-to(100k)= 67ms, sum-to(1M)= 692ms, ack(3,8)= 2300ms
Matches the table in the paper (best-of-two).
ack(3,8) was reported as "segfault" for the C impl in the previous
whitepaper revision. That was a stale observation — C has --fast
(bytecode VM with explicit frame stack) that handles deep recursion
cleanly. The benchmark table compared the wrong modes.
Corrected apples-to-apples:
- Python --fast (bytecode VM) — 17,004 ms on ack(3,8)
- C --fast (bytecode VM) — 1,433 ms **fastest of the three**
- asm native (tree-walker) — 2,322 ms
C's --fast wins every workload. asm still beats Python --fast by
~7x despite being a tree-walker, because it skips Python's per-op
overhead entirely.
c/main.c: --help text updated to clarify that --fast is required
(or `ulimit -s unlimited`) for deep recursion in the default
tree-walker mode. Attempted flipping --fast to default; reverted
because that surfaced a cumulative-state buffer overflow in the
bytecode compiler that only triggers after the full 189-test
functional suite but not on isolated scripts. Left as a TODO in
the code comment. 189 C tests + full test-all still pass.
Whitepaper §6.4 table now shows all three impls in their
high-performance configuration. Also noted that a pthread-with-
larger-stack wrapper would let the C tree-walker handle deep
recursion without --fast — tracked as low-priority future work
since --fast is strictly faster regardless.
Two landings fox requested.
§8.6 EML verification section gains a "First machine-checked
treatment" paragraph. Sub-agent WebFetched arXiv:2603.21852v2 and
confirmed Odrzywołek's original paper is pure LaTeX prose with
no formal tool; the Zenodo companion is symbolic-regression code,
not a verification artifact. Our Lean 4 proof appears to be the
first machine-checked EML formalization — five theorems, zero
`sorry`, no Mathlib dependency, 40× faster than the brute-force
numerical search.
§6.4 "Three Implementations Head-to-Head" is new — benchmark
numbers from the actual i5-8350U hardware, collected via in-
process `current-time-ms` timing on each impl:
sum-to(100k) asm 74 ms < C 121 ms < Python-fast 583 ms
sum-to(1M) asm 734 ms < C 1.2 s < Python-fast 5.4 s
ackermann(3,8) asm 2.4 s < Python-fast 18.5 s (C segfaults)
asm beats every other impl on every measurable workload. The C
interpreter segfaults on ack(3,8) — its evaluator uses the host
C stack, and deep recursion exhausts it. asm and Python-fast use
explicit frame storage and handle deep recursion cleanly.
Also documents what I tried and backed off:
- asm env-lookup inline cache: upper bound ~5% win, not 20-40%,
because asm chains are typically 2 deep. Parked.
- asm's real bottleneck is `env_define` allocating 24 bytes per
parameter per call — 48 MB for sum-to(1M). Future optimization:
per-frame batched allocation or self-tail-call env reuse.
Profiling done on the real hardware. No inline-cache code change
landed; the finding itself is the commit.
Two additions requested by fox:
1. Methodology is now explicit: GNU assembler (GAS, AT&T syntax),
Intel Core i5-8350U 8th-gen mobile, Ubuntu 24.04, Linux 6.17,
gcc 13.3, as 2.42, Python 3.12. Loopback TCP for all socket
benchmarks. Same hardware across every benchmark in the paper.
2. §12 opens with the Lean EML proof as the rigor standard. The
proof is 40× faster than the brute-force numerical verification
it replaced — that speedup IS the MOAD-0001 story at the proof
layer. We hold the implementations to the same bar: hot paths
must be fast for a reason (hash / cache / O(1) invariant), not
by benchmark luck; correctness must hold for a reason, not
coincidence.
The scanner is the second line; building with understanding is
the first. Noted that the most recent scan found 18 HIGH MOAD-
0001 candidates in C — all inspected individually turn out to
be false positives (bounded-depth ancestor walks, hash bucket
chain walks already O(1) amortized, static 6-element tables,
one-shot option parsing). The MOAD-0003 Python flags are
scanner misfires on a non-ContextVar Env.set() method.
New-work-introduced MOAD-0001 defects: zero. All defects fixed
in this paper (intern_symbol, _define_record_type,
bi_string_replace, _tokenize_lines, Env.lookup shortcut) were
surfaced by other pressures — benchmarks, crashes, portal
exchanges — not the scanner.
Two small updates surfaced by the whitepaper audit + the
just-landed Env.lookup commit (68c3d3a):
1. §12.1 MOAD-0001 adds a third correctness fix (Env.lookup
shortcut skipping intermediate parent frames). The bug got
triggered by the portal-over-HTTP client where a let-loop
accumulator named `count` collided with the SRFI-1 `count`
builtin; the inner (let ((next ...))) frame had no `count`,
the shortcut returned the global builtin, and OP_LOOK_ADD1
died with `function + int`.
2. Abstract's C line count refined: "~9,000 lines" →
"9,164 lines of runtime C (plus ~1,200 in the test harness)"
to match the real numbers.
The main story, four scopes of feedback, and all benchmark numbers
remain current. No other drift.
New subsection documenting the portal-over-HTTP demo shipped in
commit 06b93c5. Closes the Future Work item about continuation-
style payloads traversing HTTP endpoints.
Covers:
- The server endpoint that returns an S-expression portal body as
HTTP/1.0 content-type application/scheme.
- The client that strips HTTP headers, splits by newline, evals
each form. ~90 lines of portable Scheme on each side.
- 3×3 server/client matrix: every runtime hosts, every runtime
consumes. Nine cells green.
- The eval-to-global-env semantic alignment (a two-line fix in
Python and C that matches asm's long-standing bi_eval behavior).
Closing passage frames the four scopes of feedback as now all
running demos:
- within process: call/cc
- across process: portal files
- across implementations: S-expression serialization
- across machines: sockets (HTTP, RPC, raw TCP)
Removed the now-redundant "Portal over HTTP" item from §13 Future
Work. Added "Continuation-passing over HTTP" as its successor —
moving live continuations (not just bindings) via call/cc + JSON
portal + TCP.
The existing §11.5 "heap-snapshot" moves to §11.6.
Adds the cross-runtime chain story to the abstract and a new §11.4
"S-expressions over Sockets: RPC, REPL, and Chains" documenting:
- read-from-string + eval + symbol->string as the primitives that
close the loop (asm gets these as native builtins in ~100 bytes).
- Whitelisted RPC (examples/rpc-server.lsp, safe dispatch) vs full
remote REPL (examples/repl-server.lsp, persistent global env,
DANGER). 9/9 server×client matrix green across Python/C/asm.
- Transparent byte-forwarding relay (examples/rpc-relay.lsp). Chains
compose naturally because the envelope is Scheme source: Python
client → C relay → asm backend through zero format translation.
Measured chain table (200 ping requests):
Py → asm (direct) 2,061 rps
Py → C → asm 1,234 rps (+605 µs/hop)
Py → Py → C → asm 766 rps
asm → Py → C → asm 796 rps
The existing §11.4 "heap-snapshot" moves to §11.5; no other section
numbers shift.
Abstract + implementation numbers refreshed:
- Python source: 3,743 lines (was 3,678)
- Asm source: 4,968 lines (was 4,527)
- Asm binary: 22 KB stripped (was 45 KB unstripped — the old
number conflated stripped vs unstripped)
- Asm builtins: 91 (was 87), now includes eval, read-from-string,
symbol->string
- Asm syscalls: 14 (adds clock_gettime)
- vs busybox: 96× smaller (was 46× on the unstripped basis)
- vs python3: 360× smaller (was 176×)
The closing "one file is the proof — by three translations" remains
intact; the addition is that the interchange now works over sockets
too, not just files.
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.
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).
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.
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.
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