Files
AILang/docs/specs/0061-fieldtest-typed-mir.md
T
Brummel 7a99c5dbfa docs(fieldtest): record the typed-MIR milestone-close fieldtest
Milestone-close fieldtest of the typed-MIR work, run by a downstream
LLM author exercising only the public interface. Four curated scenarios
plus a reduction-bisect set, derived top-down from the milestone promise
(heap-Str-across-recur, new-over-user-ADT, cross-module print__<UserType>,
combined render loop).

Three [working] findings ratified: new-over-user-ADT + Show + print
(#51/#53 class), cross-module synthesised print, and heap-Str-across-recur
value correctness all produced correct output first try.

One [bug] found and orchestrator-verified: returning an owned heap-Str
from a callee fn across the call boundary leaks exactly one RC slab
(live=1) — producer-independent (str_concat, int_to_str), loop-independent,
silent (output correct). It sits in the coverage gap the curated
*_no_leak_pin fixtures leave open (they keep the heap-Str expression inside
main). Distinct from #49 (loop-carried within one frame); this is the
function-return leg. Routed to debug RED-first; blocks the deliberate
milestone-close until GREEN.
2026-06-01 01:53:46 +02:00

8.1 KiB
Raw Blame History

Fieldtest — typed-MIR milestone — 2026-06-01

Status: Triaged 2026-06-01 — the [bug] finding (heap-Str returned from a fn leaks one slab at the call boundary) verified by the orchestrator (mir_5g live=1, mir_5c inline-control live=0) and routed to debug (RED-first); the three [working] findings ratified as carry-on. This leak blocks the deliberate milestone-close until GREEN. Author: fieldtester (dispatched by fieldtest skill, milestone-close gate) Binary exercised: cargo run -q -p ail (debug profile, built from HEAD bd62f2b working tree; cargo build -q -p ail exit 0 before any run).

Scope

The typed-MIR milestone (spec docs/specs/0060-typed-mir.md, iterations mir.1mir.5, commit range up to bd62f2b) restructured the compiler so the type checker hands codegen a fully typed MIR and codegen re-derives nothing. No surface-language change. The milestone's value is RC/drop and codegen correctness on the reworked paths — three latent bugs lived here recently: a heap-Str loop-carried leak (#49, closed mir.4), and two new-over-ADT codegen crashes (#51/#53, closed). This fieldtest probes those paths from a downstream LLM author's seat, with only the public interface (design ledger, examples/ corpus, CLI). The four curated scenarios were derived top-down from the milestone promise: heap-Str-across-recur, new-over-user-ADT, cross-module synthesised print__<UserType>, and a combined render loop.

Examples

/tmp/ail-fieldtest/mir_1_loop_str.ail — heap-Str-across-recur (two shapes)

  • greet: a loop whose exit arm is a bare static string literal "done". banner: a loop threading a str_concat Str accumulator through recur (build "*"×5), returning it. main prints both.
  • Fits scope: directly the #49 heap-Str-loop path, both the bare-literal-exit and the threaded-accumulator variant the carrier named.
  • Outcome: built, ran, stdout done\n*****\ncorrect. But under AILANG_RC_STATS=1: allocs=7 frees=5 live=2 (see Finding 1).

/tmp/ail-fieldtest/mir_2_new_adt.ail — new-over-user-ADT, show a field

  • data Temp, a new-style ctor fn (new Temp 21), a prelude.Show instance, main prints via (app print t).
  • Fits scope: the #51/#53 new-over-user-ADT codegen path plus the synthesised print__Temp.
  • Outcome: built, ran, stdout 21Ccorrect. (live=2 under RC stats; same class as Finding 1 — the int_to_str Show result crosses the show/print boundary.)

/tmp/ail-fieldtest/mir_3_color.ail + mir_3_main.ail — cross-module print

  • mir_3_color defines data Color, ctor fn mkColor, and its Show instance. mir_3_main imports it, constructs via mir_3_color.mkColor 255, and (app print c).
  • Fits scope: the synthesised cross-module print__<UserType> path — a Show instance resolved from a different module than the print site.
  • Outcome: built, ran (run mir_3_main.ail, 4 modules), stdout #255correct. The cross-module type of c was inferred for print with no annotation. (live=2, same Finding-1 class.)

/tmp/ail-fieldtest/mir_4_render_loop.ail — combined render loop

  • data Cell + ctor fn cell + Show; render loops indices, builds a Cell each step via the new-style ctor, shows it, threads the rendered Str accumulator through recur. main prints render 4.
  • Fits scope: the hardest combination — new-over-ADT + show + heap-Str-across- recur + per-iteration str_concat.
  • Outcome: built, ran, stdout [0][1][2][3]correct. (allocs=21 frees=4 live=17 — Finding 1, amplified by per-iteration intermediate Strs.)

Reduction fixtures (Finding-1 bisect, all in /tmp/ail-fieldtest/)

mir_5a..mir_5h isolate the leak. Key results under AILANG_RC_STATS=1:

  • 5c no-loop str_concat let-bound + print, inline in mainlive=0.
  • 5d/5e loop threading str_concat acc through recur, inline in mainlive=0 (matches curated loop_str_recur_literal pin).
  • 5b/5f the same loop inside a helper fn returning Str to the caller → live=1.
  • 5g no loop at all — helper fn returns a plain str_concat heap-Str, caller binds + prints → live=1.
  • 5h helper returns an int_to_str heap-Str → live=1.

Findings

[bug] Heap-Str returned from a user function leaks one slab at the call boundary

  • Surfaced in: mir_1, mir_2, mir_3, mir_4 (all live>0); isolated by mir_5f/5g/5h vs. the live=0 mir_5c/5d/5e.
  • What happened: every program that builds a heap-allocated Str (via str_concat, int_to_str, or a loop returning Str) inside a callee fn and returns it across the call boundary leaks exactly one RC slab per such return. Verbatim, mir_5g_concat_return.ail (no loop): ailang_rc_stats: allocs=2 frees=1 live=1, stdout hi!. The same expression inlined into main (mir_5c) yields allocs=2 frees=2 live=0.
  • Why it is a bug: a non-zero live count under AILANG_RC_STATS is the exact metric the milestone's curated *_no_leak_pin fixtures assert as live=0; the design contract 0008-memory-model / 0011-str-abi promise RC balance. The leak is producer-independent (str_concat, int_to_str), loop-independent (mir_5g has no loop), and triggers on the most natural authoring move — factoring string-building into a named function. Output is correct, so it is a silent leak, not a crash. The curated pins all keep the heap-Str-producing expression inside main, so none cover the return-across-call-boundary case — the leak sits squarely in that gap. This is adjacent to #49 (the milestone's flagship heap-Str fix) but is a distinct leg: #49 was loop-carried supersede/result within one frame; this is the function-return of an owned heap-Str.
  • Repro: AILANG_RC_STATS=1 cargo run -q -p ail -- run /tmp/ail-fieldtest/mir_5g_concat_return.aillive=1 (expected live=0).
  • Recommended downstream action: debug — RED-pin mir_5g (and mir_5h) at live=0, then locate the missing dec/drop on the callee's owned heap-Str return value (or the caller's consumption of a returned owned Str). The loop variants (mir_5f) should fall out once the scalar return-of-heap-Str case is fixed.

