Iter 18c.3: uniqueness inference + non-recursive RC inc/dec
Ends the leak-everything era under --alloc=rc. Codegen now emits
ailang_rc_inc at every Term::Clone site and ailang_rc_dec at
end-of-scope of trackable RC-allocated let-binders. Default
--alloc=boehm path is byte-identical to before this iter.
Two parts:
(A) New module ailang-check::uniqueness — post-typecheck
dataflow producing UniquenessTable: BTreeMap<(def, binder),
UniquenessInfo>. consume_count is max-over-paths: at if/match
the walker saves state, walks each arm against the snapshot,
and merges by taking the per-binder max. Term::Clone is a
Borrow on its inner term — the explicit clone is the user's
signal that a fresh ref is being produced via inc.
(B) Codegen emission in ailang-codegen. Term::Clone emits inc
under --alloc=rc, gated on val_ty == "ptr" and
!val_ssa.starts_with('@') (top-level fn closure-pair globals
live in the LLVM data segment, not in runtime/rc.c's heap).
Term::Let emits dec at scope close iff the value is
rc_alloc'd (Term::Ctor / Term::Lam in escape set under
--alloc=rc), val_ty == "ptr", consume_count == 0 (no callee
or outer site already took ownership), body's tail SSA is
not the binder itself (caller-takes-ownership case), and the
block isn't already terminated.
Header declares of @ailang_rc_inc / @ailang_rc_dec are gated on
--alloc=rc so Boehm/Bump IR shape is unchanged.
Fixture examples/rc_box_drop.ail.json: single-cell Box(Int) ADT
where shallow free is sufficient (no boxed children). E2E test
alloc_rc_emits_dec_for_unique_let_bound_box runs it under both
gc and rc, asserts byte-identical stdout, clean exit, expected
output (42).
Scope cuts deferred to 18c.4: per-type drop fn / recursive dec
cascades for ADTs with boxed children (List, Tree still leak
their tails). Fn params have no dec emission yet (no static
"caller handed off ownership" signal under Implicit mode);
under explicit (own T) the signal exists but wiring is 18d.
Tests: E2E 52 -> 53, ailang-check unit 38 -> 43 (+5 uniqueness),
all other buckets unchanged. cargo test --workspace green.
This commit is contained in:
@@ -1345,3 +1345,37 @@ fn alloc_rc_matches_gc_on_std_list_demo() {
|
||||
"alloc=rc must match alloc=gc on std_list_demo"
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 18c.3: codegen actually emits `ailang_rc_dec` at end-of-scope
|
||||
/// for trackable RC binders under `--alloc=rc`. This is the first
|
||||
/// fixture where the `dec` path runs at runtime — `b` is bound to a
|
||||
/// heap-allocated `MkBox`, the box is read via `match` (a borrow,
|
||||
/// `consume_count == 0`), and the let scope closes before the fn
|
||||
/// returns Unit. Codegen emits `call void @ailang_rc_dec(ptr %v1)`
|
||||
/// after the match's join block; the box's refcount drops to 0 and
|
||||
/// the underlying `malloc`'d block is freed by `runtime/rc.c`.
|
||||
///
|
||||
/// Properties guarded:
|
||||
/// (1) the binary still produces the expected stdout (`42`) — i.e.
|
||||
/// dec does not free the box BEFORE `match` reads its `Int`
|
||||
/// payload;
|
||||
/// (2) the binary exits cleanly (exit code 0, no segfault) — i.e.
|
||||
/// the dec call does not double-free or trip
|
||||
/// `ailang_rc_dec`'s underflow guard;
|
||||
/// (3) the GC and RC paths produce byte-identical stdout — i.e.
|
||||
/// the dec emission did not perturb behaviour visible at the
|
||||
/// output boundary.
|
||||
///
|
||||
/// Box has no boxed children, so the shallow-free contract of 18c.3's
|
||||
/// `ailang_rc_dec` is sufficient. A recursive ADT (List, Tree) would
|
||||
/// leak its boxed children silently — that's the 18c.4 scope, not
|
||||
/// this iter.
|
||||
#[test]
|
||||
fn alloc_rc_emits_dec_for_unique_let_bound_box() {
|
||||
let example = "rc_box_drop.ail.json";
|
||||
let stdout_gc = build_and_run_with_alloc(example, "gc");
|
||||
let stdout_rc = build_and_run_with_alloc(example, "rc");
|
||||
assert_eq!(stdout_gc.trim(), "42");
|
||||
assert_eq!(stdout_rc.trim(), "42");
|
||||
assert_eq!(stdout_gc, stdout_rc, "alloc=rc must match alloc=gc on rc_box_drop");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user