runtime: half-retirement of Boehm — flip CLI default to --alloc=rc
The dual-allocator policy from 2026-05-08 made Boehm the CLI default
"for general workloads", with rc selected explicitly for real-time-
sensitive code. That asymmetry contradicted Decision 10: rc + uniqueness
inference is the canonical memory model AILang's typechecker enforces,
and the CLI was teaching the opposite mental model to users and prompt-
fed LLMs.
This iter flips the asymmetry without removing Boehm:
- main.rs: default_value gc -> rc for build/run; help text rewritten
so rc is the canonical path and gc is the parity oracle.
- DESIGN.md Decision 9 retitled "RC canonical, Boehm parity oracle"
and rewritten end-to-end. Migration plan step 6 updated to a
two-step retirement (default-flip done, full removal gated).
- DESIGN.md pipeline diagram: rc-first, gc-as-oracle.
- JOURNAL: 2026-05-09 entry recording the call, what shipped,
the bug the flip surfaced, the gating condition for full
Boehm removal.
Bug surfaced by the flip and fixed in the same iter:
codegen/drop.rs build_pair_drop_fn emitted `getelementptr inbounds
{{ ptr, ptr }}, ...` via push_str (not format!), so the doubled
braces leaked verbatim into the IR. LLVM parsed it as a struct-of-
struct, the GEP referenced a non-existent field, and clang failed.
Invisible under the old gc default (no per-type drop fns) and
invisible to the 8 explicit _with_alloc("rc") tests (none of them
escaped a closure-with-captures). Caught immediately by two corpus
tests once rc became the baseline: closure_captures_let_n,
local_rec_as_value_capture_demo. Fix: single braces. Tests stay
as the regression guard.
The episode is the canonical justification for keeping the GC arm
as a parity oracle for now — a months-old codegen bug found by
flipping the baseline column.
288 passed / 0 failed / 3 ignored, unchanged.
This commit is contained in:
+105
@@ -9764,3 +9764,108 @@ The 20-family is now formally closed with this tidy. Codebase
|
||||
is in good form: no architecturally load-bearing drift, all
|
||||
tests green, two minor items recorded as acceptable. The next
|
||||
iter can be a feature pick from the queue.
|
||||
|
||||
## 2026-05-09 — Boehm half-retirement: CLI default flips to rc
|
||||
|
||||
### Why
|
||||
|
||||
The "Boehm retirement" item in the post-tidy queue surfaced via
|
||||
a direct user question: *kann Boehm weg, was gewinnen wir
|
||||
dadurch?* The orchestrator's reading: full removal is premature
|
||||
— Boehm earns its keep right now as a differential parity
|
||||
oracle for codegen diagnosis — but the asymmetry (Boehm = CLI
|
||||
default) actively teaches the wrong mental model. RC is the
|
||||
canonical runtime per Decision 10, and the CLI was telling
|
||||
users and prompt-fed LLMs the opposite. So this iter ratifies a
|
||||
half-retirement: flip the default, keep the oracle, document
|
||||
the gating condition for full removal.
|
||||
|
||||
### What shipped
|
||||
|
||||
- `crates/ail/src/main.rs`: `default_value = "gc"` → `"rc"` for
|
||||
both `build` and `run` subcommands. Help text rewritten so
|
||||
`rc` is described first as canonical (Decision 10 pointer),
|
||||
`gc` as parity oracle, `bump` as bench-only. The doc reference
|
||||
to "Iter 18b plumbing — programs leak under this mode" is
|
||||
removed; it was 18b-era and no longer reflects the matured RC
|
||||
pipeline.
|
||||
- `crates/ail/tests/e2e.rs`: `build_and_run` is unchanged —
|
||||
it picks up the CLI default, which now means the entire
|
||||
corpus runs under RC as the canonical baseline. The 8
|
||||
`build_and_run_with_alloc(...)` differential tests already
|
||||
pin both backends explicitly and continue to anchor the
|
||||
parity-oracle relationship.
|
||||
- `docs/DESIGN.md`:
|
||||
- Decision 9 retitled "RC canonical, Boehm parity oracle" and
|
||||
rewritten end-to-end. The 2026-05-08 dual-allocator framing
|
||||
is preserved as historical context; the live framing is
|
||||
asymmetric in RC's favour.
|
||||
- Migration plan step 6 (Iter 18f) updated: retirement is now
|
||||
explicitly two-step (default-flip done; full removal gated
|
||||
on the oracle ceasing to catch anything).
|
||||
- Pipeline diagram (L1585): rc-first, gc-second; gc labelled
|
||||
"parity oracle" instead of "transitional".
|
||||
- Wording around "transitional fallback" replaced with the
|
||||
new "canonical default since 2026-05-09" phrasing.
|
||||
|
||||
### Bug found by the flip
|
||||
|
||||
Flipping the corpus baseline to RC immediately surfaced a silent
|
||||
codegen bug from Iter 18c.4: `crates/ailang-codegen/src/drop.rs`
|
||||
build_pair_drop_fn emitted `getelementptr inbounds {{ ptr, ptr }}, ...`
|
||||
via `push_str` (not `format!`), so the doubled braces went
|
||||
verbatim into the IR. LLVM parsed that as a struct-of-struct
|
||||
`{ { ptr, ptr } }`, the GEP referenced a non-existent field
|
||||
index, and clang failed.
|
||||
|
||||
The bug was invisible under the old `gc` default — the gc
|
||||
backend doesn't emit per-type drop fns. It was also invisible
|
||||
in the 8 explicit `_with_alloc("rc")` differential tests because
|
||||
none of them happened to construct a closure whose env captures
|
||||
escaped (the only path that triggers the pair-drop emitter).
|
||||
Two corpus tests caught it the moment the default flipped:
|
||||
`closure_captures_let_n` and `local_rec_as_value_capture_demo`.
|
||||
|
||||
Fix: single braces. The two corpus tests stay as the regression
|
||||
guard. This is the canonical example of why the GC oracle is
|
||||
worth keeping: the bug had been latent for months, and the
|
||||
parity-baseline flip was the cheap probe that found it.
|
||||
|
||||
### What this iter does NOT do
|
||||
|
||||
- Not full Boehm retirement. `--alloc=gc` still works, libgc is
|
||||
still linked when selected, the oracle column in e2e is
|
||||
preserved.
|
||||
- Not a corpus expansion. Same e2e corpus, now running RC as
|
||||
baseline instead of GC.
|
||||
- Not a bench refresh. Bench fixtures and numbers from 18f are
|
||||
untouched.
|
||||
|
||||
### Gating condition for full retirement
|
||||
|
||||
Boehm comes out completely once the parity oracle stops paying
|
||||
its keep. Concretely: a few iter families (≥3, say) that ship
|
||||
without `--alloc=gc` catching any bug `--alloc=rc` did not
|
||||
already catch. At that point the differential probe is
|
||||
diagnostic dead weight, libgc-as-build-dep stops being a
|
||||
worthwhile cost, and the gc backend can come out in a follow-up
|
||||
iter (delete the gc arm of the e2e tests, drop the `-lgc` link
|
||||
flag, remove `--alloc=gc` from the CLI parser, mark Decision 9
|
||||
historical).
|
||||
|
||||
### Test state
|
||||
|
||||
288 passed / 0 failed / 3 ignored, same as pre-iter — the
|
||||
codegen fix is offset by the now-canonical-RC baseline catching
|
||||
no other corpus regressions.
|
||||
|
||||
### JOURNAL queue (updated)
|
||||
|
||||
- **`FnDef::synthetic(...)` factor-out** — unchanged; awaits
|
||||
next schema-additive `FnDef` field.
|
||||
- **Boehm full retirement** — re-queued with the new gating
|
||||
condition (≥3 families with no oracle wins).
|
||||
- **Deferred richer integration paths** (from 20f): tool-use
|
||||
schemas, MCP server, LSP.
|
||||
- **Family 21+** — typeclasses, polymorphic ADTs at runtime,
|
||||
pattern-binding generalisation.
|
||||
|
||||
Reference in New Issue
Block a user