[working] new-over-user-ADT (monomorphic) + Show + print is correct

  • Surfaced in: mir_2, mir_4.
  • What happened: (new Temp 21) / (cell i) constructed user ADTs via a new-style ctor fn, monomorphically, and (app print …) dispatched to the user Show instance. check clean (ok (36 symbols across 3 modules)), run produced 21C and [0][1][2][3] first try.
  • Why working: this is exactly the #51/#53 crash class. No crash, correct output, clean diagnostic. The fix held under fresh-eyes consumer use.

[working] Cross-module synthesised print__ is correct

  • Surfaced in: mir_3.
  • What happened: a Show instance defined in mir_3_color, a value constructed and printed from the importing mir_3_main. run mir_3_main loaded 4 modules, dispatched the cross-module print, stdout #255. No annotation needed on the print site; the cross-module type flowed through.
  • Why working: the synthesised cross-module print__<UserType> path the milestone reworked is correct from the consumer's seat. A clear win to pin against future drift.

[working] heap-Str-across-recur produces correct values

  • Surfaced in: mir_1, mir_4, mir_5d, mir_5e.
  • What happened: loops threading a Str accumulator through recur (*****, xyyy, yyy, [0][1][2][3]) and bare-literal-exit loops (done) all produced correct strings. When the loop is inline in main, RC is balanced (live=0).
  • Why working: the #49 value-correctness (right characters, right order, no corruption) holds; the live=0 property holds for the in-main shape the curated pins cover. Recorded so the value-correctness win is not lost when Finding 1's leak is fixed.

Recommendation summary

Finding Action
[bug] heap-Str returned from a fn leaks at the call boundary debug
[working] new-over-user-ADT + Show + print carry-on
[working] cross-module print__ carry-on
[working] heap-Str-across-recur value correctness carry-on