Files
AILang/docs/journals/2026-05-12-iter-eob.1.md
T
Brummel 78e8338a8d iter eob.1: effect-op args walk as Borrow; heap-Str RC discipline closes
Language rule: Term::Do.args[*] are walked in Position::Borrow by
the uniqueness + linearity passes, symmetric to Term::Ctor.args[*]
being walked in Position::Consume. The kind itself carries the
ownership default — no per-op field on EffectOpSig. Three
analyser sites flip in lockstep: uniqueness.rs:289, linearity.rs:506,
and the shared doc-comment at linearity.rs:42.

Heap-Str-specific consequences (closing hs.4's leak):
- int_to_str / float_to_str ret_mode: Implicit → Own at four lockstep
  sites (builtins.rs:200,210 + synth.rs:172,179). The codegen
  trackability gate (drop.rs:464-475, iter 18g.2) now fires on these
  callees, so the let-binder receives a scope-close drop.
- drop_symbol_for_binder's App arm gets a Type::Con{name:"Str"} →
  "ailang_rc_dec" carve-out, symmetric to field_drop_call's existing
  Str arm. Without it, the new Own-trackable App binder would emit
  drop_<m>_Str (undefined).

Tests: the two pre-existing RED tests at e2e.rs (commit 592d87b)
flip to GREEN unchanged with predicted numbers (allocs=1,frees=1,
live=0 and allocs=2,frees=2,live=0). Two new positive tests pin
the rule's coverage: primitive-Int arg through io/print_int produces
zero RC traffic; heap-Str through 2× io/print_str in sequence
typechecks (no use-after-consume) and balances allocs=1,frees=1,
live=0.

DESIGN.md §Decision 10 gets the arg-position-policy table for both
AST node kinds (Ctor=Consume, Do=Borrow) plus a linking paragraph
explaining how Do=Borrow cooperates with ret_mode==Own.

heap-str-abi milestone closes here: WhatsNew entry shipped (Strings
produced at runtime now release cleanly), roadmap P1 entry checked
off, Post-22-Prelude depends-on line removed.

Workspace cargo test + cross_lang.py + compile_check.py all green.
check.py noisy latency cluster + bench_list_sum.bump_s ±12% — same
precedent as the previous two audits, not coupled to this milestone.
2026-05-12 21:29:05 +02:00

5.7 KiB

iter eob.1 — Effect-op args walked as Borrow; heap-Str RC discipline closes

Date: 2026-05-12 Started from: b5b0c2d7dc Status: DONE Tasks completed: 7 of 7

Summary

Land the language rule that Term::Do.args[*] are walked in Position::Borrow by the uniqueness and linearity passes, plus the two heap-Str-specific shape fixes (ret_mode: Own for int_to_str / float_to_str; Str carve-out in the App arm of drop_symbol_for_binder) that make the existing RED tests at crates/ail/tests/e2e.rs flip to GREEN under --alloc=rc. Both arg-position rules (Ctor = Consume, Do = Borrow) are anchored in DESIGN.md §Decision 10. The heap-str-abi milestone closes here: WhatsNew entry shipped, roadmap P1 entry checked off, downstream Post-22-Prelude depends on: line removed.

The five plan layers landed first-shot in lockstep — no review re-loops fired on any of the 7 tasks. The pre-existing RED tests (int_to_str_drop_balances_rc_stats, str_field_in_adt_drops_heap_str_correctly) flipped to GREEN with the predicted concrete numbers (allocs == 1, frees == 1, live == 0 and allocs == 2, frees == 2, live == 0). Two new positive tests pin the rule's coverage at the primitive boundary (Int through io/print_int: zero RC traffic) and at the linearity boundary (heap-Str through two sequential io/print_str calls: allocs == 1, frees == 1, live == 0). Workspace test --workspace + cross_lang.py

  • compile_check.py all green. check.py shows the same noisy latency-max + bump_s cluster the audit-cma and audit-ms journals both left pristine ("not coupled to milestone, baseline left untouched for second consecutive audit") — same precedent applies here; baseline left as-is.

Per-task notes

  • iter eob.1.1: Term::Do arg-walks flip Consume → Borrow in both uniqueness.rs:289 and linearity.rs:506; linearity.rs:42 doc comment split into two bullets matching the new code shape. Inline clarifying comment added on the linearity-side flip mirroring the Ctor-arm convention. cargo build + workspace check-crate tests green.
  • iter eob.1.2: builtins.rs float_to_str (:200) + int_to_str (:210) and synth.rs float_to_str (:172) + int_to_str (:179) all flip ret_mode: Implicitret_mode: Own in lockstep. Workspace build clean. Target test now reports the predicted link-error shape "undefined value @drop_int_to_str_drop_rc_Str" — exactly the gap Task 3 closes.
  • iter eob.1.3: drop.rs App arm of drop_symbol_for_binder gets the Str carve-out if name == "Str" return ailang_rc_dec, symmetric to field_drop_call's existing Str arm. The comment block mirrors field_drop_call's wording. cargo build clean.
  • iter eob.1.4 (observational): both RED tests flip to GREEN with the predicted concrete numbers (allocs == 1, frees == 1 / allocs == 2, frees == 2; live == 0 on both). Full e2e suite (79 tests) all green — no other fixture's RC trace shifted unexpectedly.
  • iter eob.1.5: two new fixtures (examples/int_to_print_int_borrow.ail.json, examples/heap_str_repeated_print_borrow.ail.json) + two new e2e tests (int_arg_to_effect_op_does_not_rc_track, heap_str_repeated_print_balances_rc_stats) appended near e2e.rs:2625. Both new tests PASS first-shot with exact predicted RC stats.
  • iter eob.1.6: DESIGN.md §Decision 10 gets a new subsection #### Arg-position policy for compound AST nodes after the ret_mode block. The two arg-position rules (Ctor = Consume, Do = Borrow) are anchored together as language rules, not per-op annotations. One added linking paragraph explains how the Do = Borrow rule cooperates with the ret_mode == Own letbinder-trackability rule above to balance heap-Str RC discipline — within the plan's "Boss judgement on placement" carve-out.
  • iter eob.1.7: WhatsNew.md entry appended at bottom (user-facing wording, no internals). roadmap.md heap-Str ABI entry flipped [ ][x]. Post-22 Prelude depends on: line removed (the only dep was Heap-Str, which has shipped). cargo test workspace
    • cross_lang.py + compile_check.py all green. check.py noisy latency cluster left pristine per audit-cma/audit-ms precedent.

Concerns

(none — all 7 tasks DONE without review re-loops)

Known debt

  • check.py shows recurring noisy latency.implicit_at_rc.max_us / latency.explicit_at_rc.max_us tail-jitter, plus throughput.bench_list_sum.bump_s swinging +11-15% across back-to-back runs (with reciprocal gc_over_bump improvement on the same fixture). Neither family is reachable from this iter's changes (bump_s on bench_list_sum has no effect-ops; the list-sum hot path has no int_to_str / float_to_str calls). The audit-cma and audit-ms journals already document the same noise cluster as "not coupled to milestone, baseline left pristine for two consecutive audits" — same precedent applies; baseline untouched.

Files touched

Code:

  • crates/ailang-check/src/uniqueness.rs (Term::Do Consume → Borrow)
  • crates/ailang-check/src/linearity.rs (Term::Do Consume → Borrow + doc-block split)
  • crates/ailang-check/src/builtins.rs (int_to_str / float_to_str ret_mode → Own)
  • crates/ailang-codegen/src/synth.rs (int_to_str / float_to_str ret_mode → Own)
  • crates/ailang-codegen/src/drop.rs (App-arm Str carve-out)

Tests + fixtures:

  • crates/ail/tests/e2e.rs (2 new positive tests appended)
  • examples/int_to_print_int_borrow.ail.json (new)
  • examples/heap_str_repeated_print_borrow.ail.json (new)

Docs:

  • docs/DESIGN.md (Decision 10: arg-position policy subsection)
  • docs/WhatsNew.md (heap-str-abi milestone close entry)
  • docs/roadmap.md (Heap-Str ABI P1 closed; Post-22 Prelude depends-on line removed)

Stats

bench/orchestrator-stats/2026-05-12-iter-eob.1.json