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.
This commit is contained in:
2026-05-08 15:55:22 +02:00
parent bea5c92591
commit 7b65c121ed
+100
View File
@@ -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.