Files
AILang/crates/ailang-codegen
Brummel 62cd4b4ee7 fix: drop owned temp passed to a borrow slot at the call site (closes #43)
GREEN side of the owned-RawBuf drop-leak. RED test
raw_buf_owned_drop_balances_rc_stats (47efbdb) now passes:
allocs=2 frees=2 live=0, stdout still "10".

The leak. An owned temporary returned by an Own-ret call and then
passed only to a `borrow`-mode parameter is dead after that call but
was dropped by no one. In the repro, RawBuf.set returns (own RawBuf)
in-place (the intercept emits `ret ptr %b`, the same slab it received),
and that result is passed to RawBuf.get whose RawBuf param is `borrow`,
so get does not consume it and returns an i64. After get the owned
slab is dead. Codegen's two scope-close drop emitters cover only
Term::Let binders (the let gate) and Own fn-params; the general call
lowerer emit_call spliced args without inspecting per-arg modes, so an
owned arg landing in a borrow slot was never dec'd. The leak is
binder-independent — the fully inline form with no `let` at all leaks
byte-identically, which is why the fix lives at the call site, not at
the let gate.

The fix (emit_call, codegen-only, +50 lines, uniqueness.rs untouched).
After a non-tail call, for each argument that is itself an
is_rc_heap_allocated owned temporary (Own-ret call / fresh escaping
ctor / lambda — never a plain Var alias, whose owner is some other
binder dropped elsewhere) AND lands in a Borrow-mode param slot, emit
a drop of that argument's SSA through the same per-type drop symbol the
let gate uses (drop_symbol_for_binder). Per-param modes are read from
the Ail-level Type::Fn.param_modes in module_def_ail_types (which
survives monomorphisation: RawBuf_get__Int -> [Borrow, Implicit]);
note FnSig itself carries only LLVM type strings, not modes.

Why it cannot double-drop or drop a live value:
- set's buffer param is Own, not Borrow, so the rule does not fire for
  the new->set link; the single drop lands on the final temp after get.
  set is in-place (same pointer in and out), so that one drop frees the
  one slab exactly once.
- the rule excludes Var args, so it never races the let/Own-param
  emitters for a named binder.
- the dropped SSA is an input arg, always distinct from the fresh call
  result, and the rule is gated on !tail, so it can never dec a value
  that flows out as the surrounding fn's result.

Known limitation (advisory, no fixture exercises it, stated in a code
comment): a musttail call passing an owned temp into a borrow slot
still leaks — a tail call terminates the block, leaving no post-call
emission point.

Verified: full workspace green; ail e2e 95/95 incl. the 4 raw_buf
fixtures (60/4.0/42/reject) and the now-GREEN drop test; codegen
intercepts_bijection_with_intrinsic_markers green.
2026-05-30 10:49:49 +02:00
..