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:
2026-05-09 00:14:56 +02:00
parent c79f7e2f00
commit 7e5c95f6c6
4 changed files with 168 additions and 40 deletions
+7 -1
View File
@@ -871,8 +871,14 @@ impl<'a> Emitter<'a> {
out.push_str(" br i1 %is_null, label %ret, label %live\n");
out.push_str("live:\n");
if has_env {
// The pair layout is `{ ptr thunk, ptr env }`; env is the
// second field. Note the single braces — this is a plain
// `push_str`, not a `format!` call, so brace-escaping does
// not apply. (Iter 18c.4 originally shipped doubled braces
// here; surfaced when the rc backend became the corpus
// default and a closure-with-captures escaped.)
out.push_str(
" %ea = getelementptr inbounds {{ ptr, ptr }}, ptr %p, i64 0, i32 1\n",
" %ea = getelementptr inbounds { ptr, ptr }, ptr %p, i64 0, i32 1\n",
);
out.push_str(" %env = load ptr, ptr %ea, align 8\n");
out.push_str(&format!(" call void @{env_drop}(ptr %env)\n"));