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:
@@ -2161,3 +2161,43 @@ fn alloc_rc_let_binder_for_owned_returning_app_drops_at_scope_close() {
|
||||
call's return type"
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 18g tidy negative-coverage: a let-binder whose value is the
|
||||
/// result of an `Implicit`-ret-mode (default, unannotated) call must
|
||||
/// NOT be trackable. Implicit is the back-compat lane that 18c.3
|
||||
/// documented as "params don't get any dec — leak rather than mis-
|
||||
/// dec"; the symmetric carve-out for ret-modes is "Implicit-returning
|
||||
/// calls do not flow ownership to the caller, the caller must NOT
|
||||
/// dec".
|
||||
///
|
||||
/// If `is_rc_heap_allocated` were mistakenly to fire on this shape,
|
||||
/// the let-scope close would emit an unjustified dec — refcount
|
||||
/// underflow at runtime (the cell's RC reaches zero before any other
|
||||
/// owner had a chance to dec). The fixture under `--alloc=rc` must:
|
||||
///
|
||||
/// (1) Build cleanly (no consume-while-borrowed false positive,
|
||||
/// parse error, or unimplemented).
|
||||
/// (2) Exit cleanly with stdout `7` — the unboxed Int from the cell.
|
||||
/// (3) Report `live = 1` — the one MkT cell leaks. The leak is the
|
||||
/// intentional Implicit-mode behaviour; if codegen ever started
|
||||
/// dec'ing this shape it would crash with refcount underflow
|
||||
/// before the leak number could even be observed.
|
||||
///
|
||||
/// This test is the negative-side companion to
|
||||
/// `alloc_rc_let_binder_for_owned_returning_app_drops_at_scope_close`:
|
||||
/// the asymmetry between (own)-ret-mode (live=0) and Implicit-ret-mode
|
||||
/// (live=1) is the documented contract.
|
||||
#[test]
|
||||
fn alloc_rc_let_binder_for_implicit_returning_app_does_not_drop() {
|
||||
let (stdout, allocs, frees, live) =
|
||||
build_and_run_with_rc_stats("rc_let_implicit_returning_app.ail.json");
|
||||
assert_eq!(stdout.trim(), "7", "alloc(7) -> unbox should print 7");
|
||||
assert_eq!(
|
||||
live, 1,
|
||||
"Implicit-ret-mode App must leak (not crash with refcount \
|
||||
underflow); allocs={allocs} frees={frees} live={live}. \
|
||||
A live count of 0 means is_rc_heap_allocated mistakenly \
|
||||
marked Implicit-ret-mode App as trackable; a non-zero exit \
|
||||
means the unjustified dec triggered the underflow guard."
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user