iter loop-recur.3: codegen — real LLVM-IR lowering + run-to-value E2E (milestone terminal)

Third and terminal iteration of the standalone loop/recur
milestone (plan eae73bf). Replaces the iter-1 lower_term
CodegenError::Internal stub for Term::Loop/Term::Recur with real
LLVM-IR lowering: loop binders as entry-block allocas (mut.3
pending_entry_allocas, reusing mut_var_allocas so the existing
Term::Var load path is byte-unchanged), a fresh loop-header block,
recur stores + back-edge br, a loop_frames stack saved/restored at
the lambda boundary. clang -O2 mem2reg promotes the allocas to
phi. Four Boss design calls implemented verbatim and journalled
(alloca-not-hand-phi; mut_var_allocas reuse; emergent loop-exit
via the if/match join; single block_terminated field + parallel
SET site). Diff confirmed surgical: codegen/lib.rs 3 hunks (field
+ init + stub->2-arms), lambda.rs 2 hunks (save+restore); zero
edits to any existing block_terminated SET/READ site, tail-app
lowering, or verify_tail_positions (Boss call 4, the spec-pinned
invariant). Three new .ail fixtures: sum_to->55,
deep-n 1e6->500000500000 (clause-2 correctness made executable),
infinite-loop build-only. Codegen-only: no schema/typecheck
change; hash pins + drift trio stay green untouched.

One DONE_WITH_CONCERNS (T3): the plan's `cargo test ... tail`
filter resolved to no tests; ran via real names + the full 619/0
which subsumes it (feedback_plan_pseudo_vs_reality class, no
behaviour change). Boss systemic fix folded in: planner SKILL.md
Step-5 gains item 8 (verification-command filter strings must
resolve) — the second planner-meta-gap this milestone surfaced.

cargo test --workspace 616 -> 619 / 0 red (Boss-reran
independently); the 3 loop/recur e2e explicitly green. All three
components shipped: the loop/recur milestone is structurally
complete. Milestone-close audit + fieldtest is the next step.
This commit is contained in:
2026-05-17 23:57:46 +02:00
parent eae73bf320
commit edd2558d35
10 changed files with 434 additions and 3 deletions
+37
View File
@@ -82,6 +82,43 @@ fn sum_1_to_10_is_55() {
assert_eq!(stdout.trim(), "55");
}
#[test]
fn loop_recur_sum_to_runs_to_value() {
let stdout = build_and_run("loop_sum_to_run.ail");
assert_eq!(stdout.trim(), "55");
}
#[test]
fn loop_recur_deep_n_safe_by_construction() {
// 1..1_000_000 summed via the loop back-edge (no stack growth);
// a hand-written non-tail self-recursion to this depth would
// overflow. sum 1..1_000_000 = 500000500000 (fits i64).
let stdout = build_and_run("loop_sum_to_deep.ail");
assert_eq!(stdout.trim(), "500000500000");
}
#[test]
fn loop_recur_infinite_loop_compiles() {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
let src = workspace.join("examples").join("loop_forever_build.ail");
let tmp = std::env::temp_dir().join(format!(
"ailang_e2e_loopforever_{}",
std::process::id()
));
std::fs::create_dir_all(&tmp).unwrap();
let out = tmp.join("bin");
let status = Command::new(ail_bin())
.args(["build", src.to_str().unwrap(), "-o"])
.arg(&out)
.status()
.expect("ail build failed to run");
assert!(
status.success(),
"ail build must succeed on an infinite loop (typechecks AND compiles, no termination claim)"
);
}
/// Guards block tracking in codegen: max3 has nested `if`s, and wrong
/// phi block tracking would produce a wrong result here.
#[test]