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:
+42
-30
@@ -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 18b–18e:
|
||||
*
|
||||
* 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.1–18d.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;
|
||||
|
||||
Reference in New Issue
Block a user