From b057789b55a86cd664e2d7fee2c998bb855cad98 Mon Sep 17 00:00:00 2001 From: Brummel Date: Fri, 15 May 2026 01:44:13 +0200 Subject: [PATCH] =?UTF-8?q?plan:=20mut.3=20=E2=80=94=20codegen=20+=20e2e?= =?UTF-8?q?=20for=20Term::Mut=20/=20Term::Assign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine tasks: Emitter struct extension with mut_var_allocas + pending_entry_allocas + entry_block_end_marker fields (Task 1); start_block captures the entry-block byte marker (Task 2); Term::Var resolution prepends mut-var lookup with load emission (Task 3); Term::Mut codegen — alloca to side buffer, init at current position, store to alloca, push binding (Task 4); Term::Assign codegen — load alloca, lower value, store (Task 5); final flush of pending_entry_allocas into self.body at the marker (Task 6); examples/mut_counter.ail (Int) + examples/mut_sum_floats.ail (Float) (Task 7); e2e tests in crates/ail/tests/e2e.rs (Task 8); DESIGN.md bullet under 'What is supported' (Task 9). Five Boss decisions encoded: entry-block hoist via String::insert_str at the captured marker (chosen over post-pass or non-standard emit-at-current); no LLVM helper wrapper — direct push_str matches existing codebase convention; e2e tests append to the existing crates/ail/tests/e2e.rs (reuse build_and_run helper); DESIGN.md bullet lives in 'What is supported' subsection (recon misread — the section exists at line 2680); Unit-codegen via the existing Term::Lit { lit: Unit } canonical form. --- docs/plans/2026-05-15-iter-mut.3.md | 716 ++++++++++++++++++++++++++++ 1 file changed, 716 insertions(+) create mode 100644 docs/plans/2026-05-15-iter-mut.3.md diff --git a/docs/plans/2026-05-15-iter-mut.3.md b/docs/plans/2026-05-15-iter-mut.3.md new file mode 100644 index 0000000..b740ceb --- /dev/null +++ b/docs/plans/2026-05-15-iter-mut.3.md @@ -0,0 +1,716 @@ +# mut.3 — codegen + e2e — Implementation Plan + +> **Parent spec:** `docs/specs/2026-05-15-mut-local.md` +> +> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement` +> to run this plan. Steps use `- [ ]` checkboxes for tracking. + +**Goal:** Replace the iter mut.1 dispatch stubs at +`crates/ailang-codegen/src/lib.rs:1683-1691` with real LLVM lowering: +mut-vars become `alloca` slots hoisted to the fn's entry block, +`assign` lowers to `store`, mut-var reads lower to `load`. Extend the +codegen-side `Term::Var` resolution ladder to consult mut-var allocas +before `self.locals`. Ship two end-to-end fixtures +(`examples/mut_counter.ail` + `examples/mut_sum_floats.ail`) and +two e2e test cases asserting expected stdout. Append a bullet to +DESIGN.md §"What is supported". + +**Architecture:** Mut-var allocas land in the fn's entry block via +a side buffer `pending_entry_allocas: String` accumulated during +body lowering and flushed once into `self.body` at a tracked byte +position immediately after `start_block("entry")` emits the `entry:` +label. This is a deliberate departure from the existing alloca +sites (`match_lower.rs:114`, `lambda.rs:240/266`) which emit allocas +at the current body position — those allocas are one-shot per fn +invocation and don't suffer from being non-entry-block. Mut-var +allocas in deeply-nested blocks (e.g. inside `if`/`match`/`seq`) +must remain accessible across the whole fn body, so mem2reg +eligibility (entry-block placement) matters. Mut-var lookup at +codegen uses a per-fn `mut_var_allocas: BTreeMap` mapping name → (alloca SSA name, AIL type). Push entries +on `Term::Mut`-entry, remove on exit; lexical scoping mirrors the +mut.2 typecheck-side stack. + +**Tech Stack:** `ailang-codegen` only. No `ailang-core`, +`ailang-surface`, `ailang-check`, `ailang-prose`, or `ail` source +changes (one new test fn in `crates/ail/tests/e2e.rs`). Two new +example fixtures + one DESIGN.md amendment. + +--- + +**Files this plan creates or modifies:** + +- Create: `examples/mut_counter.ail` — sum 1..10 via mut, prints 55. +- Create: `examples/mut_sum_floats.ail` — Float twin (sum 1.0..10.0), + prints the Float total. +- Modify: `crates/ailang-codegen/src/lib.rs:716-717` — add + `mut_var_allocas: BTreeMap` and + `pending_entry_allocas: String` and `entry_block_end_marker: Option` + fields to `Emitter`. +- Modify: `crates/ailang-codegen/src/lib.rs:820-842` — initialise the + three new fields in `Emitter::new`. +- Modify: `crates/ailang-codegen/src/lib.rs:1008-1034` — reset the + three new fields per fn-body in `emit_fn`, and capture the + entry-block byte marker after `start_block("entry")`. +- Modify: `crates/ailang-codegen/src/lib.rs:837-842` — extend + `start_block` (or its caller) to record `entry_block_end_marker` + on entry-block emission. +- Modify: `crates/ailang-codegen/src/lib.rs:1292-1357` (`Term::Var` + arm) — prepend a mut-var lookup that emits a `load` and returns + the SSA name. +- Modify: `crates/ailang-codegen/src/lib.rs:1683-1691` — replace + the `Term::Mut`/`Term::Assign` `Internal` stubs with real lowering. +- Modify: `crates/ailang-codegen/src/lib.rs` (end of `emit_fn`) — + flush `pending_entry_allocas` into `self.body` at the marker. +- Modify: `crates/ail/tests/e2e.rs` — add `mut_counter_prints_55` + and `mut_sum_floats_prints_55_dot_0` test fns. +- Modify: `docs/DESIGN.md` — append a bullet to the "What **is** + supported" subsection around line 2680. + +--- + +## Task 1 — `Emitter` field additions + per-fn reset + +**Files:** +- Modify: `crates/ailang-codegen/src/lib.rs:716-717` (struct fields) +- Modify: `crates/ailang-codegen/src/lib.rs:820-842` (`new`) +- Modify: `crates/ailang-codegen/src/lib.rs:1008-1034` (`emit_fn` reset) + +- [ ] **Step 1: Add three fields to `Emitter`** + +Locate the `Emitter` struct around `crates/ailang-codegen/src/lib.rs:716`. +After the existing field `current_param_modes: BTreeMap` +(line 717), insert: + +```rust + /// Iter mut.3: per-fn map of mut-var name → (alloca SSA name, + /// AIL element type). Populated when entering a `Term::Mut` + /// block; entries removed when leaving the block. Consulted by + /// the `Term::Var` arm of `lower_term` before `self.locals`, + /// matching the typecheck-side mut-scope-stack precedence. + mut_var_allocas: BTreeMap, + + /// Iter mut.3: side buffer for `alloca` instructions emitted + /// during body lowering but hoisted to the fn's entry block. + /// Flushed once into `self.body` at `entry_block_end_marker` + /// after `lower_term` completes. + pending_entry_allocas: String, + + /// Iter mut.3: byte position in `self.body` immediately after + /// the `entry:` label, captured during `start_block("entry")` + /// at fn-body emission. Used to splice `pending_entry_allocas` + /// into the entry block once body lowering completes. + entry_block_end_marker: Option, +``` + +- [ ] **Step 2: Initialise in `Emitter::new`** + +Locate `Emitter::new` around `crates/ailang-codegen/src/lib.rs:820`. +Near the existing `closure_drops: BTreeMap::new(),` line (around 831), +add: + +```rust + mut_var_allocas: BTreeMap::new(), + pending_entry_allocas: String::new(), + entry_block_end_marker: None, +``` + +- [ ] **Step 3: Reset in `emit_fn`** + +Locate the per-fn reset block in `emit_fn` (around +`crates/ailang-codegen/src/lib.rs:1008-1034` — where +`self.locals.clear()`, `self.counter = 0`, `self.moved_slots.clear()`, +`self.current_param_modes.clear()` already live). Append: + +```rust + self.mut_var_allocas.clear(); + self.pending_entry_allocas.clear(); + self.entry_block_end_marker = None; +``` + +- [ ] **Step 4: Verify build green** + +Run: `cargo build --workspace 2>&1 | tail -10` +Expected: green (the three new fields are added but not yet +consumed; the only consumers are added in Tasks 2-3). + +--- + +## Task 2 — `start_block` records the entry-block marker + +**Files:** +- Modify: `crates/ailang-codegen/src/lib.rs:837-842` (`start_block`) + +`start_block` is the canonical helper that emits `