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:
@@ -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)))))
|
||||
)
|
||||
Reference in New Issue
Block a user