# raw-buf.1 — Intercept registry refactor — Implementation Plan > **Parent spec:** `docs/specs/0054-raw-buf.md` > > **For agentic workers:** REQUIRED SUB-SKILL: use the > `implement` skill to run this plan. Steps use `- [ ]` > checkboxes for tracking. **Goal:** Lift the hardcoded `try_emit_primitive_instance_body` match in `crates/ailang-codegen/src/lib.rs` (18 arms) into a single registry table consulted at the existing dispatch site. Zero behavioural change. **Architecture:** New file `crates/ailang-codegen/src/intercepts.rs` carries an `Intercept` struct (`name`, `expected_params`, `expected_ret`, `wants_alwaysinline`, `emit: fn(&mut Emitter) -> Result<()>`), an `INTERCEPTS: &[Intercept]` static table, and a `lookup(name) -> Option<&'static Intercept>`. Each arm body becomes a free fn `emit_(emitter)`. The wrapper `try_emit_primitive_instance_body` survives as a thin shim that does `match intercepts::lookup(fn_name) { Some(i) => { check_sig(i, ...)?; (i.emit)(self)?; Ok(true) } None => Ok(false) }` — so the 14 in-source comment-refs to the wrapper stay valid. The `intercept_emit_wants_alwaysinline` predicate is rewritten to consult the registry, closing the silent-drift class that recon flagged. **Tech Stack:** Rust 1.x (stable), `crates/ailang-codegen/` (lib + intercepts submodule), workspace integration tests in `crates/ail/tests/`. --- **Files this plan creates or modifies:** - Create: `crates/ailang-codegen/src/intercepts.rs` — the registry table + entry types + lookup + free emit fns + `check_sig` helper + the `registry_contains_all_legacy_arms` pin test. - Modify: `crates/ailang-codegen/src/lib.rs:43-48` — add `mod intercepts;`. - Modify: `crates/ailang-codegen/src/lib.rs` — bump visibility of fields `body`, `locals`, `block_terminated` (currently private) to `pub(crate)` so sibling-module free fns can read/write them. Bump visibility of helper methods `emit_compare_ladder`, `emit_ordering_arm`, `emit_direct_int_icmp_intercept`, `lower_ctor` (currently private; `start_block`, `fresh_ssa`, `fresh_id` are already `pub(crate)`) to `pub(crate)`. - Modify: `crates/ailang-codegen/src/lib.rs:2841-3147` — replace the 300-line match body of `try_emit_primitive_instance_body` with a 6-line shim that consults `intercepts::lookup`. - Modify: `crates/ailang-codegen/src/lib.rs:1172` (the `intercept_emit_wants_alwaysinline` predicate body) — rewrite as `intercepts::lookup(symbol).map_or(false, |i| i.wants_alwaysinline)`. - Test: `crates/ailang-codegen/src/intercepts.rs::registry_contains_all_legacy_arms` — enumerates all 18 legacy arm names + asserts each resolves to a registered entry. Ratifies the migration as complete (a half-finished migration where any name is missing becomes a hard test fail). - Test (regression, not edited): the four E2E build-and-run tests named in spec § Testing strategy raw-buf.1 — `eq_primitives_smoke_compiles_and_runs`, `compare_primitives_smoke_prints_1_2_3_thrice`, `float_compare_smoke_prints_true_true_false`, `eq_ord_polymorphic_runs_end_to_end`. Plus the alwaysinline-presence pin tests `crates/ailang-codegen/tests/eq_primitives_pin.rs` and `crates/ail/tests/prelude_eq_alwaysinline_ir_pin.rs`. Plus the full workspace. --- ### Task 1: intercepts module skeleton — types, empty table, lookup, sig_check **Files:** - Create: `crates/ailang-codegen/src/intercepts.rs` - Modify: `crates/ailang-codegen/src/lib.rs:43-48` (mod declaration list) - [ ] **Step 1: Create the new file `crates/ailang-codegen/src/intercepts.rs` with the registry types, empty table, lookup, and sig-check helper.** ```rust //! Codegen intercept registry — one dispatch table for every //! mono-symbol whose body the codegen supplies as LLVM IR //! directly (replacing the .ail placeholder body that the //! intercept's home module ships for round-trip stability). //! //! Each entry carries: //! - `name` — the mono symbol the dispatch keys on //! - `expected_params` — LLVM-IR param types as text //! - `expected_ret` — LLVM-IR return type as text //! - `wants_alwaysinline` — whether the dispatch site should //! attach the `alwaysinline` attribute //! to the emitted fn (was a separate //! hardcoded name-list in the predicate //! `intercept_emit_wants_alwaysinline` //! before raw-buf.1) //! - `emit` — the per-arm IR-emission body, run //! against `&mut Emitter` after the //! sig check //! //! Adding a new intercept = adding one row to `INTERCEPTS`. The //! dispatch site (`try_emit_primitive_instance_body` in //! `crates/ailang-codegen/src/lib.rs`) and the alwaysinline //! predicate both consult this single table. use crate::{CodegenError, Emitter, Result}; pub(crate) struct Intercept { pub name: &'static str, pub expected_params: &'static [&'static str], pub expected_ret: &'static str, pub wants_alwaysinline: bool, pub emit: fn(&mut Emitter<'_>) -> Result<()>, } pub(crate) static INTERCEPTS: &[Intercept] = &[]; pub(crate) fn lookup(name: &str) -> Option<&'static Intercept> { INTERCEPTS.iter().find(|i| i.name == name) } pub(crate) fn check_sig( intercept: &Intercept, param_tys: &[String], ret_ty: &str, ) -> Result<()> { let params_match = param_tys.len() == intercept.expected_params.len() && param_tys .iter() .zip(intercept.expected_params.iter()) .all(|(have, want)| have == want); if !params_match || ret_ty != intercept.expected_ret { return Err(CodegenError::Internal(format!( "{} body intercept: unexpected signature \ ({:?}) -> {} (want {:?} -> {})", intercept.name, param_tys, ret_ty, intercept.expected_params, intercept.expected_ret, ))); } Ok(()) } ``` - [ ] **Step 2: Add `mod intercepts;` to the mod-declaration block in `crates/ailang-codegen/src/lib.rs` (currently L43-48, alphabetically sorted: `drop`, `escape`, `lambda`, `match_lower`, `subst`, `synth`). Insert `intercepts` between `escape` and `lambda` to preserve alphabetical order.** Edit at `crates/ailang-codegen/src/lib.rs:43-48`. Insert the line `mod intercepts;` between the existing `mod escape;` and `mod lambda;` lines. - [ ] **Step 3: Build the workspace — empty registry compiles clean.** Run: `cargo build --workspace 2>&1 | tail -5` Expected: build succeeds, zero warnings about `intercepts` being unused (the `pub(crate)` visibility and the `lookup` fn keep it consumed by neither call site yet — Rust does NOT warn on `pub(crate)` items in a sibling module, only on truly unused items). If a `dead_code` warning fires for `INTERCEPTS` or `Intercept` because the table is empty, that's expected and will go away in Task 2 when entries are added; do NOT add `#[allow(dead_code)]` — just accept the warning until Task 2. ``` cargo build --workspace 2>&1 | tail -5 ``` Expected output ends with `Finished` and no errors. A few dead-code warnings about `intercepts::Intercept` / `INTERCEPTS` / `lookup` / `check_sig` are acceptable at this point. --- ### Task 2: Populate the registry — 18 entries + per-arm emit fns + visibility bumps **Files:** - Modify: `crates/ailang-codegen/src/intercepts.rs` (add the 18 free emit fns + populate `INTERCEPTS`) - Modify: `crates/ailang-codegen/src/lib.rs` (visibility bumps on fields + helper methods) - [ ] **Step 1: Bump visibility on the three `Emitter` struct fields the intercept bodies read from `&mut self`.** In `crates/ailang-codegen/src/lib.rs`, find the `Emitter` struct definition starting at L706 and bump these three fields to `pub(crate)`: - `body: String` (around L711) → `pub(crate) body: String,` - `locals: Vec<(String, String, String, Type)>` (around L725) → `pub(crate) locals: Vec<(String, String, String, Type)>,` - `block_terminated: bool` (around L765) → `pub(crate) block_terminated: bool,` (If the field types differ slightly from what's listed — e.g. the locals tuple shape — preserve the existing type and just add the `pub(crate)` prefix.) - [ ] **Step 2: Bump visibility on the four `Emitter` helper methods that the intercept emit fns call.** In `crates/ailang-codegen/src/lib.rs`, locate each of these methods inside `impl<'a> Emitter<'a>` (L921 onwards) and prepend `pub(crate)`: - `fn emit_compare_ladder(&mut self, lt: &str, eq: &str) -> Result<()>` at L3220 → `pub(crate) fn emit_compare_ladder(...)` - `fn emit_ordering_arm(&mut self, label: &str, ctor: &str) -> Result<()>` at L3190 → `pub(crate) fn emit_ordering_arm(...)` - `fn emit_direct_int_icmp_intercept(...)` at L3156 → `pub(crate) fn emit_direct_int_icmp_intercept(...)` - `fn lower_ctor(...)` (used by `emit_ordering_arm`; exact line variable) → `pub(crate) fn lower_ctor(...)` `start_block`, `fresh_ssa`, `fresh_id` are already `pub(crate)`; no edit needed. `intercept_emit_wants_alwaysinline` is rewritten in Task 4 and does not need visibility bumping (it stays a method on Emitter consulted from inside the crate). - [ ] **Step 3: Build to confirm visibility-bumps compile clean before adding emit fns.** Run: `cargo build --workspace 2>&1 | tail -5` Expected: build succeeds; no new warnings or errors from the visibility changes. - [ ] **Step 4: Add the two Str-path emit fns (`emit_eq_str`, `emit_compare_str`) to `crates/ailang-codegen/src/intercepts.rs`.** Append the two fns below to `intercepts.rs` after the `check_sig` helper. The bodies are extracted verbatim from `try_emit_primitive_instance_body` at L2848 (`eq__Str` arm) and L2915 (`compare__Str` arm) of `lib.rs`, minus the per-arm sig-check prologue (which `check_sig` now handles) and minus the trailing `Ok(true)` (which the dispatch shim wraps). ```rust pub(crate) fn emit_eq_str(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); let a_bytes = emitter.fresh_ssa(); let b_bytes = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {a_bytes} = getelementptr inbounds i8, ptr {a_ssa}, i64 8\n" )); emitter.body.push_str(&format!( " {b_bytes} = getelementptr inbounds i8, ptr {b_ssa}, i64 8\n" )); let dst = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {dst} = call zeroext i1 @ail_str_eq(ptr {a_bytes}, ptr {b_bytes})\n" )); emitter.body.push_str(&format!(" ret i1 {dst}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } pub(crate) fn emit_compare_str(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); let a_bytes = emitter.fresh_ssa(); let b_bytes = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {a_bytes} = getelementptr inbounds i8, ptr {a_ssa}, i64 8\n" )); emitter.body.push_str(&format!( " {b_bytes} = getelementptr inbounds i8, ptr {b_ssa}, i64 8\n" )); let cmp_res = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {cmp_res} = call i32 @ail_str_compare(ptr {a_bytes}, ptr {b_bytes})\n" )); emitter.emit_compare_ladder( &format!("icmp slt i32 {cmp_res}, 0"), &format!("icmp eq i32 {cmp_res}, 0"), )?; Ok(()) } ``` **Note for the implementer:** verify the verbatim text of the `eq__Str` and `compare__Str` arms in `crates/ailang-codegen/src/lib.rs` before lifting. The exact `format!` template strings, the exact `cmp_res` ladder argument shape, and the `emit_compare_ladder` invocation arguments are the IR-fidelity critical bits. If the bodies in `lib.rs` differ from what is shown above (e.g. an extra SSA fetch was added in a later commit), preserve the lib.rs verbatim body and just structurally separate the sig-check prologue from the actual IR emission. - [ ] **Step 5: Add the five Eq/Compare emit fns (`emit_compare_int`, `emit_compare_bool`, `emit_eq_int`, `emit_eq_bool`, `emit_eq_unit`).** Append the five fns below to `intercepts.rs` after `emit_compare_str`. Bodies are extracted verbatim from `try_emit_primitive_instance_body` at L2883, L2899, L2947, L2966, L2985 of `lib.rs`, minus per-arm sig-checks and trailing `Ok(true)`. ```rust pub(crate) fn emit_compare_int(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); emitter.emit_compare_ladder( &format!("icmp slt i64 {a_ssa}, {b_ssa}"), &format!("icmp eq i64 {a_ssa}, {b_ssa}"), )?; Ok(()) } pub(crate) fn emit_compare_bool(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); emitter.emit_compare_ladder( &format!("icmp ult i1 {a_ssa}, {b_ssa}"), &format!("icmp eq i1 {a_ssa}, {b_ssa}"), )?; Ok(()) } pub(crate) fn emit_eq_int(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); let dst = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {dst} = icmp eq i64 {a_ssa}, {b_ssa}\n" )); emitter.body.push_str(&format!(" ret i1 {dst}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } pub(crate) fn emit_eq_bool(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); let dst = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {dst} = icmp eq i1 {a_ssa}, {b_ssa}\n" )); emitter.body.push_str(&format!(" ret i1 {dst}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } pub(crate) fn emit_eq_unit(emitter: &mut Emitter<'_>) -> Result<()> { emitter.body.push_str(" ret i1 1\n"); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } ``` **Note for the implementer:** the `emit_eq_int` / `emit_eq_bool` body shapes above are the planner's reconstruction from the recon report's "icmp eq … / ret i1" idiom column. Open `crates/ailang-codegen/src/lib.rs` at the `eq__Int` (L2947) and `eq__Bool` (L2966) arms and lift the actual body text verbatim. If the lib.rs body differs in `format!` template wording, IR comment lines, or attribute placement, preserve the lib.rs version exactly — the goal is byte-identical emitted IR after the dispatch site rewires. - [ ] **Step 6: Add the six float-family emit fns (`emit_float_eq`, `emit_float_ne`, `emit_float_lt`, `emit_float_le`, `emit_float_gt`, `emit_float_ge`).** Append the six fns below to `intercepts.rs` after `emit_eq_unit`. Bodies are extracted verbatim from `try_emit_primitive_instance_body` at L3000, L3019, L3040, L3059, L3078, L3097 of `lib.rs`, minus per-arm sig-checks and trailing `Ok(true)`. The fcmp predicates are NaN-aware (`oeq`, `une`, `olt`, `ole`, `ogt`, `oge`) — preserve them exactly as the lib.rs source has them. Note `float_ne` uses `une` (unordered-or-not-equal), not `one` (ordered-not-equal) — this is deliberate NaN-aware semantics per the comment at the lib.rs arm. ```rust pub(crate) fn emit_float_eq(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); let dst = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {dst} = fcmp oeq double {a_ssa}, {b_ssa}\n" )); emitter.body.push_str(&format!(" ret i1 {dst}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } pub(crate) fn emit_float_ne(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); let dst = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {dst} = fcmp une double {a_ssa}, {b_ssa}\n" )); emitter.body.push_str(&format!(" ret i1 {dst}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } pub(crate) fn emit_float_lt(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); let dst = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {dst} = fcmp olt double {a_ssa}, {b_ssa}\n" )); emitter.body.push_str(&format!(" ret i1 {dst}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } pub(crate) fn emit_float_le(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); let dst = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {dst} = fcmp ole double {a_ssa}, {b_ssa}\n" )); emitter.body.push_str(&format!(" ret i1 {dst}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } pub(crate) fn emit_float_gt(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); let dst = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {dst} = fcmp ogt double {a_ssa}, {b_ssa}\n" )); emitter.body.push_str(&format!(" ret i1 {dst}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } pub(crate) fn emit_float_ge(emitter: &mut Emitter<'_>) -> Result<()> { let n = emitter.locals.len(); let a_ssa = emitter.locals[n - 2].1.clone(); let b_ssa = emitter.locals[n - 1].1.clone(); let dst = emitter.fresh_ssa(); emitter.body.push_str(&format!( " {dst} = fcmp oge double {a_ssa}, {b_ssa}\n" )); emitter.body.push_str(&format!(" ret i1 {dst}\n")); emitter.body.push_str("}\n\n"); emitter.block_terminated = true; Ok(()) } ``` - [ ] **Step 7: Add the five Int icmp-delegate emit fns (`emit_lt_int`, `emit_le_int`, `emit_gt_int`, `emit_ge_int`, `emit_ne_int`).** Append the five fns below to `intercepts.rs` after `emit_float_ge`. Bodies are 1-line delegates to the existing `Emitter::emit_direct_int_icmp_intercept` helper (now `pub(crate)` per Step 2). The icmp predicate strings come from lib.rs L3140-3144. ```rust pub(crate) fn emit_lt_int(emitter: &mut Emitter<'_>) -> Result<()> { emitter.emit_direct_int_icmp_intercept("icmp slt i64") } pub(crate) fn emit_le_int(emitter: &mut Emitter<'_>) -> Result<()> { emitter.emit_direct_int_icmp_intercept("icmp sle i64") } pub(crate) fn emit_gt_int(emitter: &mut Emitter<'_>) -> Result<()> { emitter.emit_direct_int_icmp_intercept("icmp sgt i64") } pub(crate) fn emit_ge_int(emitter: &mut Emitter<'_>) -> Result<()> { emitter.emit_direct_int_icmp_intercept("icmp sge i64") } pub(crate) fn emit_ne_int(emitter: &mut Emitter<'_>) -> Result<()> { emitter.emit_direct_int_icmp_intercept("icmp ne i64") } ``` **Note for the implementer:** the recon report says these five arms (lib.rs L3140-3144) delegate to `emit_direct_int_icmp_intercept` with a predicate string. Verify the exact signature of `emit_direct_int_icmp_intercept` at lib.rs L3156 before lifting — if it takes more than one argument (e.g. an additional "compare-form" enum or a Result-returning bool), adapt the delegate signatures to match. The shape above assumes single-arg `&str` predicate. - [ ] **Step 8: Populate the `INTERCEPTS` table with all 18 entries.** Replace the `pub(crate) static INTERCEPTS: &[Intercept] = &[];` line in `intercepts.rs` with the populated table below. Set `wants_alwaysinline` per entry; the values mirror the current name-list in `intercept_emit_wants_alwaysinline` at `crates/ailang-codegen/src/lib.rs:1172` — every existing intercept that the predicate lists today gets `true`, the rest get `false`. ```rust pub(crate) static INTERCEPTS: &[Intercept] = &[ Intercept { name: "eq__Str", expected_params: &["ptr", "ptr"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_eq_str, }, Intercept { name: "compare__Int", expected_params: &["i64", "i64"], expected_ret: "ptr", wants_alwaysinline: true, emit: emit_compare_int, }, Intercept { name: "compare__Bool", expected_params: &["i1", "i1"], expected_ret: "ptr", wants_alwaysinline: true, emit: emit_compare_bool, }, Intercept { name: "compare__Str", expected_params: &["ptr", "ptr"], expected_ret: "ptr", wants_alwaysinline: true, emit: emit_compare_str, }, Intercept { name: "eq__Int", expected_params: &["i64", "i64"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_eq_int, }, Intercept { name: "eq__Bool", expected_params: &["i1", "i1"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_eq_bool, }, Intercept { name: "eq__Unit", expected_params: &[], expected_ret: "i1", wants_alwaysinline: true, emit: emit_eq_unit, }, Intercept { name: "float_eq", expected_params: &["double", "double"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_float_eq, }, Intercept { name: "float_ne", expected_params: &["double", "double"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_float_ne, }, Intercept { name: "float_lt", expected_params: &["double", "double"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_float_lt, }, Intercept { name: "float_le", expected_params: &["double", "double"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_float_le, }, Intercept { name: "float_gt", expected_params: &["double", "double"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_float_gt, }, Intercept { name: "float_ge", expected_params: &["double", "double"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_float_ge, }, Intercept { name: "lt__Int", expected_params: &["i64", "i64"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_lt_int, }, Intercept { name: "le__Int", expected_params: &["i64", "i64"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_le_int, }, Intercept { name: "gt__Int", expected_params: &["i64", "i64"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_gt_int, }, Intercept { name: "ge__Int", expected_params: &["i64", "i64"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_ge_int, }, Intercept { name: "ne__Int", expected_params: &["i64", "i64"], expected_ret: "i1", wants_alwaysinline: true, emit: emit_ne_int, }, ]; ``` **Note for the implementer:** the `expected_params` for `eq__Unit` is `&[]` because lib.rs L2985 has `(_ params ignored)` per the recon — Unit values carry no SSA payload. Verify against the lib.rs arm; if the actual arm checks against a different param shape (e.g. `["ptr"]` for a Unit-token slot), use the lib.rs version. The `wants_alwaysinline: true` on every entry mirrors the current predicate's all-inclusive name list per recon — if the predicate at lib.rs L1172 actually distinguishes some entries (e.g. only Eq names get inline, not Compare names), copy that distinction here. This is the **lockstep coupling** (intercept names ↔ alwaysinline-name-list) that the registry consolidates. - [ ] **Step 9: Build the workspace; verify all four E2E ratifier tests still pass.** The `INTERCEPTS` table is now populated but **not yet consulted** by the dispatch site — `try_emit_primitive_instance_body` still runs the full match in `lib.rs`. The table is dead code at this point. Behaviour is unchanged. Run: `cargo build --workspace 2>&1 | tail -5` Expected: build succeeds; dead-code warnings on `INTERCEPTS` / `lookup` / `check_sig` (the table is populated but the dispatch site does not yet call `lookup`) and possibly on the emit fns are acceptable. Run: `cargo test -p ail --test e2e eq_primitives_smoke_compiles_and_runs` Expected: PASS Run: `cargo test -p ail --test e2e compare_primitives_smoke_prints_1_2_3_thrice` Expected: PASS Run: `cargo test -p ail --test float_compare_smoke_e2e float_compare_smoke_prints_true_true_false` Expected: PASS Run: `cargo test -p ail --test eq_ord_e2e eq_ord_polymorphic_runs_end_to_end` Expected: PASS --- ### Task 3: Rewire the dispatch site — `try_emit_primitive_instance_body` body → `intercepts::lookup` shim **Files:** - Modify: `crates/ailang-codegen/src/lib.rs:2841-3147` (the entire fn body, replaced by a 6-line shim) - [ ] **Step 1: Replace the body of `try_emit_primitive_instance_body` with the registry-consulting shim.** In `crates/ailang-codegen/src/lib.rs`, locate `try_emit_primitive_instance_body` at L2841. The fn currently runs from L2841 to L3147 (~306 lines). Preserve the fn signature (so all 14 in-source comment-refs remain valid pointers to the canonical dispatch name), and replace the entire body with: ```rust fn try_emit_primitive_instance_body( &mut self, fn_name: &str, param_tys: &[String], ret_ty: &str, ) -> Result { match intercepts::lookup(fn_name) { Some(intercept) => { intercepts::check_sig(intercept, param_tys, ret_ty)?; (intercept.emit)(self)?; Ok(true) } None => Ok(false), } } ``` The 18 match arms at L2848-L3144 are deleted. The trailing `_ => Ok(false)` fallthrough is preserved by the `None` arm of the `match intercepts::lookup(...)`. - [ ] **Step 2: Build the workspace + run the four E2E ratifiers.** Run: `cargo build --workspace 2>&1 | tail -5` Expected: build succeeds; the dead-code warnings on `INTERCEPTS` / `lookup` / `check_sig` / the emit fns are now gone (the dispatch site consumes them). Run: `cargo test -p ail --test e2e eq_primitives_smoke_compiles_and_runs` Expected: PASS — `eq__Str` and `eq__Int|Bool` arms now flow through the registry; printed output unchanged. Run: `cargo test -p ail --test e2e compare_primitives_smoke_prints_1_2_3_thrice` Expected: PASS — `compare__Int|Bool|Str` arms now flow through the registry; printed output unchanged. Run: `cargo test -p ail --test float_compare_smoke_e2e float_compare_smoke_prints_true_true_false` Expected: PASS — the six `float_*` arms now flow through the registry; printed output unchanged. Run: `cargo test -p ail --test eq_ord_e2e eq_ord_polymorphic_runs_end_to_end` Expected: PASS — polymorphic Eq/Ord dispatch over Int/Bool/Str exercises the registry secondarily. - [ ] **Step 3: Run the full workspace test suite to catch any regression the four E2E ratifiers missed.** Run: `cargo test --workspace 2>&1 | tail -10` Expected: every test passes; baseline of 668 tests stays green (Task 5 adds one new test bringing the total to 669, but at this point the count is still 668). --- ### Task 4: Fold `wants_alwaysinline` into the registry — close the lockstep drift class **Files:** - Modify: `crates/ailang-codegen/src/lib.rs:1172` (the `intercept_emit_wants_alwaysinline` predicate body) - [ ] **Step 1: Rewrite the `intercept_emit_wants_alwaysinline` predicate body to consult the registry.** In `crates/ailang-codegen/src/lib.rs`, locate `intercept_emit_wants_alwaysinline` starting at L1172. The predicate is currently a method on `Emitter` (or a free fn — check the signature) that takes `symbol: &str` (or `name: &str`) and returns `bool` by matching on a hardcoded name list. Replace the body with a registry consultation: ```rust pub(crate) fn intercept_emit_wants_alwaysinline(&self, symbol: &str) -> bool { intercepts::lookup(symbol).is_some_and(|i| i.wants_alwaysinline) } ``` (If the existing predicate is a free fn rather than a method, drop the `&self` parameter and adjust accordingly. If it returns via a non-`is_some_and` idiom that the project uses elsewhere, prefer `.map_or(false, |i| i.wants_alwaysinline)` — both are stable Rust.) - [ ] **Step 2: Build the workspace.** Run: `cargo build --workspace 2>&1 | tail -5` Expected: build succeeds. - [ ] **Step 3: Run the two alwaysinline-presence pin tests + the four E2E ratifiers.** The alwaysinline attribute is what these pin tests verify is attached to the generated IR; if the predicate's new registry-driven body returns a different set than the old name list, the pins go red. Run: `cargo test -p ailang-codegen --test eq_primitives_pin 2>&1 | tail -10` Expected: all tests in this file PASS. Run: `cargo test -p ail --test prelude_eq_alwaysinline_ir_pin 2>&1 | tail -10` Expected: all tests in this file PASS. Run: `cargo test -p ail --test e2e eq_primitives_smoke_compiles_and_runs` Expected: PASS. Run: `cargo test -p ail --test e2e compare_primitives_smoke_prints_1_2_3_thrice` Expected: PASS. Run: `cargo test -p ail --test float_compare_smoke_e2e float_compare_smoke_prints_true_true_false` Expected: PASS. Run: `cargo test -p ail --test eq_ord_e2e eq_ord_polymorphic_runs_end_to_end` Expected: PASS. - [ ] **Step 4: Full workspace test run.** Run: `cargo test --workspace 2>&1 | tail -10` Expected: all 668 tests PASS. --- ### Task 5: Add the `registry_contains_all_legacy_arms` pin test **Files:** - Modify: `crates/ailang-codegen/src/intercepts.rs` (append the test module at the bottom) - [ ] **Step 1: Append the pin test module to `crates/ailang-codegen/src/intercepts.rs`.** Append the following to the end of the file: ```rust #[cfg(test)] mod tests { use super::lookup; /// Pin: every name that was a match arm in the legacy /// `try_emit_primitive_instance_body` (before raw-buf.1) /// must resolve to a registered Intercept. If this test /// goes red, a name was dropped during migration AND/OR /// removed from the registry without the dispatch wrapper /// being audited to confirm no consumer still depends on /// it. #[test] fn registry_contains_all_legacy_arms() { let legacy_names = [ "eq__Str", "compare__Int", "compare__Bool", "compare__Str", "eq__Int", "eq__Bool", "eq__Unit", "float_eq", "float_ne", "float_lt", "float_le", "float_gt", "float_ge", "lt__Int", "le__Int", "gt__Int", "ge__Int", "ne__Int", ]; let missing: Vec<&str> = legacy_names .iter() .copied() .filter(|n| lookup(n).is_none()) .collect(); assert!( missing.is_empty(), "registry is missing legacy intercept names: {missing:?}" ); } } ``` - [ ] **Step 2: Run the new pin test.** Run: `cargo test -p ailang-codegen --lib intercepts::tests::registry_contains_all_legacy_arms` Expected: PASS — all 18 names resolve. - [ ] **Step 3: Final full workspace test run.** Run: `cargo test --workspace 2>&1 | tail -10` Expected: 669 tests PASS (baseline 668 + the new pin). --- ## Self-review This section is the planner's inline scrub, NOT a checklist for the implementer. 1. **Spec coverage.** Every iter-raw-buf.1 spec section maps to a task: - § Architecture point 1 ("Lift the existing hard-coded match … into a registry table") → Tasks 1+2+3. - § Components row 1 ("new file `crates/ailang-codegen/src/intercepts.rs`; edits to `crates/ailang-codegen/src/lib.rs`") → Tasks 1+2+3. - § Testing strategy "raw-buf.1" first bullet (four E2E ratifiers stay green) → Tasks 2,3,4,5 Step "Run the four E2E ratifiers". - § Testing strategy "raw-buf.1" second bullet (`registry_contains_all_legacy_arms` test) → Task 5. - Lockstep coupling that recon flagged (intercept names ↔ `intercept_emit_wants_alwaysinline`) → Task 4 (folded the bool into the table, closing the drift class). 2. **Placeholder scan.** No "TBD", no "TODO", no "implement later", no "similar to Task N", no "add appropriate error handling". The two "Note for the implementer" callouts (Step 4 in Task 2, Step 5 in Task 2) are verification asks against the verbatim source ("preserve the lib.rs body exactly"), not placeholders for design judgement. 3. **Type consistency.** `Emitter<'_>` is the struct name everywhere; `Intercept` is the registry-entry struct name; `INTERCEPTS` (static `&[Intercept]`) is the table; `lookup(name) -> Option<&'static Intercept>` is the resolver; `check_sig(intercept, params, ret)` is the sig-check helper. `try_emit_primitive_instance_body` is preserved as a method on `Emitter`. All 18 emit-fn names follow the convention `emit_` where `` is the legacy arm name with `__` → `_` and lowercased (`eq__Str` → `emit_eq_str`). 4. **Step granularity.** Task 2 has 9 steps; each step touches 1–6 fns or one structural change. The "add 6 float emit fns" step is the chunkiest but each fn is 8–12 lines of mechanical lift; total ~5 min. Other steps are 2-3 min. 5. **No commit steps.** No `git commit` anywhere in the task templates. 6. **Pin/replacement substring contiguity.** Task 5's pin test asserts the 18 names exist in the registry. The replacement body for `INTERCEPTS` in Task 2 Step 8 names each of the 18 strings literally; each name appears as a contiguous string literal (e.g. `name: "compare__Int"`), not soft-wrapped across two lines. Substring contiguity preserved. 7. **Compile-gate vs deferred-caller ordering.** The fn `try_emit_primitive_instance_body` keeps its signature throughout the plan. Only its body changes (Task 3). No caller-threading deferred across a compile gate. The visibility bumps in Task 2 (Steps 1+2) are additive and do not affect any caller. The `intercept_emit_wants_alwaysinline` predicate signature also stays constant (only body changes in Task 4). 8. **Verification-command filter strings.** Each `cargo test` filter string is a test fn name that the recon report verified exists at a specific line in a specific file. Spot-check: `eq_primitives_smoke_compiles_and_runs` at `crates/ail/tests/e2e.rs:945`; `compare_primitives_smoke_prints_1_2_3_thrice` at `crates/ail/tests/e2e.rs:932`; `float_compare_smoke_prints_true_true_false` at `crates/ail/tests/float_compare_smoke_e2e.rs:11`; `eq_ord_polymorphic_runs_end_to_end` at `crates/ail/tests/eq_ord_e2e.rs:61`. The new pin `registry_contains_all_legacy_arms` is invented in this plan; the Task 5 Step 2 invocation `cargo test -p ailang-codegen --lib intercepts::tests::registry_contains_all_legacy_arms` follows the standard `cargo test --lib` form for in-source `#[cfg(test)] mod tests`. The `cargo test --workspace` invocation has no filter — it runs the whole suite. Filter-zero-match risk: zero.