Files
AILang/docs/specs
Brummel f7226cfba5 spec: branch-consume-split drop soundness (#63 leg 3)
Design spec for the last open leg of bug #63: an (own ...) heap
param consumed on one branch of a match/if but live on a sibling
branch is never dropped on the live branch, leaking one slab per
call. This is the residual live=2 leak in series_sma that keeps
the series milestone #61 from closing.

The leak is general (match AND if, not Series-specific): the
per-fn aggregate consume_count — the worst-case max over all
branches from uniqueness::merge_states — is codegen's only consume
signal, and every owned-param drop site gates on it. A param
consumed on some branch makes the aggregate >= 1, so every drop
site skips it, including the branch where it stays live.

Approach A (chosen, scope = the whole branch-consume-split class):
retain the per-branch consume snapshots the uniqueness walker
already computes and currently discards at the max merge; carry
them additively on MArm.consume and MTerm::If.{then,else}_consume
(attached by lower_to_mir in structural traversal-order lock-step,
since Term/Match/If/Arm carry no node id); codegen reads them at
the branch drop sites. The check pass stays the sole consume
authority (the mir.3a direction).

Mechanism (refined during planning recon, simpler + provably safe
than the first draft's fn-return-disable): the drop sites PARTITION
by aggregate consume, so the per-branch map only ever ADDS drops
for the leak class and never collides with an existing site:

  - fn-return dec (lib.rs) and arm-close pattern-binder dec: UNCHANGED.
    They keep firing for aggregate == 0 (live on every path).
  - pre-tail-call dec (match_lower.rs): gate source switches from the
    per-fn aggregate to the per-arm map. Identical decision for legs
    1/2 (aggregate 0 => per-arm 0 on every arm); additionally drops a
    param live on this tail-call arm but consumed on a sibling.
  - NEW fall-through drop (match arms + if branches), before the br to
    the join: fires ONLY for the leak class — branch_consume == 0 AND
    aggregate >= 1.

Double-free safety is disjointness by aggregate: the new drop needs
aggregate >= 1, fn-return needs aggregate == 0 — mutually exclusive,
so no fn-return disable and no tail-position analysis are needed.
Use-after-free safety: the checker REJECTS using an owned value
after it was consumed on any branch (use-after-consume), pinned green
by use_after_consume_on_own_param_is_reported and
harden_ownership_heap_double_consume_still_errors — so an aggregate>=1
param is provably never live past the construct, making the per-branch
drop path-terminal regardless of tail position.

grounding-check PASS (all load-bearing assumptions ratified by green
tests / structural fact, incl. the disjointness and use-after-consume
claims). Two RED repros carried: the already-committed ignored match
pin and a new if-branch pin.

refs #63
2026-06-02 15:18:08 +02:00
..