From 7b65c121ed3b74c1ea2dff4da144346f2e2d40b9 Mon Sep 17 00:00:00 2001 From: Brummel Date: Fri, 8 May 2026 15:55:22 +0200 Subject: [PATCH] journal: 2026-05-08 codegen split tidy Records the four-phase reorganisation that moved 2470 lines out of lib.rs into purpose-named sibling submodules. Captures the visibility model (descendant-module privacy + selective pub(crate)), the deliberate non-extractions (lower_term, the app/call cluster), and the framing as a navigability tidy rather than a feature-driven change. --- docs/JOURNAL.md | 100 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index cb7c19b..66a2b68 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -8351,3 +8351,103 @@ post-18g-tidy follow-ups: all closed. Decision 9 has been re-framed (commit `f10a77e`) to match the orchestrator's dual-allocator stance. Next iter is queue-driven; nothing load-bearing is open. + +## 2026-05-08 — codegen split: lib.rs from 5295 → 2825 lines + +User asked mid-session "wie groß ist die codebase mittlerweile? +Kannst du sie noch kontrollieren?" The honest answer was that +`crates/ailang-codegen/src/lib.rs` was the one file flagged for +navigability — 5295 lines monolithic, every codegen concern +sharing a single namespace. Decision delegated by the user +("Das musst du selbst entscheiden"). With the 18g family closed +and tests dense, the window for a mechanical move-only refactor +was as good as it gets. + +Executed as four sequential commits, full +`cargo test --workspace` between each, no behaviour change. +Each phase moved a coherent cluster into a sibling submodule +under `crates/ailang-codegen/src/`: + + - **Phase 1** (`84ba83d`): `synth.rs` (216 lines) + + `subst.rs` (319 lines). The free-function tail of lib.rs: + LLVM IR shaping helpers and the monomorphisation + substitution pipeline. + - **Phase 2** (`86989a7`): `drop.rs` (696 lines). All RC- + allocator drop work — per-type drop fns, per-let-close + drop dispatch, closure-pair drops. + - **Phase 3** (`86406ac`): `match_lower.rs` (935 lines). + `Term::Ctor`, `Term::ReuseAs`, `Term::Match` lowering. The + `lower_match` body (≈500 lines) is the single largest + method left in the project; moving it gave the biggest + navigability win. + - **Phase 4** (`bea5c92`): `lambda.rs` (447 lines). + `Term::Lam` + closure machinery (capture analysis, env + construction, deferred per-pair drop fns). + +Final layout (lib.rs is now the Emitter setup + the +`lower_term` mass dispatcher + the lookup helpers + tests): + +``` +lib.rs 2825 Emitter struct, lifecycle, lower_term + dispatcher, app/eq/effect lowering, + lookup helpers, synth_arg_type, + monomorphisation entry, tests. +match_lower.rs 935 Term::Ctor / ReuseAs / Match. +escape.rs 722 escape analysis (predates split). +drop.rs 696 per-type + per-let-close drops. +lambda.rs 447 Term::Lam + closure machinery. +subst.rs 319 substitution + unification. +synth.rs 216 IR shaping helpers + builtin types. +``` + +### Visibility model + +Rust's "private = visible to module + descendants" rule +made the split cheap. The `Emitter` struct and its private +fields stay private in `lib.rs`; submodules (drop, lambda, +match_lower) can read those fields directly through normal +descendant-module privacy. Only methods that lib.rs calls +back into the submodule needed `pub(crate)` (the entry-point +methods); helpers used only inside a submodule stayed +private. Mirroring this, a small set of lib.rs-side helpers +that submodules call back into were upgraded to +`pub(crate)`: `start_block`, `lower_term`, `fresh_ssa`, +`fresh_id`, `synth_arg_type`, `collect_owner_local_types`, +`lookup_ctor_by_type`, `lookup_ctor_in_pattern`. No fields +needed `pub(crate)`. + +### What was deliberately not done + +- **No behaviour change.** Not a single character of + generated IR is different. The split is purely a + reorganisation. (Verified by 66/66 e2e tests passing + byte-identically across all four commits.) +- **No method signatures changed.** Visibility upgrades only + (`fn` → `pub(crate) fn`) on helpers crossed by the new + module boundary. No parameter or return-type changes. +- **No further extraction.** `lower_term` is 395 lines of + pure dispatch into other methods; splitting its arms into + per-shape files would force every shape's dispatcher to + re-enter through a `pub(crate)` boundary and would split + the term-shape ↔ lowered-shape correspondence across + files. Kept whole. Same call for the + `lower_app`/`lower_polymorphic_call`/`emit_call`/ + `emit_indirect_call` cluster — they are too tightly + coupled to the `Emitter`'s dispatch state to extract + cleanly. + +### What this enables + +The biggest concrete win is for `ailang-architect` and +debugging conversations: a "what does the drop emission do?" +question now reads one 696-line file instead of grepping +through five thousand lines. Same for the closure machinery, +the match lowering, and the substitution pipeline. Future +iters that touch these subsystems get a smaller working +context. + +The split is not motivated by a coming feature, and no +queued iter required it. It is a tidy paid for navigability +alone — the kind of work that compounds across future +sessions without showing up in any single one's bench +numbers.