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:
+13
-12
@@ -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)]
|
||||
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user