Skip to main content

logicaffeine_compile/optimize/
mod.rs

1mod defunctionalize;
2mod fold;
3mod splice_fuse;
4mod inline_tiny;
5mod inline_leaf;
6mod inline_recursive;
7mod popcount_leaf;
8mod symmetry;
9mod dce;
10mod propagate;
11mod gvn;
12mod loop_carried_cse;
13mod float_induction_sr;
14mod licm;
15mod bound_version;
16mod closed_form;
17mod ctfe;
18mod deforest;
19// Loop unrolling reuses the codegen peephole's counted-loop recognizer, so it
20// rides the same feature gate; the AOT (Rust-emitting) pipeline is its consumer.
21#[cfg(feature = "codegen")]
22mod unroll;
23// Fixed-size float/int Seq scalarization (the interpreter's SROA). Reuses the
24// codegen scalarization detection, so it rides the same feature gate; paired
25// AFTER the run-path unroller, which makes every surviving index constant.
26#[cfg(feature = "codegen")]
27mod scalarize;
28// Affine read-only `Seq` scalarization (the interpreter's analog of the AOT's
29// `codegen::affine_array`): delete a CSR-style offset array built by one affine
30// `Push f(i) to arr` loop and substitute every `item k of arr` read with the
31// closed form `f(k-1)`. Default OFF behind `LOGOS_RUN_AFFINE`; AOT-only feature
32// gate (it rewrites the run-path AST). Runs before `scalarize`, on the array
33// form GVN already proved reload-correct.
34#[cfg(feature = "codegen")]
35mod affine_scalarize;
36// Guard-based loop index-set splitting reuses the same recognizer (its emitted
37// sub-loops must satisfy it to vectorize), so it rides the same gate; AOT-only.
38#[cfg(feature = "codegen")]
39mod loop_split;
40mod affine;
41mod abstract_interp;
42pub use abstract_interp::{
43    lin_to_rust, oracle_analyze, oracle_analyze_with, oracle_analyze_with_entry_guards, OracleFacts,
44    ScalarKind, VarProvenFacts,
45};
46pub mod egraph;
47pub mod supercompile;
48pub mod effects;
49pub mod bta;
50pub mod partial_eval;
51
52use std::collections::HashMap;
53
54use crate::arena::Arena;
55use crate::ast::stmt::{Expr, Stmt};
56use crate::intern::{Interner, Symbol};
57use crate::optimization::{
58    admits_pinned, FiredOptimizations, HotswapConfig, Opt, OptimizationConfig, Pin, Tier,
59};
60
61thread_local! {
62    /// The optimization config for the CURRENT compile, consulted by codegen and
63    /// the VM compiler for their per-emit toggle decisions (e.g. emit
64    /// `get_unchecked` vs a checked index). It is set ONCE at each compile entry
65    /// (`codegen_program`, `optimize_for_run`) from the explicitly-threaded
66    /// `OptimizationConfig`; any path that does not set it sees the all-on
67    /// default (the prior, fully-optimized behaviour). This is the single
68    /// replacement for the ~16 scattered `std::env::var("LOGOS_*")` reads codegen
69    /// and the VM used to perform — one controlled per-compile source, not
70    /// ambient process state.
71    static ACTIVE_CONFIG: std::cell::Cell<OptimizationConfig> =
72        std::cell::Cell::new(OptimizationConfig::all_on());
73}
74
75/// Record the optimization config for the current compile. Call at every compile
76/// entry (codegen / VM) so the deep codegen + VM toggle reads see it.
77pub fn set_active_config(cfg: OptimizationConfig) {
78    ACTIVE_CONFIG.with(|c| c.set(cfg));
79}
80
81thread_local! {
82    /// Expr node addresses whose Int op is PROVEN in-range by the pass that
83    /// CONSTRUCTED them — the proof lives where the knowledge lives (e.g.
84    /// `try_defer_modulus` sizes its version guard so no op in the guarded
85    /// chunk can overflow, a relation the interval oracle's widening cannot
86    /// re-derive). Codegen consults this BEFORE the interval oracle and
87    /// lowers a registered op as raw i64. Cleared at every optimizer entry
88    /// so a freed arena's reused addresses can never leak a stale proof
89    /// into a later program on the same thread.
90    static PROVEN_RAW_INT_OPS: std::cell::RefCell<std::collections::HashSet<usize>> =
91        std::cell::RefCell::new(std::collections::HashSet::new());
92}
93
94/// Forget all constructed-proof registrations (call at optimizer entry).
95pub(crate) fn clear_proven_raw_int_ops() {
96    PROVEN_RAW_INT_OPS.with(|s| s.borrow_mut().clear());
97}
98
99/// Register an Int op the CONSTRUCTING pass proved can never leave i64.
100pub(crate) fn mark_proven_raw_int_op(e: &crate::ast::stmt::Expr) {
101    PROVEN_RAW_INT_OPS.with(|s| {
102        s.borrow_mut().insert(e as *const crate::ast::stmt::Expr as usize);
103    });
104}
105
106/// Was this exact op node proven in-range by its constructing pass?
107pub fn expr_proven_raw_int_op(e: &crate::ast::stmt::Expr) -> bool {
108    PROVEN_RAW_INT_OPS.with(|s| s.borrow().contains(&(e as *const crate::ast::stmt::Expr as usize)))
109}
110
111/// The current compile's optimization config (all-on if unset).
112pub fn active_config() -> OptimizationConfig {
113    ACTIVE_CONFIG.with(|c| c.get())
114}
115
116thread_local! {
117    /// When `Some`, an optimization-firing trace is active for the current
118    /// compile: each optimization records (via [`mark_fired`]) the moment it
119    /// actually fires. `None` (the default) means no trace — [`mark_fired`] is
120    /// a no-op and the change-detection in [`run_traced`] is skipped, so the
121    /// normal compile path pays nothing. Opt-in, set once per traced compile by
122    /// [`begin_fired_trace`]; lives on the same thread that runs both the
123    /// optimizer and codegen, so codegen-time marks are recorded too.
124    static FIRED: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
125
126    /// The PRECEDENCE decisions recorded during the current trace: each
127    /// `(winner, loser)` pair is a spot where the code chose `winner` and skipped
128    /// `loser` for an instance (the conflict the optimization SYSTEM resolves).
129    /// `None` when no trace is active, so [`mark_preempted`] is then a no-op and
130    /// the normal compile path pays nothing. Shares the trace lifetime with
131    /// [`FIRED`] — both reset by [`begin_fired_trace`].
132    static PREEMPTED: std::cell::RefCell<Option<Vec<(Opt, Opt)>>> =
133        const { std::cell::RefCell::new(None) };
134}
135
136/// Begin recording which optimizations fire AND which they preempt for the
137/// current compile. Resets both accumulators; pair with [`end_fired_trace`] /
138/// [`end_preempted_trace`].
139pub fn begin_fired_trace() {
140    FIRED.with(|f| f.set(Some(0)));
141    PREEMPTED.with(|p| *p.borrow_mut() = Some(Vec::new()));
142}
143
144/// Stop recording and take the fired set (`None` if no trace was active).
145pub fn end_fired_trace() -> Option<FiredOptimizations> {
146    FIRED.with(|f| f.take().map(FiredOptimizations::from_bits))
147}
148
149/// Whether a firing trace is currently active. Gates the cost of the
150/// before/after fingerprint in [`run_traced`].
151#[inline]
152pub fn fired_trace_active() -> bool {
153    FIRED.with(|f| f.get().is_some())
154}
155
156/// Record that `opt` fired in the current compile. No-op when no trace is active.
157#[inline]
158pub fn mark_fired(opt: Opt) {
159    FIRED.with(|f| {
160        if let Some(bits) = f.get() {
161            f.set(Some(bits | opt.bit()));
162        }
163    });
164}
165
166/// Record that `winner` took precedence over `loser` here — the exact spot the
167/// code skips one optimization for another (the conflict, traced at its source,
168/// the same way [`mark_fired`] traces firing). No-op when no trace is active, so
169/// the normal compile path and the benchmark scripts pay nothing. Self-gated on
170/// BOTH being enabled in the current config: a preemption is only meaningful when
171/// the two genuinely contest an instance — so toggling either off cleanly stops
172/// the edge from being reported.
173#[inline]
174pub fn mark_preempted(winner: Opt, loser: Opt) {
175    if !fired_trace_active() {
176        return;
177    }
178    let cfg = active_config();
179    if !cfg.is_on(winner) || !cfg.is_on(loser) {
180        return;
181    }
182    PREEMPTED.with(|p| {
183        if let Some(v) = p.borrow_mut().as_mut() {
184            v.push((winner, loser));
185        }
186    });
187}
188
189/// Stop recording and take the deduplicated set of `(winner, loser)` precedence
190/// decisions made during the trace (empty if no trace was active).
191pub fn end_preempted_trace() -> Vec<(Opt, Opt)> {
192    let mut edges = PREEMPTED.with(|p| p.borrow_mut().take().unwrap_or_default());
193    edges.sort_by_key(|&(w, l)| (w as u8, l as u8));
194    edges.dedup();
195    edges
196}
197
198/// Run an AST pass, recording that `opt` fired iff the pass changed the program
199/// — but only while a trace is active. The AST derives `Debug` (not `PartialEq`),
200/// so a `{:?}` fingerprint is the change signal; it is taken only under a live
201/// trace, so the normal compile path runs the pass exactly as before with no
202/// extra cost. The pass runs exactly once either way (no behavior change).
203#[inline]
204pub(crate) fn run_traced<'a>(
205    opt: Opt,
206    input: Vec<Stmt<'a>>,
207    pass: impl FnOnce(Vec<Stmt<'a>>) -> Vec<Stmt<'a>>,
208) -> Vec<Stmt<'a>> {
209    if !fired_trace_active() {
210        return pass(input);
211    }
212    let before = format!("{input:?}");
213    let out = pass(input);
214    if format!("{out:?}") != before {
215        mark_fired(opt);
216    }
217    out
218}
219
220/// Lighter optimization for Futamura P1: fold + propagate + PE + CTFE.
221/// Skips DCE, abstract interpretation, supercompilation, and structural
222/// transforms (LICM, closed-form, deforestation) that eliminate branches
223/// or restructure control flow. The residual preserves the program's
224/// original control-flow structure while still folding constants,
225/// propagating values, specializing functions, and evaluating CTFE.
226pub fn optimize_for_projection<'a>(
227    stmts: Vec<Stmt<'a>>,
228    expr_arena: &'a Arena<Expr<'a>>,
229    stmt_arena: &'a Arena<Stmt<'a>>,
230    interner: &mut Interner,
231    cfg: &OptimizationConfig,
232) -> Vec<Stmt<'a>> {
233    let bta_cache = bta::analyze_with_sccs(&stmts, interner);
234    let mut current = stmts;
235    if cfg.is_on(Opt::Specialize) {
236        let mut variant_count: HashMap<Symbol, usize> = HashMap::new();
237        for _ in 0..16 {
238            let folded = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
239            let propagated = propagate::propagate_stmts(folded, expr_arena, stmt_arena, interner);
240            let (specialized, changes) = partial_eval::specialize_stmts_with_state(
241                propagated, expr_arena, stmt_arena, interner, &mut variant_count,
242                Some(&bta_cache),
243            );
244            current = specialized;
245            if changes == 0 {
246                break;
247            }
248        }
249    }
250    if cfg.is_on(Opt::Comptime) {
251        current = ctfe::ctfe_stmts(current, expr_arena, stmt_arena, interner);
252    }
253    fold::fold_stmts(current, expr_arena, stmt_arena, interner)
254}
255
256/// The RUN-PATH pipeline (EXODIA D1): the Futamura residual — fold,
257/// propagate, polyvariant PE, CTFE, GVN, LICM, closed-form, deforestation,
258/// interval analysis, DCE — everything except supercompilation (whose
259/// driving cost is unbounded; it stays AOT-only). Budgeted: programs beyond
260/// the statement gate run raw, and `LOGOS_RUN_OPT=0` kills the pass
261/// entirely. Optimizer time lands inside the measured run, so the budget is
262/// part of the contract.
263pub fn optimize_for_run<'a>(
264    stmts: Vec<Stmt<'a>>,
265    expr_arena: &'a Arena<Expr<'a>>,
266    stmt_arena: &'a Arena<Stmt<'a>>,
267    interner: &mut Interner,
268    cfg: &OptimizationConfig,
269) -> Vec<Stmt<'a>> {
270    // The un-tiered entry point is `Tier::T3`: every admitted opt runs, so with an
271    // all-on config this is today's whole-program pipeline, bit-for-bit (HOTSWAP §2).
272    optimize_for_run_tiered(
273        stmts,
274        expr_arena,
275        stmt_arena,
276        interner,
277        cfg,
278        &HotswapConfig::default(),
279        Tier::T3,
280    )
281}
282
283/// How many partial-evaluation fixpoint iterations to pay for. `Tier::T3` runs the
284/// full 16-iteration fixpoint (PE-full); `Tier::T2` runs a capped 4 (PE-light) to feed
285/// the Medium passes folded/specialized code without the full tax. Below T2 the
286/// Specialize pass is not admitted at all (it is `Medium`), so the cap is unused. A
287/// `## Tier specialize <eager|t..>` pin brings PE forward AND gives it the full
288/// fixpoint ("ignore its cost", HOTSWAP §8).
289fn pe_iters(hs: &HotswapConfig, tier: Tier) -> usize {
290    if matches!(hs.pin(Opt::Specialize), Some(Pin::Eager) | Some(Pin::At(_))) {
291        return 16;
292    }
293    match tier {
294        Tier::T0 | Tier::T1 => 0,
295        Tier::T2 => 4,
296        Tier::T3 => 16,
297    }
298}
299
300/// The run-path optimizer, gated by the unit's hotness `tier` (HOTSWAP §4). Every
301/// pass runs in the SAME order as the un-tiered pipeline; `tier` decides WHICH passes
302/// run (via [`admits_pinned`], a single cost comparison, with `hs`'s per-opt pins
303/// applied) and how many PE-fixpoint iterations to pay for. `Tier::T3` with an all-on
304/// config and no pins reproduces [`optimize_for_run`] exactly — the compatibility +
305/// soundness anchor.
306pub fn optimize_for_run_tiered<'a>(
307    stmts: Vec<Stmt<'a>>,
308    expr_arena: &'a Arena<Expr<'a>>,
309    stmt_arena: &'a Arena<Stmt<'a>>,
310    interner: &mut Interner,
311    cfg: &OptimizationConfig,
312    hs: &HotswapConfig,
313    tier: Tier,
314) -> Vec<Stmt<'a>> {
315    set_active_config(*cfg);
316    clear_proven_raw_int_ops();
317    if cfg.is_all_off() || tier == Tier::T0 {
318        return stmts;
319    }
320    const MAX_RUN_OPT_STMTS: usize = 5_000;
321    if stmts.len() > MAX_RUN_OPT_STMTS {
322        return stmts;
323    }
324    let bta_cache = bta::analyze_with_sccs(&stmts, interner);
325    let mut current = stmts;
326    if admits_pinned(cfg, hs, tier, Opt::Inline) {
327        // Tiny pure helpers inline first, so PE/fold/GVN/LICM see straight
328        // arithmetic and the loop regions compile call-free.
329        current = run_traced(Opt::Inline, current, |c| {
330            inline_tiny::inline_tiny_fns(c, expr_arena, stmt_arena, interner)
331        });
332        // Statement-body leaf helpers (iterative gcd shape) fold in next, so
333        // their calling loops compile as one call-free region.
334        current = run_traced(Opt::Inline, current, |c| {
335            inline_leaf::inline_leaf_fns(c, expr_arena, stmt_arena, interner)
336        });
337    }
338    if admits_pinned(cfg, hs, tier, Opt::Specialize) {
339        let mut variant_count: HashMap<Symbol, usize> = HashMap::new();
340        let mut specialized_any = false;
341        let cap = pe_iters(hs, tier);
342        let mut capped_early = false;
343        for i in 0..cap {
344            let folded = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
345            let propagated = propagate::propagate_stmts(folded, expr_arena, stmt_arena, interner);
346            let (specialized, changes) = partial_eval::specialize_stmts_with_state(
347                propagated, expr_arena, stmt_arena, interner, &mut variant_count,
348                Some(&bta_cache),
349            );
350            current = specialized;
351            if changes == 0 {
352                break;
353            }
354            specialized_any = true;
355            // The fixpoint had not converged by the tier's iteration budget.
356            capped_early = i + 1 == cap;
357        }
358        if specialized_any {
359            mark_fired(Opt::Specialize);
360        }
361        // PE-light (T2) stops the fixpoint early, which can leave the residual not
362        // fold-stable; the Medium passes below (loop-carried CSE, GVN, closed-form)
363        // assume canonical sub-expressions. Re-normalize after a capped-early exit so
364        // they fire on the same shape they would at T3. T3 runs to convergence (its
365        // last iteration specialized 0 changes on folded+propagated input), so it is
366        // already canonical and stays byte-for-byte today's pipeline.
367        if capped_early && tier < Tier::T3 {
368            current = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
369            current = propagate::propagate_stmts(current, expr_arena, stmt_arena, interner);
370        }
371    }
372    if admits_pinned(cfg, hs, tier, Opt::Comptime) {
373        current = run_traced(Opt::Comptime, current, |c| {
374            ctfe::ctfe_stmts(c, expr_arena, stmt_arena, interner)
375        });
376        current = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
377    }
378    // Loop-carried CSE: hoist a squared term that a `While`'s escape guard
379    // computes over the freshly assigned loop iterate and the next iteration's
380    // body recomputes over the same value (mandelbrot's `zr*zr` / `zi*zi`) into a
381    // fresh per-iteration temp — computed ONCE just after the operand's `Set`,
382    // substituted into both the guard and the body recomputation. Runs after the
383    // mask&1 PE fixpoint (so identical sub-expressions are already canonical) and
384    // before GVN; fold+propagate refold the substituted forms. PROMOTED 2026-06-21:
385    // default ON (kill-switch LOGOS_RUN_LCSE=0) — mandelbrot -14% on the faithful
386    // interleaved A/B (2.40x->2.07x Node), 30/30 benchmarks bit-identical, no regress.
387    if admits_pinned(cfg, hs, tier, Opt::LoopCse) {
388        let (c, fired) =
389            loop_carried_cse::loop_carried_cse_stmts(current, expr_arena, stmt_arena, interner);
390        current = c;
391        if fired {
392            mark_fired(Opt::LoopCse);
393            current = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
394            current = propagate::propagate_stmts(current, expr_arena, stmt_arena, interner);
395        }
396    }
397    // U6: float induction-variable strength reduction — replace a per-iteration
398    // `c1*k + c2` (a multiply + int->float cvtsi2sd) with a guarded incremental
399    // float accumulator. PROMOTED 2026-06-21: default ON (kill-switch
400    // LOGOS_RUN_FLOATSR=0) — pi_leibniz -31.7% on the faithful A/B (geomean
401    // 0.891->0.879), bit-identical, no regression (the runtime trip-count guard
402    // keeps it bit-identical for ALL inputs). Found via the stencil investigation.
403    if admits_pinned(cfg, hs, tier, Opt::FloatStrength) {
404        let (c, fired) = float_induction_sr::float_induction_sr_stmts(
405            current, expr_arena, stmt_arena, interner,
406        );
407        current = c;
408        if fired {
409            mark_fired(Opt::FloatStrength);
410            current = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
411            current = propagate::propagate_stmts(current, expr_arena, stmt_arena, interner);
412        }
413    }
414    if admits_pinned(cfg, hs, tier, Opt::Cse) {
415        current = run_traced(Opt::Cse, current, |c| {
416            gvn::cse_stmts(c, expr_arena, stmt_arena, interner)
417        });
418    }
419    // Run-path array scalarization (the interpreter's SROA): fully unroll the
420    // constant-trip loops over a fixed-size float/int Seq, then replace the Seq
421    // with N scalar locals so every `item k of arr` is a register-resident
422    // variable read instead of a bounds-checked heap load. Unroll alone was a
423    // measured loss (a bigger region over the same loads); the scalarize half is
424    // what removes them. PROMOTED 2026-06-21: default ON (kill-switch
425    // LOGOS_RUN_SCALARIZE=0) — nbody -17% on the faithful interleaved A/B,
426    // 30/30 benchmarks bit-identical, no benchmark regressed.
427    //
428    // It runs AFTER GVN deliberately: GVN's value numbering is reload-correct on
429    // the array form (it never CSEs an `item k of arr` read across a write), but
430    // it would unsoundly CSE the loop-carried SCALAR reads this pass introduces
431    // across the step loop's mutations. Sequencing scalarization after GVN keeps
432    // GVN on the sound array form; the structural passes below (LICM, closed-form,
433    // deforestation, interval analysis, DCE) are reload-correct on the scalar form.
434    // Affine read-only array scalarization: delete a CSR-style offset array
435    // (graph_bfs's `adjStarts`, built by one affine `Push i*5 to adjStarts`
436    // loop) and substitute every `item k of arr` random heap load with the
437    // closed form `5*(k-1)` — C's `v*5` shift. Runs on the ARRAY form, BEFORE
438    // unroll/scalarize, so GVN (which already ran) stayed reload-correct over
439    // the original reads. fold+propagate after it collapse the substituted
440    // closed forms. PROMOTED 2026-06-21: default ON (kill-switch
441    // LOGOS_RUN_AFFINE=0) — graph_bfs -20% on the faithful interleaved A/B
442    // (2.86x->2.29x Node), 30/30 benchmarks bit-identical, no benchmark regressed.
443    #[cfg(feature = "codegen")]
444    if admits_pinned(cfg, hs, tier, Opt::Affine) {
445        let (a, ca) =
446            affine_scalarize::affine_scalarize_seqs(current, expr_arena, stmt_arena, interner);
447        current = a;
448        if ca {
449            mark_fired(Opt::Affine);
450            current = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
451            current = propagate::propagate_stmts(current, expr_arena, stmt_arena, interner);
452        }
453    }
454    #[cfg(feature = "codegen")]
455    {
456        let mut changed = false;
457        if admits_pinned(cfg, hs, tier, Opt::Unroll) {
458            let (u, c1) = unroll::unroll_stmts_run(current, expr_arena, stmt_arena, interner);
459            current = u;
460            if c1 {
461                mark_fired(Opt::Unroll);
462            }
463            changed |= c1;
464        }
465        if admits_pinned(cfg, hs, tier, Opt::Scalarize) {
466            let (s, c2) = scalarize::scalarize_seqs(current, expr_arena, stmt_arena, interner);
467            current = s;
468            if c2 {
469                mark_fired(Opt::Scalarize);
470            }
471            changed |= c2;
472        }
473        if changed {
474            current = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
475            current = propagate::propagate_stmts(current, expr_arena, stmt_arena, interner);
476        }
477    }
478    if admits_pinned(cfg, hs, tier, Opt::LoopHoist) {
479        current = run_traced(Opt::LoopHoist, current, |c| {
480            licm::licm_stmts_run(c, expr_arena, stmt_arena, interner)
481        });
482    }
483    if admits_pinned(cfg, hs, tier, Opt::ClosedForm) {
484        current = run_traced(Opt::ClosedForm, current, |c| {
485            closed_form::closed_form_stmts(c, expr_arena, stmt_arena, interner)
486        });
487    }
488    if admits_pinned(cfg, hs, tier, Opt::Fuse) {
489        current = run_traced(Opt::Fuse, current, |c| {
490            deforest::deforest_stmts(c, expr_arena, stmt_arena, interner)
491        });
492    }
493    if admits_pinned(cfg, hs, tier, Opt::Oracle) {
494        current = run_traced(Opt::Oracle, current, |c| {
495            abstract_interp::abstract_interp_stmts(c, expr_arena, stmt_arena)
496        });
497    }
498    if admits_pinned(cfg, hs, tier, Opt::DeadCode) {
499        current = run_traced(Opt::DeadCode, current, |c| {
500            dce::eliminate_dead_code(c, stmt_arena, expr_arena)
501        });
502    }
503    // Bounded recursion inlining (recursion unrolling) — the lone AOT pass we
504    // also run on the live run path. It flattens a self-recursive function's
505    // own loop-interleaved body k levels deep (the inline LLVM refuses, gcc
506    // -O3 performs), cutting the per-call VM overhead that dominates the
507    // recursion benchmarks. It MUST run LAST: placed before the fixpoint
508    // passes it re-analyses k enlarged clones and the optimizer cost balloons
509    // ~30×, so it runs on the already-optimized residual exactly like the AOT
510    // pipeline (each clone is pre-optimized). It defers to TCE/accumulator by
511    // construction — it fires only on loop-interleaved recursion, never on the
512    // return-position shape `tail_call` later rewrites to a constant-stack
513    // loop. Optimizer time lands inside the measured run, so it is gated twice:
514    // the mask bit (256), and a tight statement budget (`MAX_RUN_INLINE_STMTS`)
515    // below the broad run-opt gate — the recursive kernels are tiny source
516    // programs, while a large body's inlining cost would outweigh the savings.
517    const MAX_RUN_INLINE_STMTS: usize = 1_500;
518    if admits_pinned(cfg, hs, tier, Opt::Unfold) && current.len() <= MAX_RUN_INLINE_STMTS {
519        let unrolled = run_traced(Opt::Unfold, current, |c| {
520            inline_recursive::inline_recursive_fns_run(c, expr_arena, stmt_arena, interner)
521        });
522        // Collapse the argument-binding temps the splice introduces
523        // (`__rN_n = n`), exactly as the AOT pipeline does after this pass.
524        let folded = fold::fold_stmts(unrolled, expr_arena, stmt_arena, interner);
525        let propagated = propagate::propagate_stmts(folded, expr_arena, stmt_arena, interner);
526        current = dce::eliminate_dead_code(propagated, stmt_arena, expr_arena);
527    }
528    current
529}
530
531/// The AOT optimizer — ONE pipeline. The Futamura residual (fold, propagate,
532/// polyvariant PE, CTFE) then the ARCHITECT: equality saturation over a
533/// kernel-certified e-graph, Oracle facts gating the conditional rewrites
534/// (value facts suppressed for loop-mutated variables — see `egraph::convert`);
535/// then defunctionalization, deforestation, interval analysis, DCE, and
536/// supercompilation. GVN/LICM/closed-form STILL run after the e-graph until it
537/// subsumes their cross-statement and loop-recurrence reach (the EG waves).
538pub fn optimize_program<'a>(
539    stmts: Vec<Stmt<'a>>,
540    expr_arena: &'a Arena<Expr<'a>>,
541    stmt_arena: &'a Arena<Stmt<'a>>,
542    interner: &mut Interner,
543    cfg: &OptimizationConfig,
544) -> Vec<Stmt<'a>> {
545    set_active_config(*cfg);
546    clear_proven_raw_int_ops();
547    // De-desugar FIRST: under reference semantics the parser's place-write
548    // Splices fuse back to the direct nested writes every downstream pass
549    // (BTA, borrow-hoist, bounds elision) pattern-matches.
550    let stmts = splice_fuse::fuse_place_splices(stmts, expr_arena, stmt_arena, interner);
551    // NOTE: no early tiny-fn inliner here — that is a RUN-PATH device
552    // (optimize_for_run skips supercompile for budget). This AOT pipeline
553    // inlines through supercompile at the end exactly like v1, so the PE
554    // machinery keeps its subjects.
555    let bta_cache = bta::analyze_with_sccs(&stmts, interner);
556    let mut current = stmts;
557    if cfg.is_on(Opt::Specialize) {
558        let mut variant_count: HashMap<Symbol, usize> = HashMap::new();
559        let mut specialized_any = false;
560        for _ in 0..16 {
561            let folded = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
562            let propagated = propagate::propagate_stmts(folded, expr_arena, stmt_arena, interner);
563            let (specialized, changes) = partial_eval::specialize_stmts_with_state(
564                propagated, expr_arena, stmt_arena, interner, &mut variant_count,
565                Some(&bta_cache),
566            );
567            current = specialized;
568            if changes == 0 {
569                break;
570            }
571            specialized_any = true;
572        }
573        if specialized_any {
574            mark_fired(Opt::Specialize);
575        }
576    }
577    if cfg.is_on(Opt::Comptime) {
578        current = run_traced(Opt::Comptime, current, |c| {
579            ctfe::ctfe_stmts(c, expr_arena, stmt_arena, interner)
580        });
581    }
582    current = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
583    // Reflection symmetry-breaking: halve a bitmask counting search's first-row
584    // enumeration (×2 + odd-n middle) when the kernel proves the reflection
585    // invariance for all n. Runs on the clean specialized entry, before the
586    // structural passes; the sub-counts it calls still get popcount-leaf +
587    // recursion unrolling later. Fail-closed (kernel certificate gates it).
588    if cfg.is_on(Opt::Symmetry) {
589        current = run_traced(Opt::Symmetry, current, |c| {
590            symmetry::break_symmetry_stmts(c, expr_arena, stmt_arena, interner)
591        });
592    }
593    current = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
594    // Fully unroll small constant-trip loops nested inside hot loops (nbody's
595    // per-step force loops), then refold/repropagate so the substituted indices
596    // collapse to constants (`item (3-1) of bx` → `bx[2]`) and the dead counter
597    // scaffolding is cleaned up. LLVM then SROAs the `[f64; N]` arrays into
598    // registers and vectorizes the straight-line body — the codegen it already
599    // produces for top-level constant loops but refuses inside a runtime loop.
600    #[cfg(feature = "codegen")]
601    if cfg.is_on(Opt::Unroll) {
602        let (unrolled, changed) = unroll::unroll_stmts(current, expr_arena, stmt_arena, interner);
603        current = unrolled;
604        if changed {
605            mark_fired(Opt::Unroll);
606            current = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
607            current = propagate::propagate_stmts(current, expr_arena, stmt_arena, interner);
608        }
609    }
610    // Group 4: direct closures lift to first-order functions BEFORE the
611    // e-graph runs, so the Architect (and every later pass) sees plain
612    // calls instead of heap closures.
613    if cfg.is_on(Opt::Defunctionalize) {
614        current = run_traced(Opt::Defunctionalize, current, |c| {
615            defunctionalize::defunctionalize_stmts(c, expr_arena, stmt_arena, interner)
616        });
617    }
618    // The Architect saturates every expression under kernel-certified
619    // rewrites with Oracle-gated conditionals. GVN/LICM/closed-form STAY
620    // until the e-graph's cross-statement runs (M13's SSA versioning)
621    // subsume their capabilities — the structural suite pins them, and a
622    // capability superset is the honest sprint-22 wiring.
623    if cfg.is_on(Opt::Saturate) {
624        current = run_traced(Opt::Saturate, current, |c| {
625            egraph::convert::egraph_stmts(c, expr_arena, stmt_arena, interner)
626        });
627    }
628    if cfg.is_on(Opt::Cse) {
629        current = run_traced(Opt::Cse, current, |c| {
630            gvn::cse_stmts(c, expr_arena, stmt_arena, interner)
631        });
632    }
633    if cfg.is_on(Opt::LoopHoist) {
634        current = run_traced(Opt::LoopHoist, current, |c| {
635            licm::licm_stmts(c, expr_arena, stmt_arena, interner)
636        });
637    }
638    if cfg.is_on(Opt::ClosedForm) {
639        current = run_traced(Opt::ClosedForm, current, |c| {
640            closed_form::closed_form_stmts(c, expr_arena, stmt_arena, interner)
641        });
642    }
643    if cfg.is_on(Opt::Fuse) {
644        current = run_traced(Opt::Fuse, current, |c| {
645            deforest::deforest_stmts(c, expr_arena, stmt_arena, interner)
646        });
647    }
648    // Guard-based loop index-set splitting: split a counted loop carrying a
649    // top-level affine-monotone IV guard into a guard-false prefix (then-block
650    // dropped → memcpy) and a guard-true suffix (then-block inlined, branch-free
651    // → vectorizable unconditional load), behind a version guard that keeps the
652    // original loop for an out-of-range threshold. fold+propagate immediately
653    // after collapse the version guard / branches where statically resolvable,
654    // and the suffix's literal-threshold IV init lets abstract_interp re-prove
655    // the now-unconditional access in range. AOT-only (no SIMD payoff for the
656    // interpreter), so it is not in optimize_for_run / optimize_for_projection.
657    #[cfg(feature = "codegen")]
658    {
659        current = run_traced(Opt::LoopSplit, current, |c| {
660            loop_split::loop_split_stmts(c, expr_arena, stmt_arena, interner, cfg)
661        });
662        current = fold::fold_stmts(current, expr_arena, stmt_arena, interner);
663        current = propagate::propagate_stmts(current, expr_arena, stmt_arena, interner);
664    }
665    if cfg.is_on(Opt::Oracle) {
666        current = run_traced(Opt::Oracle, current, |c| {
667            abstract_interp::abstract_interp_stmts(c, expr_arena, stmt_arena)
668        });
669    }
670    let dced = if cfg.is_on(Opt::DeadCode) {
671        run_traced(Opt::DeadCode, current, |c| {
672            dce::eliminate_dead_code(c, stmt_arena, expr_arena)
673        })
674    } else {
675        current
676    };
677    let current = if cfg.is_on(Opt::Supercompile) {
678        run_traced(Opt::Supercompile, dced, |c| {
679            supercompile::supercompile_stmts(c, expr_arena, stmt_arena, interner)
680        })
681    } else {
682        dced
683    };
684    // Popcount-leaf: collapse the second-to-last level of a bitmask counting
685    // search (`row == n-1`, every remaining bit is one solution) into a single
686    // `count_ones`. Runs BEFORE inline_recursive so the fast path is carried
687    // into every unrolled clone. Pure structural rewrite (no kernel).
688    let current = run_traced(Opt::Popcount, current, |c| {
689        popcount_leaf::popcount_leaf_stmts(c, expr_arena, stmt_arena, interner, cfg)
690    });
691    // Bounded recursive inlining (recursion unrolling) runs LAST: flatten each
692    // self-recursive function's own body k levels deep — the inline LLVM
693    // refuses but gcc -O3 performs (the lever that lets compiled-LOGOS
694    // n-queens match/beat C). Placed after the heavy analyses (e-graph,
695    // supercompile) so they optimize the ORIGINAL body once and never pay to
696    // re-analyze the k enlarged copies — each clone is already optimized.
697    // fold+propagate+DCE then collapse the argument-binding temps the splice
698    // introduces (`__rN_n = n`).
699    let unrolled = if cfg.is_on(Opt::Unfold) {
700        run_traced(Opt::Unfold, current, |c| {
701            inline_recursive::inline_recursive_fns(c, expr_arena, stmt_arena, interner)
702        })
703    } else {
704        current
705    };
706    let folded = fold::fold_stmts(unrolled, expr_arena, stmt_arena, interner);
707    let propagated = propagate::propagate_stmts(folded, expr_arena, stmt_arena, interner);
708    // O9 — bound-versioned loop nests (spectral_norm's vectorization): runs
709    // LAST among the rewriters so nothing rebuilds its proven-raw-marked
710    // clone nodes before codegen. Versioning is guard-based loop cloning, the
711    // LoopSplit family; AOT-only like its sibling (the payoff is SIMD).
712    #[cfg(feature = "codegen")]
713    let propagated = if cfg.is_on(Opt::LoopSplit) {
714        run_traced(Opt::LoopSplit, propagated, |c| {
715            bound_version::bound_version_stmts(c, expr_arena, stmt_arena)
716        })
717    } else {
718        propagated
719    };
720    if cfg.is_on(Opt::DeadCode) {
721        run_traced(Opt::DeadCode, propagated, |c| {
722            dce::eliminate_dead_code(c, stmt_arena, expr_arena)
723        })
724    } else {
725        propagated
726    }
727}