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
+13 -12
View File
@@ -125,16 +125,17 @@ enum Cmd {
/// Optimization (e.g. `-O2`); default `-O0` for debuggability.
#[arg(long, default_value = "-O0")]
opt: String,
/// Bench iter: heap allocator. `gc` (default) is Boehm
/// conservative GC; `bump` swaps every `@GC_malloc` for a
/// no-free 256 MB bump-allocator stub from `runtime/bump.c`.
/// The bump path is bench-only — it leaks every allocation.
/// `rc` (Iter 18b plumbing) routes through `runtime/rc.c`'s
/// `@ailang_rc_alloc` (libc-malloc backing + 8-byte refcount
/// header); inc/dec instrumentation arrives in 18c, so 18b
/// programs leak under this mode but must produce correct
/// stdout.
#[arg(long, default_value = "gc", value_parser = ["gc", "bump", "rc"])]
/// Heap allocator. `rc` (default, canonical) routes through
/// `runtime/rc.c`'s `@ailang_rc_alloc` (libc-malloc backing +
/// 8-byte refcount header) with `ailang_rc_inc`/`_dec`
/// instrumentation emitted by codegen; this is the runtime
/// AILang's memory model (RC + uniqueness, Decision 10) is
/// designed for. `gc` retains the Boehm conservative GC path
/// (`@GC_malloc`, libgc); it is kept as a differential parity
/// oracle for codegen diagnosis (Decision 9). `bump` is a
/// bench-only no-free stub from `runtime/bump.c` — it leaks
/// every allocation by design.
#[arg(long, default_value = "rc", value_parser = ["gc", "bump", "rc"])]
alloc: String,
},
/// Build into a tempdir and execute. Exits with the binary's exit code.
@@ -146,8 +147,8 @@ enum Cmd {
/// Optimization (e.g. `-O2`); default `-O0` for debuggability.
#[arg(long, default_value = "-O0")]
opt: String,
/// Bench iter: heap allocator. See `build --alloc` for details.
#[arg(long, default_value = "gc", value_parser = ["gc", "bump", "rc"])]
/// Heap allocator. See `build --alloc` for details. Default `rc`.
#[arg(long, default_value = "rc", value_parser = ["gc", "bump", "rc"])]
alloc: String,
/// Args passed through to the compiled program.
#[arg(last = true)]