Iter 18b: RC runtime + --alloc=rc routing

Wires up reference-counting allocator end-to-end without any
inc/dec emission. Programs run under --alloc=rc and produce
correct stdout (validated against --alloc=gc); they leak every
allocation, exactly like the pre-Boehm era. The point of 18b is
to establish the runtime contract before 18c adds the
inc/dec-emission codegen pass.

runtime/rc.c — new file. 8-byte uint64 refcount header
prepended to every payload; ailang_rc_alloc(size) returns a
ptr to the payload (header at ptr-8). ailang_rc_inc / dec are
declared but never called by codegen yet; they exist so 18c
can wire codegen against a stable runtime ABI. dec frees on
zero refcount but does NOT recursively dec child references —
that's 18c's job once it has per-ctor type info.

crates/ailang-codegen/src/lib.rs — AllocStrategy::Rc variant
added; fn_name() returns "ailang_rc_alloc". Single-line
extension because Iter 18a's bump path had already centralised
the allocator-symbol decision on fn_name() for all four
allocation sites.

crates/ail/src/main.rs — --alloc=rc accepted by Build/Run;
parse_alloc_strategy extended; locate_rc_runtime() helper
mirrors locate_bump_runtime; new Rc arm in build_to compiles
runtime/rc.c with clang -O2 -c and links the resulting .o
into the final binary (no -lgc).

E2E coverage: alloc_rc_produces_same_stdout_as_gc on
list.ail.json (42), alloc_rc_matches_gc_on_std_list_demo for
broader allocation-site coverage. Total e2e bundle: 51 tests
(was 49).

Hand-verified on:
  sum.ail.json --alloc=rc → 55
  list.ail.json --alloc=rc → 42
  borrow_own_demo.ail.json --alloc=rc → 3 then 6
  std_list_demo.ail.json --alloc=rc → matches --alloc=gc

cargo build/test --workspace green; git diff examples/ empty.
This commit is contained in:
2026-05-08 02:13:08 +02:00
parent 1e3697926b
commit 1eed78c41e
4 changed files with 271 additions and 5 deletions
+10 -1
View File
@@ -116,11 +116,19 @@ type Result<T> = std::result::Result<T, CodegenError>;
/// `Bump` swaps every `@GC_malloc` for `@bump_malloc`, which is supplied
/// by `runtime/bump.c` — a no-free, statically-sized arena allocator
/// used purely to quantify the GC's overhead via an A/B comparison.
/// The IR is otherwise byte-identical between the two strategies.
/// `Rc` (Iter 18b, Decision 10) routes allocation through
/// `@ailang_rc_alloc` from `runtime/rc.c`, which prefixes every payload
/// with an 8-byte refcount header. Iter 18b stops at allocator routing —
/// codegen does not yet emit `inc`/`dec` calls, so programs leak
/// every allocation under `Rc`. The actual instrumentation arrives in
/// Iter 18c once uniqueness inference is wired up.
/// The IR is otherwise byte-identical between the three strategies
/// modulo the allocator symbol name.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllocStrategy {
Gc,
Bump,
Rc,
}
impl Default for AllocStrategy {
@@ -135,6 +143,7 @@ impl AllocStrategy {
match self {
AllocStrategy::Gc => "GC_malloc",
AllocStrategy::Bump => "bump_malloc",
AllocStrategy::Rc => "ailang_rc_alloc",
}
}
}