JOURNAL: Iter 18c.1 — Term::Clone schema
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.
This commit is contained in:
+115
@@ -5983,3 +5983,118 @@ schema), 18c.2 (linearity check + suggested_rewrites), 18c.3
|
|||||||
(inference + codegen) sequentially. Don't attempt all three in
|
(inference + codegen) sequentially. Don't attempt all three in
|
||||||
one pass — each builds on the previous and wants its own
|
one pass — each builds on the previous and wants its own
|
||||||
verification cycle.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user