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.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
# 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 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*****\n` — **correct**. 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 `21C` — **correct**. (`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 `#255` —
|
||||
**correct**. 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, `show`s 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 `main` → `live=0`.
|
||||
- `5d`/`5e` loop threading `str_concat` acc through `recur`, **inline in
|
||||
`main`** → `live=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.ail` → `live=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__<UserType> is correct
|
||||
- Surfaced in: mir_3.
|
||||
- What happened: a `Show` instance defined in `mir_3_color`, a value
|
||||
constructed and `print`ed 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__<UserType> | carry-on |
|
||||
| [working] heap-Str-across-recur value correctness | carry-on |
|
||||
Reference in New Issue
Block a user