tidy: rewrite stale 18b/18c.x doc-headers to match shipped state

First half of the post-18-arc tidy-iter (per the new CLAUDE.md
iter-cycle rule). Architect's drift review flagged module-doc
headers describing 18b's leak-everything snapshot or 18c.x's
"deferred" debt that has since shipped. Doc-only changes; cargo
build clean, cargo test --workspace green at e2e=61, no
behavioural change.

- runtime/rc.c top-of-file header: rewrote from "Iter 18b
  deliberately stops at the layout and the alloc... programs
  leak every allocation" (false post-18c.3) to a stage summary
  spanning 18b–18e. Fixed `--memory=rc` reference (renamed to
  `--alloc=rc` in 18b's CLI work). Updated ailang_rc_inc /
  ailang_rc_dec block comments to point at `drop_<m>_<T>` and
  the worklist as the cascade owners, not at "18c will wire
  this up".

- ailang-check uniqueness.rs module-doc: replaced the "deferred
  to later iters" block (which named 18c.4 + 18d as future
  work, both shipped) with a current "what this pass does NOT
  do" block. Cross-fn reasoning is still genuinely deferred;
  per-type drop fns and recursive cascades are NOT this pass's
  job by design (codegen does them, not the inference).

- ailang-codegen emit_drop_fn_for_type doc + in-body comment:
  rewrote "Iter 18e replaces the recursive call with an
  iterative worklist free" to describe the actual shipped
  behaviour — the 18e (drop-iterative) annotation routes
  annotated types through emit_iterative_drop_fn_for_type;
  unannotated types stay recursive by orchestrator design
  (cheaper IR, no worklist alloc).

Held back for the second half of the tidy-iter (pending the
ailang-bencher determinism result):

- DESIGN.md Decision 10 line 700 says modes are "mandatory"
  but lines 940–952 admit they're opt-in with deferred
  mandatoriness. The bench result either supports tightening
  the mandatoriness claim or backs down to "opt-in with
  performance benefit" — orchestrator-level decision blocked
  on the bench data.
- Dynamic-tag partial-drop debt is captured in JOURNAL but
  should be surfaced in DESIGN as a known precision gap.
This commit is contained in:
2026-05-08 13:34:32 +02:00
parent 973e075f43
commit 6b3ff3bbed
3 changed files with 68 additions and 45 deletions
+42 -30
View File
@@ -1,20 +1,29 @@
/* AILang reference-counting runtime — Iter 18b.
/* AILang reference-counting runtime.
*
* This is the allocator + counter primitives for `ail build --memory=rc`.
* It establishes the memory layout (8-byte refcount header preceding
* every allocation) and the three runtime entry points the codegen will
* eventually call: ailang_rc_alloc / ailang_rc_inc / ailang_rc_dec.
* Allocator + counter primitives for `ail build --alloc=rc`, plus the
* worklist allocator for `(drop-iterative)` data types. The runtime
* shape is set by Decision 10 (RC + uniqueness inference) and grew
* across Iter 18b18e:
*
* Iter 18b deliberately stops at the *layout* and the *alloc*. The
* codegen routes `Term::Ctor` / `Term::Lam` env / closure-pair sites
* through `ailang_rc_alloc` instead of `GC_malloc` / `bump_malloc`, but
* does NOT yet emit `inc` or `dec` calls anywhere. Programs running
* under `--memory=rc` therefore leak every allocation — the same
* behaviour as the pre-Boehm era. This is intentional: the next iter
* (18c) ships uniqueness inference and the codegen pass that emits
* inc/dec. 18b is purely about plumbing the allocator and validating
* that compiled programs still produce correct output under the new
* allocator.
* - 18b shipped the 8-byte-refcount-header layout and
* `ailang_rc_alloc`. Programs leaked everything because codegen
* did not yet emit inc/dec.
* - 18c.3 added `ailang_rc_inc` / `ailang_rc_dec` emission at
* `Term::Clone` and at `Term::Let` scope close (when the binder is
* a unique RC-allocated value).
* - 18c.4 added per-type `drop_<m>_<T>(ptr)` functions emitted by
* codegen; on dec-to-zero the cascade walks pointer-typed fields
* before freeing the outer cell.
* - 18d.118d.4 added explicit `(reuse-as)` rewrites and move-aware
* pattern-binder + Own-param dec at scope close, all built on top
* of these runtime primitives without changing the ABI.
* - 18e added `ailang_drop_worklist_*` for ADTs annotated
* `(drop-iterative)`. Codegen swaps the recursive cascade for a
* worklist loop; deep ADT chains free without stack growth.
*
* The ABI defined here is stable; adding behaviour to the codegen
* (further uniqueness elision, atomic refcounts under threading, etc.)
* will not require runtime changes unless the layout itself shifts.
*
* Layout:
*
@@ -80,28 +89,31 @@ void ailang_rc_inc(void *payload) {
if (payload == NULL) {
return;
}
/* Heuristic for "static, do not touch": the static closure-pair env
* pointers (Iter 8b) live in the LLVM data segment, not in heap
* memory we allocated. We cannot trivially distinguish them at
* runtime without a flag bit; for Iter 18b, we accept that inc on
* static memory is undefined behaviour. Iter 18c's codegen will
* elide inc/dec for known-static pointers, so this path will not
* be reached for them in practice. */
/* Static closure-pair env pointers (Iter 8b) live in the LLVM data
* segment, not in heap memory we allocated. Codegen elides inc/dec
* for known-static pointers (the `@`-prefix gate added in 18c.3),
* so this path is not reached for them in practice. The runtime
* itself has no header-bit flag distinguishing static from heap;
* if codegen ever loses the elision, inc on a static pointer is
* undefined behaviour. */
ailang_rc_header_t *hdr = header_of(payload);
*hdr += 1;
}
/* Refcount -= 1. If it reaches zero, frees the underlying block.
*
* Iter 18b deliberately does NOT recursively dec child references.
* That requires per-type traversal info (which fields are pointer-
* typed, which are unboxed), which is added in Iter 18c when the
* codegen learns to emit per-ctor `dec` cascades. For now, free-on-
* zero just frees the box; any boxed children leak.
* This function performs only the *outer* free. The per-type
* `drop_<m>_<T>(ptr)` functions emitted by codegen (Iter 18c.4) are
* what walk pointer-typed children before calling `ailang_rc_dec` on
* the outer cell. For ADTs annotated `(drop-iterative)`, codegen
* emits a worklist loop using `ailang_drop_worklist_*` (Iter 18e)
* instead of recursive cascade, allowing arbitrarily deep ADT chains
* to free without stack growth.
*
* Iter 18b never emits `dec` calls from codegen, so this fn is
* effectively dead code in 18b. It exists so the runtime ABI is
* complete and 18c can wire codegen up against a stable surface. */
* Calling `ailang_rc_dec` directly on a pointer whose type has boxed
* children will leak those children. Codegen routes through
* `drop_<m>_<T>` when it knows the type; this entry point is the
* shared bottom they all converge on. */
void ailang_rc_dec(void *payload) {
if (payload == NULL) {
return;