tidy: 18g sub-arc — DESIGN ratification, latency bench, negative coverage

Resolves the architect's drift report on the 18g sub-arc:

- DESIGN.md: ratify mode-metadata's codegen role. param_modes /
  ret_mode were already on Type::Fn since 18a; with 18d.4 / 18g
  they became load-bearing for drop-emission decisions in
  codegen. The new 'Mode metadata is load-bearing for codegen'
  subsection records the four seams (Iter A + Iter B + 18g.1 +
  18g.2) and names the let-alias-of-borrow carve-out the gates
  do not propagate through.

- bench/run.sh: post-throughput, invoke bench/latency_harness.py
  on the three canonical arms (Implicit @ gc, explicit @ rc,
  Implicit @ rc control). The Boehm-retirement bench numbers
  are now reproducible by anyone running the harness, not just
  by hand on a specific host.

- Negative coverage: examples/rc_let_implicit_returning_app
  + alloc_rc_let_binder_for_implicit_returning_app_does_not_drop
  pin the asymmetry to the (own)-ret-mode test (live=0 there,
  live=1 here). The Borrow-ret-mode case is covered by the
  language design itself — typechecker rejects 'borrow-
  passthrough' shapes with consume-while-borrowed.

JOURNAL entry records four items as deferred known debt:
emit_inlined_partial_drop dynamic-tag fallback, carve-out
diagnostics surface, bench-number stat-of-N, and the
cross-family ordering observation about CLAUDE.md's tidy-iter
rule.

The 18-arc is formally closed with this tidy. Next iter is
the orchestrator's Boehm-retirement decision (Path A vs Path B
from JOURNAL 2026-05-08 18f entry, joined by the post-18g.2
re-bench numbers).
This commit is contained in:
2026-05-08 14:37:36 +02:00
parent 97e793dd62
commit 3113258680
6 changed files with 345 additions and 0 deletions
@@ -0,0 +1 @@
{"defs":[{"ctors":[{"fields":[{"k":"con","name":"Int"}],"name":"MkT"}],"kind":"type","name":"T"},{"body":{"args":[{"name":"n","t":"var"}],"ctor":"MkT","t":"ctor","type":"T"},"kind":"fn","name":"alloc","params":["n"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"k":"con","name":"T"}}},{"body":{"arms":[{"body":{"name":"v","t":"var"},"pat":{"ctor":"MkT","fields":[{"name":"v","p":"var"}],"p":"ctor"}}],"scrutinee":{"name":"x","t":"var"},"t":"match"},"kind":"fn","name":"unbox","params":["x"],"type":{"effects":[],"k":"fn","param_modes":["borrow"],"params":[{"k":"con","name":"T"}],"ret":{"k":"con","name":"Int"}}},{"body":{"body":{"args":[{"args":[{"name":"x","t":"var"}],"fn":{"name":"unbox","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"name":"x","t":"let","value":{"args":[{"lit":{"kind":"int","value":7},"t":"lit"}],"fn":{"name":"alloc","t":"var"},"t":"app"}},"kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[],"name":"rc_let_implicit_returning_app","schema":"ailang/v0"}
@@ -0,0 +1,63 @@
; Iter 18g tidy negative-coverage fixture: a let-binder whose
; value is the result of an `Implicit`-ret-mode fn call must NOT
; be trackable for scope-close drop. Implicit is the back-compat
; lane (default for unannotated fns); 18c.3 documented "Implicit-
; mode params do not get any dec — they leak rather than mis-dec",
; and 18g.2's symmetric carve-out is "Implicit-returning calls do
; not flow ownership to the caller, the caller must NOT dec".
;
; The asymmetry to `(own T)`-returning calls is real and
; intentional: an Own-returning fn signs a static contract that
; the caller now owns the cell and must dec it; an Implicit-
; returning fn does not. If `is_rc_heap_allocated` were
; mistakenly to fire on this shape, the let-scope close would
; emit a dec for a cell whose ownership is not (statically) the
; caller's. The cell may be a fresh allocation the callee
; happens to return without explicit annotation, OR a static
; constant in BSS, OR a returned-borrow indistinguishable from
; an own at the callee's signature. Dec'ing it would be
; refcount underflow in the first two cases and use-after-free
; in the third.
;
; Properties guarded:
; (1) Build succeeds (no consume-while-borrowed false positive).
; (2) Binary exits cleanly with stdout `7` (no crash, no
; refcount underflow).
; (3) `live = 1` at program exit — the one MkT cell leaks. The
; leak is intentional under the Implicit back-compat lane;
; it documents the asymmetry from the (own)-ret case (which
; would have `live = 0`).
(module rc_let_implicit_returning_app
(data T
(ctor MkT (con Int)))
; No ret-mode annotation -> default Implicit. Takes one
; ignored Int param (form-A doesn't allow zero-arg fn-app).
(fn alloc
(type
(fn-type
(params (con Int))
(ret (con T))))
(params n)
(body
(term-ctor T MkT n)))
(fn unbox
(type
(fn-type
(params (borrow (con T)))
(ret (con Int))))
(params x)
(body
(match x
(case (pat-ctor MkT v) v))))
(fn main
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(let x (app alloc 7)
(do io/print_int (app unbox x)))))
)