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.
8.1 KiB
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.1–mir.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 astr_concatStr accumulator throughrecur(build"*"×5), returning it.mainprints 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*****\n— correct. But underAILANG_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, anew-style ctor fn(new Temp 21), aprelude.Showinstance,mainprints via(app print t).- Fits scope: the #51/#53
new-over-user-ADT codegen path plus the synthesisedprint__Temp. - Outcome: built, ran, stdout
21C— correct. (live=2under RC stats; same class as Finding 1 — theint_to_strShow result crosses theshow/printboundary.)
/tmp/ail-fieldtest/mir_3_color.ail + mir_3_main.ail — cross-module print
mir_3_colordefinesdata Color, ctor fnmkColor, and itsShowinstance.mir_3_mainimports it, constructs viamir_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#255— correct. The cross-module type ofcwas inferred forprintwith no annotation. (live=2, same Finding-1 class.)
/tmp/ail-fieldtest/mir_4_render_loop.ail — combined render loop
data Cell+ ctor fncell+Show;renderloops indices, builds aCelleach step via thenew-style ctor,shows it, threads the rendered Str accumulator throughrecur.mainprintsrender 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:
5cno-loopstr_concatlet-bound + print, inline inmain→live=0.5d/5eloop threadingstr_concatacc throughrecur, inline inmain→live=0(matches curatedloop_str_recur_literalpin).5b/5fthe same loop inside a helper fn returning Str to the caller →live=1.5gno loop at all — helper fn returns a plainstr_concatheap-Str, caller binds + prints →live=1.5hhelper returns anint_to_strheap-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. thelive=0mir_5c/5d/5e. - What happened: every program that builds a heap-allocated
Str(viastr_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, stdouthi!. The same expression inlined intomain(mir_5c) yieldsallocs=2 frees=2 live=0. - Why it is a
bug: a non-zerolivecount underAILANG_RC_STATSis the exact metric the milestone's curated*_no_leak_pinfixtures assert aslive=0; the design contract0008-memory-model/0011-str-abipromise 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 insidemain, 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.ail→live=1(expectedlive=0). - Recommended downstream action:
debug— RED-pinmir_5g(andmir_5h) atlive=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 anew-style ctor fn, monomorphically, and(app print …)dispatched to the userShowinstance.checkclean (ok (36 symbols across 3 modules)),runproduced21Cand[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
Showinstance defined inmir_3_color, a value constructed andprinted from the importingmir_3_main.run mir_3_mainloaded 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-moduleprint__<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 inmain, RC is balanced (live=0). - Why
working: the #49 value-correctness (right characters, right order, no corruption) holds; thelive=0property holds for the in-mainshape 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 |