From 3d22dc13bf555072bd80c8a2657fa9d8e93fdf86 Mon Sep 17 00:00:00 2001 From: Brummel Date: Fri, 8 May 2026 02:27:08 +0200 Subject: [PATCH] =?UTF-8?q?JOURNAL:=20Iter=2018c.1=20=E2=80=94=20Term::Clo?= =?UTF-8?q?ne=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records that 18c.1 ran zero-surprise (mechanical recursion at ~10 sites across 6 files), the future-work seam in codegen for 18c.3 is a single line (emit one rc_inc call before returning the inner SSA reg), and 18c.2's linearity check should start as 'green for borrow_own_demo' since that fixture's shape is exactly what the check needs to accept. --- docs/JOURNAL.md | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index bf112d7..cb99fa8 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -5983,3 +5983,118 @@ schema), 18c.2 (linearity check + suggested_rewrites), 18c.3 (inference + codegen) sequentially. Don't attempt all three in one pass — each builds on the previous and wants its own verification cycle. + +## Iter 18c.1 — `Term::Clone` schema (no inc emission yet) + +Schema floor for explicit RC inc. Adds `Term::Clone { value }` +with serde tag `"clone"`, form-A `(clone X)`. Pure additive +schema — typechecker treats it as identity, codegen lowers it +exactly like its inner term. The runtime semantics of `(clone X)` +in 18c.1 are: nothing happens; it's an identity wrapper. + +The variant is the author-visible alternative to implicit +sharing under the LLM-aware RC design (Decision 10's +mechanism (2)). When 18c.2 ships linearity enforcement, an LLM +that wants two `own`-mode uses of the same binder will be +required to spell one of them as `(clone X)`. When 18c.3 ships +codegen, the `Term::Clone` arm in `lower_term` will emit a +single `call void @ailang_rc_inc(ptr %v)` before delegating +to the inner term's lowering. + +The 18c.3 emission seam is documented in the codegen arm with a +comment so the future-work site is greppable. + +### Sites touched + +The match-arm count was higher than the brief estimated +(~10 across 6 files) but every site was pure structural +recursion through `value`. Pretty.rs needed nothing — it has no +`match t {}` over Term. The non-obvious sites: + +- `crates/ailang-check/src/lib.rs::verify_tail_positions` — + records that `(clone tail-call)` is itself a tail call. Under + 18c.1's identity-of-clone semantics, the inner term keeps its + tail-position. (Under 18c.3, the inserted `inc` happens before + the call returns, so the tail-position story is preserved + there too — `inc` is not a function call from the LLVM-IR + tail-position perspective; it's an inline call after the value + is computed.) + +- `crates/ailang-codegen/src/escape.rs` — three sites in the + escape analysis: `walk`, `escapes`, `collect_free_vars`. The + pattern is the same as everywhere else — recurse through the + inner value. + +- `crates/ail/src/main.rs::walk_term` — the deps walker that + finds cross-module references for the workspace loader. Pass- + through. + +### Fixture + +`examples/clone_demo.{ailx,ail.json}`: + +``` +(module clone_demo + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body + (let x 42 + (do io/print_int (clone x)))))) +``` + +Stdout: `42` under both `--alloc=gc` and `--alloc=rc`. The +fixture is intentionally minimal — the schema/parser/printer/ +round-trip path is exercised end-to-end, but the value being +"cloned" is a primitive `Int` where RC inc is meaningless even +in 18c.3. A richer fixture wrapping a list-bound clone is a +18c.3 concern (where clone actually does something). + +### Build / test + +`cargo build --workspace` clean. `cargo test --workspace` green: +52 E2E (+1), 14 surface parse (+2), all other buckets unchanged. +Hash regression test in `crates/ailang-core/src/hash.rs` passes +unchanged (the new serde tag `"clone"` appears in zero existing +fixtures). `git diff examples/` is empty for pre-existing +fixtures. + +### Did anything surprise + +- **Zero surprises.** The 18c.1 iter ran exactly like 18a in + shape: new variant + ~10 mechanical recursion sites + parser + + printer + fixture + test. Both iters lean on the same + underlying structure (an additive AST extension that + typechecker / codegen / desugar passes traverse without + semantic effect). When 18c.2 / 18c.3 land they will be + fundamentally different — those are real analysis passes + with semantic consequences. The schema-floor iter pattern is + rich because the project has good structural discipline + (every Term-traversal site uses pattern matching, not + reflection), and that's worth recording. + +- **The future-work seam is one line.** 18c.3 only needs to + flip the codegen `Term::Clone` arm from "lower inner, return + the SSA reg" to "lower inner, emit one `inc` call, return the + SSA reg". That's the entire emission cost of explicit clone. + The hard work in 18c.3 is everywhere else (uniqueness + inference + dec instrumentation across all binder-going-out- + of-scope sites); explicit clone is comparatively cheap. + +### Next + +Iter 18c.2: linearity check. Walk every fn body whose params +are all explicit-mode (`Borrow` or `Own`, no `Implicit`); track +each binder's consumption state through the AST; emit +structured `use-after-consume` / `consume-while-borrowed` +diagnostics with form-A `suggested_rewrites`. Functions with any +`Implicit` param stay exempt — that's the back-compat lane +while existing fixtures stay unannotated. + +The check is purely an addition to the diagnostic pipeline; it +emits no IR change. Existing fixtures (all `Implicit`) are +untouched. The new `borrow_own_demo` fixture (which has all- +explicit modes) becomes the first program subject to the check; +its current shape (borrow-then-own on `xs`) is exactly what the +check should accept, so 18c.2 starts as a "green for the +existing test" iter.