Skip to main content

logicaffeine_compile/codegen/
program.rs

1use std::collections::{HashMap, HashSet};
2use std::fmt::Write;
3
4use crate::analysis::registry::{FieldDef, TypeDef, TypeRegistry};
5use crate::analysis::policy::PolicyRegistry;
6use crate::analysis::types::RustNames;
7use crate::ast::stmt::{Expr, Stmt, TypeExpr};
8use crate::optimization::{Opt, OptimizationConfig};
9use crate::intern::{Interner, Symbol};
10use crate::sourcemap::{OwnershipRole, SourceMap, SourceMapBuilder};
11use crate::token::Span as LogosSpan;
12use crate::registry::SymbolRegistry;
13
14use super::context::{RefinementContext, VariableCapabilities, analyze_variable_capabilities};
15use crate::analysis::callgraph::CallGraph;
16
17/// Does the program call `count_ones` (inserted by `optimize::popcount_leaf`)?
18/// Gates emission of the `count_ones` helper so programs that don't use it have
19/// byte-identical codegen (keeps the codegen snapshot tests stable).
20fn program_uses_count_ones(stmts: &[Stmt], interner: &Interner) -> bool {
21    fn in_expr(e: &Expr, it: &Interner) -> bool {
22        match e {
23            Expr::Call { function, args } => {
24                it.resolve(*function) == "count_ones" || args.iter().any(|a| in_expr(a, it))
25            }
26            Expr::BinaryOp { left, right, .. } => in_expr(left, it) || in_expr(right, it),
27            Expr::Not { operand } => in_expr(operand, it),
28            Expr::Index { collection, index } => in_expr(collection, it) || in_expr(index, it),
29            Expr::Length { collection } => in_expr(collection, it),
30            _ => false,
31        }
32    }
33    fn in_block(b: &[Stmt], it: &Interner) -> bool {
34        b.iter().any(|s| match s {
35            Stmt::Let { value, .. } | Stmt::Set { value, .. } => in_expr(value, it),
36            Stmt::Return { value } => value.map_or(false, |e| in_expr(e, it)),
37            Stmt::If { cond, then_block, else_block } => {
38                in_expr(cond, it)
39                    || in_block(then_block, it)
40                    || else_block.map_or(false, |b| in_block(b, it))
41            }
42            Stmt::While { cond, body, .. } => in_expr(cond, it) || in_block(body, it),
43            Stmt::FunctionDef { body, .. } => in_block(body, it),
44            _ => false,
45        })
46    }
47    in_block(stmts, interner)
48}
49use crate::analysis::liveness::LivenessResult;
50use crate::analysis::readonly::{ReadonlyParams, MutableBorrowParams};
51
52use super::detection::{
53    requires_async, requires_vfs, collect_mutable_vars,
54    collect_crdt_register_fields, collect_boxed_fields, collect_async_functions,
55    collect_pure_functions, count_self_calls, is_hashable_type, is_copy_type_expr,
56    should_memoize, body_contains_self_call, should_inline,
57    collect_pipe_sender_params, collect_pipe_vars,
58    collect_mutable_vars_stmt, is_result_type,
59    vec_to_slice_type, vec_to_mut_slice_type, collect_give_arg_indices, is_vec_type_expr,
60    collect_single_char_text_vars, collect_escaping_collection_vars,
61    collect_scalarizable_seqs, collect_interleaved_groups, collect_de_rc_seqs, collect_vec_return_fns,
62    detect_double_recursion_closed_form,
63};
64use super::expr::{codegen_expr, codegen_expr_with_async};
65use super::ffi::{
66    has_wasm_exports, has_c_exports, has_c_exports_with_text,
67    codegen_logos_runtime_preamble, collect_c_export_reference_types,
68    collect_c_export_value_type_structs,
69};
70use super::marshal::{is_text_type, is_char_type, codegen_c_export_with_marshaling};
71use super::policy::codegen_policy_impls;
72use super::stmt::codegen_stmt;
73use super::tce::{
74    is_tail_recursive, body_has_top_level_tail_pair, codegen_tce_loopback,
75    codegen_stmt_acc,
76    detect_mutual_tce_pairs, codegen_mutual_tce_pair, codegen_stmt_tce,
77};
78use crate::tail_call::detect_accumulator_pattern;
79use super::types::{
80    codegen_type_expr, infer_return_type_from_body,
81    codegen_struct_def, codegen_enum_def,
82};
83use super::{escape_rust_ident, is_rust_keyword};
84use super::{
85    collect_c_export_ref_structs, codegen_c_accessors,
86    try_emit_vec_fill_pattern, try_emit_for_range_pattern, try_emit_swap_pattern,
87    try_emit_prefix_reverse,
88    try_emit_seq_copy_pattern, try_emit_seq_from_slice_pattern,
89    try_emit_bare_slice_push_pattern,
90    try_emit_vec_with_capacity_pattern, try_emit_merge_capacity_pattern,
91    try_emit_string_with_capacity_pattern,
92    try_emit_rotate_left_pattern,
93    try_emit_buffer_reuse_while,
94    classify_type_for_c_abi, CAbiClass,
95};
96
97/// Check if a function body contains escape blocks (raw Rust code).
98/// Functions with escape blocks should not have their param types changed
99/// by borrow optimization, since the escape code may depend on specific types.
100pub(crate) fn body_contains_escape(body: &[Stmt]) -> bool {
101    body.iter().any(|stmt| stmt_contains_escape(stmt))
102}
103
104fn stmt_contains_escape(stmt: &Stmt) -> bool {
105    match stmt {
106        Stmt::Escape { .. } => true,
107        Stmt::Let { value, .. } => expr_contains_escape(value),
108        Stmt::Set { value, .. } => expr_contains_escape(value),
109        Stmt::If { then_block, else_block, .. } => {
110            body_contains_escape(then_block)
111                || else_block.as_ref().map_or(false, |eb| body_contains_escape(eb))
112        }
113        Stmt::While { body, .. } | Stmt::Repeat { body, .. } => body_contains_escape(body),
114        Stmt::Inspect { arms, .. } => arms.iter().any(|arm| body_contains_escape(arm.body)),
115        _ => false,
116    }
117}
118
119fn expr_contains_escape(expr: &Expr) -> bool {
120    matches!(expr, Expr::Escape { .. })
121}
122
123/// - Uses `Distributed<T>` when both Mount and Sync detected
124/// - Boxes recursive enum fields
125/// Generates a complete Rust program from LOGOS statements.
126///
127/// This is the main entry point for code generation. It produces a full Rust
128/// program including:
129/// - Prelude imports (`use logicaffeine_data::*;`)
130/// - Type definitions (structs, enums, inductive types)
131/// - Policy structs with capability methods
132/// - Main function with async runtime if needed
133/// - VFS initialization for file operations
134///
135/// # Arguments
136///
137/// * `stmts` - The parsed LOGOS statements to compile
138/// * `registry` - Type definitions discovered during parsing
139/// * `policies` - Policy definitions for access control
140/// * `interner` - Symbol interner for resolving names
141///
142/// # Returns
143///
144/// A complete Rust source code string ready for compilation.
145/// An affine read-only array's declaration (`Let A be a new Seq …` where `A` was
146/// recognized as an affine read-only array). Codegen deletes such arrays — the
147/// build push is suppressed and every read substitutes the closed form — so the
148/// decl is filtered out of the statement stream before peephole dispatch, where
149/// a `vec_with_capacity`/`vec_fill` pattern would otherwise re-materialize it.
150fn is_affine_array_decl(stmt: &Stmt, ctx: &RefinementContext) -> bool {
151    matches!(stmt, Stmt::Let { var, value, .. }
152        if matches!(value, Expr::New { .. }) && ctx.affine_array(*var).is_some())
153}
154
155/// A statement that BUILDS a constant-table local (its `Let` or one of its constant `Push`es). Filtered
156/// out of the body: the table is emitted once as a stack array `[T; N]` at the top of the function, so
157/// its original heap-`Vec` build (`Seq::default()` + `push` × N) is dropped.
158fn is_const_table_stmt(stmt: &Stmt, ctx: &RefinementContext) -> bool {
159    match stmt {
160        Stmt::Let { var, .. } => ctx.const_table(*var).is_some(),
161        Stmt::Push { collection: Expr::Identifier(c), .. } => ctx.const_table(*c).is_some(),
162        _ => false,
163    }
164}
165
166/// Recursively collect every symbol in `stmts` (INCLUDING nested loop / if / match blocks) whose interned
167/// name equals `name`. Needed because the parser mints distinct symbols per occurrence of an identifier,
168/// so a constant-table's `[T; N]` type must be registered for the USE-SITE symbol (e.g. a call argument
169/// inside a loop), not only the `Let`-binding symbol — `variable_types` is symbol-keyed.
170fn collect_named_syms(stmts: &[Stmt], name: &str, interner: &Interner, out: &mut std::collections::HashSet<Symbol>) {
171    for s in stmts {
172        super::worklist::for_each_stmt_expr(s, &mut |e| {
173            super::worklist::visit_idents(e, &mut |sym| {
174                if name == interner.resolve(sym) {
175                    out.insert(sym);
176                }
177            });
178        });
179        match s {
180            Stmt::If { then_block, else_block, .. } => {
181                collect_named_syms(then_block, name, interner, out);
182                if let Some(eb) = else_block {
183                    collect_named_syms(eb, name, interner, out);
184                }
185            }
186            Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
187                collect_named_syms(body, name, interner, out);
188            }
189            Stmt::Inspect { arms, .. } => {
190                for arm in arms {
191                    collect_named_syms(arm.body, name, interner, out);
192                }
193            }
194            _ => {}
195        }
196    }
197}
198
199/// Run i64→i32 narrowing detection (on by default). `LOGOS_NO_NARROW` is the
200/// kill-switch (A/B and debugging), matching the other codegen toggles. Excludes
201/// deleted-affine and worklist sequences. `LOGOS_NARROW_TRACE` reports what
202/// narrowed and why.
203fn narrow_seqs<'a>(
204    body: &'a [Stmt<'a>],
205    de_rc: &std::collections::HashSet<Symbol>,
206    affine: &HashMap<Symbol, super::affine_array::AffineArrayInfo>,
207    worklists: &HashMap<Symbol, super::worklist::WorklistInfo>,
208    interner: &Interner,
209) -> HashMap<Symbol, super::narrow::NarrowInfo> {
210    if !crate::optimize::active_config().is_on(crate::optimization::Opt::Narrow) {
211        return HashMap::new();
212    }
213    let mut n = super::narrow::detect_narrowable(body, de_rc, interner);
214    n.retain(|sym, _| {
215        // A deleted affine array has no plain `Vec` to narrow — Affine preempts Narrow.
216        if affine.contains_key(sym) {
217            crate::optimize::mark_preempted(
218                crate::optimization::Opt::Affine,
219                crate::optimization::Opt::Narrow,
220            );
221            return false;
222        }
223        !worklists.contains_key(sym)
224    });
225    if !n.is_empty() {
226        crate::optimize::mark_fired(crate::optimization::Opt::Narrow);
227    }
228    if std::env::var_os("LOGOS_NARROW_TRACE").is_some() {
229        for (sym, info) in &n {
230            eprintln!(
231                "LOGOS_NARROW: `{}` → Vec<i32>{}",
232                interner.resolve(*sym),
233                if info.guards.is_empty() {
234                    " (static range)".to_string()
235                } else {
236                    format!(" (guards: {})", info.guards.join(" && "))
237                }
238            );
239        }
240    }
241    n
242}
243
244/// Register every function's call-site param-role info into a context's
245/// `variable_types`, packing readonly-borrow (`&[T]`), element-`&mut`-borrow, and
246/// value-semantics `mutable`-collection indices into a single slot per function.
247/// A function may hold several roles (e.g. a readonly `Seq` param plus a `mutable`
248/// param), so the sets are combined rather than written as separate overwriting
249/// strings.
250fn register_fn_roles(
251    ctx: &mut RefinementContext,
252    borrow_params_map: &HashMap<Symbol, HashSet<usize>>,
253    mut_borrow_params_map: &HashMap<Symbol, HashSet<usize>>,
254    value_mutable_params_map: &HashMap<Symbol, HashSet<usize>>,
255) {
256    let mut role_fns: HashSet<Symbol> = HashSet::new();
257    role_fns.extend(borrow_params_map.keys().copied());
258    role_fns.extend(mut_borrow_params_map.keys().copied());
259    role_fns.extend(value_mutable_params_map.keys().copied());
260    let empty = HashSet::new();
261    for fn_sym in role_fns {
262        let b = borrow_params_map.get(&fn_sym).unwrap_or(&empty);
263        let m = mut_borrow_params_map.get(&fn_sym).unwrap_or(&empty);
264        let v = value_mutable_params_map.get(&fn_sym).unwrap_or(&empty);
265        ctx.register_variable_type(fn_sym, super::encode_fn_roles(b, m, v));
266    }
267}
268
269/// The fixed length of every local that codegen emits as a `[T; N]` stack array in `body`, keyed by NAME
270/// (the parser mints distinct symbols per occurrence; a call argument's symbol differs from the decl's).
271/// Computed from the SAME detections codegen uses, so it never over-approximates (a size here ⟹ a real
272/// `[T; N]`), which keeps the derived `&[T; N]` parameter type sound.
273fn scope_array_sizes(
274    body: &[Stmt],
275    all_stmts: &[Stmt],
276    is_main: bool,
277    returns_vec: bool,
278    this_ret: Option<&super::affine_array::ArrayReturnInfo>,
279    borrow_params: &HashMap<Symbol, HashSet<usize>>,
280    mut_borrow_params: &HashMap<Symbol, HashSet<usize>>,
281    vec_return_fns: &HashSet<Symbol>,
282    array_return_fns: &HashMap<Symbol, super::affine_array::ArrayReturnInfo>,
283    interner: &Interner,
284) -> HashMap<String, usize> {
285    let de_rc = collect_de_rc_seqs(body, interner, borrow_params, mut_borrow_params, vec_return_fns, returns_vec);
286    let mut m: HashMap<String, usize> = HashMap::new();
287    let mut put = |sym: Symbol, n: usize| { m.insert(interner.resolve(sym).to_string(), n); };
288    if is_main {
289        // Main scalarizes ONLY via `collect_scalarizable_seqs` (indexed/const O3 walk that disqualifies
290        // call-arg appearances) + straight-line buffers (borrow-aware) + fn-return arrays. The scratch /
291        // indexed / const-table passes are function-body-only, so including them here would over-report.
292        for (v, info) in collect_scalarizable_seqs(body, interner) { put(v, info.len); }
293    } else {
294        for (v, info) in super::affine_array::detect_const_tables(body, all_stmts, &de_rc, borrow_params, interner) { put(v, info.values.len()); }
295        for (v, info) in super::affine_array::detect_scratch_buffers(body, &de_rc, borrow_params, interner) { put(v, info.len); }
296        for (v, info) in super::affine_array::detect_indexed_buffers(body, &de_rc, interner) { put(v, info.len); }
297    }
298    for (v, (_, len)) in super::affine_array::detect_straightline_buffers(body, &de_rc, borrow_params, interner) { put(v, len); }
299    for (v, ty) in super::affine_array::array_var_types(body, this_ret, array_return_fns) {
300        if let Some(n) = ty.strip_prefix('[').and_then(|s| s.rsplit_once("; ")).and_then(|(_, n)| n.strip_suffix(']')).and_then(|n| n.parse::<usize>().ok()) {
301            put(v, n);
302        }
303    }
304    m
305}
306
307/// Record the fixed array length of every read-borrow argument of every `Call` in `e` (recursing into
308/// sub-expressions). `None` = a non-fixed argument at a borrow position (that param can't be `&[T; N]`).
309fn record_call_arg_sizes(
310    e: &Expr,
311    borrow_params: &HashMap<Symbol, HashSet<usize>>,
312    sizes: &HashMap<String, usize>,
313    interner: &Interner,
314    agg: &mut HashMap<(Symbol, usize), Option<usize>>,
315) {
316    match e {
317        Expr::Call { function, args } => {
318            if let Some(bset) = borrow_params.get(function) {
319                for (i, a) in args.iter().enumerate() {
320                    if bset.contains(&i) {
321                        let val = if let Expr::Identifier(v) = a { sizes.get(interner.resolve(*v)).copied() } else { None };
322                        agg.entry((*function, i)).and_modify(|cur| { if *cur != val { *cur = None; } }).or_insert(val);
323                    }
324                }
325            }
326            for a in args {
327                record_call_arg_sizes(a, borrow_params, sizes, interner, agg);
328            }
329        }
330        Expr::BinaryOp { left, right, .. } | Expr::Union { left, right } | Expr::Intersection { left, right } | Expr::Range { start: left, end: right } => {
331            record_call_arg_sizes(left, borrow_params, sizes, interner, agg);
332            record_call_arg_sizes(right, borrow_params, sizes, interner, agg);
333        }
334        Expr::Index { collection, index } => {
335            record_call_arg_sizes(collection, borrow_params, sizes, interner, agg);
336            record_call_arg_sizes(index, borrow_params, sizes, interner, agg);
337        }
338        Expr::Not { operand } => record_call_arg_sizes(operand, borrow_params, sizes, interner, agg),
339        Expr::Length { collection } | Expr::Copy { expr: collection } | Expr::Give { value: collection } | Expr::OptionSome { value: collection } | Expr::FieldAccess { object: collection, .. } => {
340            record_call_arg_sizes(collection, borrow_params, sizes, interner, agg)
341        }
342        Expr::CallExpr { callee, args } => {
343            record_call_arg_sizes(callee, borrow_params, sizes, interner, agg);
344            for a in args {
345                record_call_arg_sizes(a, borrow_params, sizes, interner, agg);
346            }
347        }
348        Expr::List(items) | Expr::Tuple(items) => {
349            for it in items {
350                record_call_arg_sizes(it, borrow_params, sizes, interner, agg);
351            }
352        }
353        _ => {}
354    }
355}
356
357fn walk_call_sizes<'a>(
358    scope: &[Stmt<'a>],
359    borrow_params: &HashMap<Symbol, HashSet<usize>>,
360    sizes: &HashMap<String, usize>,
361    interner: &Interner,
362    agg: &mut HashMap<(Symbol, usize), Option<usize>>,
363) {
364    for s in scope {
365        super::worklist::for_each_stmt_expr(s, &mut |e| record_call_arg_sizes(e, borrow_params, sizes, interner, agg));
366        match s {
367            Stmt::If { then_block, else_block, .. } => {
368                walk_call_sizes(then_block, borrow_params, sizes, interner, agg);
369                if let Some(eb) = else_block {
370                    walk_call_sizes(eb, borrow_params, sizes, interner, agg);
371                }
372            }
373            Stmt::While { body, .. } | Stmt::Repeat { body, .. } => walk_call_sizes(body, borrow_params, sizes, interner, agg),
374            Stmt::Inspect { arms, .. } => {
375                for arm in arms {
376                    walk_call_sizes(arm.body, borrow_params, sizes, interner, agg);
377                }
378            }
379            _ => {}
380        }
381    }
382}
383
384/// Fixed-array borrow-parameter propagation: `(fn, param_idx) → N` when EVERY call site passes a fixed
385/// `[T; N]` array of the same N at that read-borrow position. The parameter then becomes `&[T; N]`, so
386/// LLVM elides the constant-index bounds checks. Value-safe: `&arr` coerces to either form.
387fn fixed_array_params(
388    stmts: &[Stmt],
389    borrow_params: &HashMap<Symbol, HashSet<usize>>,
390    mut_borrow_params: &HashMap<Symbol, HashSet<usize>>,
391    vec_return_fns: &HashSet<Symbol>,
392    array_return_fns: &HashMap<Symbol, super::affine_array::ArrayReturnInfo>,
393    interner: &Interner,
394) -> HashMap<(Symbol, usize), usize> {
395    if !crate::optimize::active_config().is_on(crate::optimization::Opt::Scalarize) {
396        return HashMap::new();
397    }
398    let mut agg: HashMap<(Symbol, usize), Option<usize>> = HashMap::new();
399    let main_sizes = scope_array_sizes(stmts, stmts, true, false, None, borrow_params, mut_borrow_params, vec_return_fns, array_return_fns, interner);
400    walk_call_sizes(stmts, borrow_params, &main_sizes, interner, &mut agg);
401    for s in stmts {
402        if let Stmt::FunctionDef { name, body, is_native: false, .. } = s {
403            let rv = vec_return_fns.contains(name);
404            let sizes = scope_array_sizes(body, stmts, false, rv, array_return_fns.get(name), borrow_params, mut_borrow_params, vec_return_fns, array_return_fns, interner);
405            walk_call_sizes(body, borrow_params, &sizes, interner, &mut agg);
406        }
407    }
408    agg.into_iter().filter_map(|(k, v)| v.map(|n| (k, n))).collect()
409}
410
411pub fn codegen_program(stmts: &[Stmt], registry: &TypeRegistry, policies: &PolicyRegistry, interner: &Interner, type_env: &crate::analysis::types::TypeEnv, cfg: &OptimizationConfig) -> String {
412    codegen_program_with_proven(stmts, registry, policies, interner, type_env, cfg, "proven", None)
413}
414
415/// Like [`codegen_program`], but bundles an extracted math/logic module into the
416/// output. `proven` is the body of a Rust module — functions, `check_*` property
417/// fns, a `World`/`holds` model-checker — produced by the Forge's extraction with
418/// NO `fn main`. It is emitted as `pub mod <module_name> { … }` right after the
419/// prelude (before `user_types`), followed by `use <module_name>::*;` so a bare
420/// call in the imperative program below — e.g. `double(21)` — resolves into it.
421/// Naming the module (rather than dumping items at crate root) keeps multiple
422/// proven modules reachable and avoids polluting the imperative namespace. When
423/// `proven` is `None`/blank the output is byte-identical to [`codegen_program`].
424pub fn codegen_program_with_proven(stmts: &[Stmt], registry: &TypeRegistry, policies: &PolicyRegistry, interner: &Interner, type_env: &crate::analysis::types::TypeEnv, cfg: &OptimizationConfig, module_name: &str, proven: Option<&str>) -> String {
425    codegen_program_inner(stmts, registry, policies, interner, type_env, cfg, module_name, proven, None).0
426}
427
428/// Like [`codegen_program`], but also builds the rustc→LOGOS [`SourceMap`]:
429/// every generated line maps to the top-level statement that produced it
430/// (`stmt_spans` is the parser's side-table, 1:1 with `stmts`; zero-width
431/// spans mark prelude statements and are skipped), and variable origins carry
432/// ownership roles for the diagnostic bridge. This is the flycheck substrate.
433pub fn codegen_program_mapped(
434    stmts: &[Stmt],
435    registry: &TypeRegistry,
436    policies: &PolicyRegistry,
437    interner: &Interner,
438    type_env: &crate::analysis::types::TypeEnv,
439    cfg: &OptimizationConfig,
440    stmt_spans: &[LogosSpan],
441    logos_source: &str,
442) -> (String, SourceMap) {
443    let (code, map) = codegen_program_inner(
444        stmts, registry, policies, interner, type_env, cfg, "proven", None,
445        Some((stmt_spans, logos_source)),
446    );
447    (code, map.expect("mapping was requested"))
448}
449
450#[allow(clippy::too_many_arguments)]
451fn codegen_program_inner(stmts: &[Stmt], registry: &TypeRegistry, policies: &PolicyRegistry, interner: &Interner, type_env: &crate::analysis::types::TypeEnv, cfg: &OptimizationConfig, module_name: &str, proven: Option<&str>, mapping: Option<(&[LogosSpan], &str)>) -> (String, Option<SourceMap>) {
452    crate::optimize::set_active_config(*cfg);
453    let mut output = String::new();
454    // (rust line, LOGOS span) records; materialized into the SourceMap at the
455    // end. `line_count(&output)` before/after an emission brackets its lines.
456    let mut line_records: Vec<(u32, LogosSpan)> = Vec::new();
457
458    // Prelude
459    // Use extracted crates instead of logos_core
460    writeln!(output, "#[allow(unused_imports)]").unwrap();
461    writeln!(output, "use std::fmt::Write as _;").unwrap();
462    writeln!(output, "use logicaffeine_data::*;").unwrap();
463    writeln!(output, "use logicaffeine_system::*;\n").unwrap();
464    // Population-count intrinsic — emitted ONLY when the program calls it (so
465    // unrelated codegen is byte-identical). A free function so every codegen
466    // path renders `count_ones(x)` as a plain call; two's-complement faithful,
467    // matching the tree-walker/VM builtin (optimize::popcount_leaf inserts it).
468    if program_uses_count_ones(stmts, interner) {
469        writeln!(
470            output,
471            "#[inline(always)]\nfn count_ones(x: i64) -> i64 {{ (x as u64).count_ones() as i64 }}\n"
472        )
473        .unwrap();
474    }
475    // SIMD substring-count kernel — emitted ONLY when the program contains a
476    // recognized naive-search nest (unrelated codegen stays byte-identical). The
477    // recognizer (peephole::try_emit_naive_search) lowers the nest to a call
478    // into this kernel; the generated binary cannot link the compiler, so the
479    // kernel travels with it, like the emitted `fn args()` wrapper.
480    if super::peephole::stmts_contain_naive_search(stmts, interner) {
481        writeln!(output, "{}", super::strsearch::RUNTIME_SRC).unwrap();
482    }
483
484    // FFI: Emit wasm_bindgen preamble if any function is exported for WASM
485    if has_wasm_exports(stmts, interner) {
486        writeln!(output, "use wasm_bindgen::prelude::*;\n").unwrap();
487    }
488
489    // FFI: Emit CStr/CString imports if any C export uses Text types
490    if has_c_exports_with_text(stmts, interner) {
491        writeln!(output, "use std::ffi::{{CStr, CString}};\n").unwrap();
492    }
493
494    // Universal ABI: Emit LogosStatus runtime preamble if any C exports exist
495    let c_exports_exist = has_c_exports(stmts, interner);
496    if c_exports_exist {
497        output.push_str(&codegen_logos_runtime_preamble());
498    }
499
500    // Bundled proven module: the extracted math/logic objects (functions, `check_*`
501    // property fns, `World`/`holds`) the imperative program calls into. Named so
502    // multiple proven modules stay reachable; `use super::*;` gives it the crate
503    // prelude and `use <name>::*;` brings its public items into imperative scope so
504    // a bare `double(21)` resolves into it. Emitted only when the mixed compile
505    // supplies it — otherwise the output is byte-identical to the bare imperative path.
506    if let Some(proven) = proven {
507        if !proven.trim().is_empty() {
508            writeln!(output, "pub mod {module_name} {{").unwrap();
509            writeln!(output, "    #![allow(dead_code, unused, non_snake_case)]").unwrap();
510            writeln!(output, "    use super::*;").unwrap();
511            output.push_str(proven);
512            if !proven.ends_with('\n') {
513                writeln!(output).unwrap();
514            }
515            writeln!(output, "}}").unwrap();
516            writeln!(output, "use {module_name}::*;\n").unwrap();
517        }
518    }
519
520    // Phase 49: Collect CRDT register fields for special SetField handling
521    // LWW fields need timestamp, MV fields don't
522    let (lww_fields, mv_fields) = collect_crdt_register_fields(registry, interner);
523
524    // Phase 54: Collect async functions for Launch codegen
525    let async_functions = collect_async_functions(stmts);
526
527    // Purity analysis for memoization
528    let pure_functions = collect_pure_functions(stmts);
529
530    // Phase 54: Collect pipe declarations (variables with _tx/_rx suffixes)
531    let main_pipe_vars = collect_pipe_vars(stmts);
532
533    // Phase 102: Collect boxed fields for recursive enum handling
534    let boxed_fields = collect_boxed_fields(registry, interner);
535
536    // Collect value-type struct names used in C exports (need #[repr(C)])
537    let c_abi_value_structs: HashSet<Symbol> = if c_exports_exist {
538        collect_c_export_value_type_structs(stmts, interner, registry)
539    } else {
540        HashSet::new()
541    };
542
543    // Collect reference-type struct names used in C exports (need serde derives for from_json/to_json)
544    let c_abi_ref_structs: HashSet<Symbol> = if c_exports_exist {
545        collect_c_export_ref_structs(stmts, interner, registry)
546    } else {
547        HashSet::new()
548    };
549
550    // Collect user-defined structs from registry (Phase 34: generics, Phase 47: is_portable, Phase 49: is_shared)
551    let mut structs: Vec<_> = registry.iter_types()
552        .filter_map(|(name, def)| {
553            if let TypeDef::Struct { fields, generics, is_portable, is_shared } = def {
554                if !fields.is_empty() || !generics.is_empty() {
555                    Some((*name, fields.clone(), generics.clone(), *is_portable, *is_shared))
556                } else {
557                    None
558                }
559            } else {
560                None
561            }
562        })
563        .collect();
564    // DETERMINISM: `iter_types` walks a HashMap (random per-run order). Sort by
565    // name so the emitted Rust is byte-identical across recompiles (Rust resolves
566    // top-level types order-independently, so this can't affect correctness).
567    structs.sort_by(|a, b| interner.resolve(a.0).cmp(interner.resolve(b.0)));
568
569    // Phase 33/34: Collect user-defined enums from registry (generics, Phase 47: is_portable, Phase 49: is_shared)
570    let mut enums: Vec<_> = registry.iter_types()
571        .filter_map(|(name, def)| {
572            if let TypeDef::Enum { variants, generics, is_portable, is_shared } = def {
573                if !variants.is_empty() || !generics.is_empty() {
574                    Some((*name, variants.clone(), generics.clone(), *is_portable, *is_shared))
575                } else {
576                    None
577                }
578            } else {
579                None
580            }
581        })
582        .collect();
583    enums.sort_by(|a, b| interner.resolve(a.0).cmp(interner.resolve(b.0)));
584
585    // Emit struct and enum definitions in user_types module if any exist
586    if !structs.is_empty() || !enums.is_empty() {
587        writeln!(output, "pub mod user_types {{").unwrap();
588        writeln!(output, "    use super::*;\n").unwrap();
589
590        for (name, fields, generics, is_portable, is_shared) in &structs {
591            output.push_str(&codegen_struct_def(*name, fields, generics, *is_portable, *is_shared, interner, 4, &c_abi_value_structs, &c_abi_ref_structs));
592        }
593
594        for (name, variants, generics, is_portable, is_shared) in &enums {
595            output.push_str(&codegen_enum_def(*name, variants, generics, *is_portable, *is_shared, interner, 4));
596        }
597
598        writeln!(output, "}}\n").unwrap();
599        writeln!(output, "use user_types::*;\n").unwrap();
600    }
601
602    // Phase 50: Generate policy impl blocks with predicate and capability methods
603    output.push_str(&codegen_policy_impls(policies, interner));
604
605    // Mutual TCO: Detect pairs of mutually tail-calling functions
606    let mutual_tce_pairs = detect_mutual_tce_pairs(stmts, interner);
607    let mut mutual_tce_members: HashSet<Symbol> = HashSet::new();
608    for (a, b) in &mutual_tce_pairs {
609        mutual_tce_members.insert(*a);
610        mutual_tce_members.insert(*b);
611    }
612    let mut mutual_tce_emitted: HashSet<Symbol> = HashSet::new();
613
614    // Pre-pass: Build borrow_params_map — identifies which function params
615    // can be borrowed as &[T] instead of owned Vec<T>.
616    // Uses whole-program transitive readonly analysis (ReadonlyParams) instead of
617    // local-body-only detection so that params passed to mutating callees are
618    // correctly excluded from borrow optimization.
619    let callgraph = CallGraph::build(stmts, interner);
620    let readonly_params = ReadonlyParams::analyze(stmts, &callgraph, type_env);
621
622    let mut borrow_params_map: HashMap<Symbol, HashSet<usize>> = HashMap::new();
623    for stmt in stmts {
624        if let Stmt::FunctionDef { name, params, body, is_native, is_exported, opt_flags, .. } = stmt {
625            // Native wrappers pass each Seq param straight to the underlying kernel (which takes
626            // `&[i64]`), reading but never mutating it — so every Seq param is readonly-borrowed.
627            // This makes the whole native call path zero-copy: a caller's `&[i64]` Seq param flows
628            // through the wrapper to the kernel with no `LogosSeq` materialization.
629            if *is_native {
630                if crate::optimize::active_config().merged(opt_flags).is_on(Opt::Borrow) {
631                    let indices: HashSet<usize> = params.iter().enumerate()
632                        .filter(|(_, (_, param_type))| is_vec_type_expr(param_type, interner))
633                        .map(|(i, _)| i)
634                        .collect();
635                    if !indices.is_empty() {
636                        borrow_params_map.insert(*name, indices);
637                    }
638                }
639                continue;
640            }
641            // Skip exported, TCE, accumulator, and mutual TCE functions
642            if *is_exported || mutual_tce_members.contains(name) {
643                continue;
644            }
645            // Respect ## No Borrow / ## No Optimize annotations
646            if !crate::optimize::active_config().merged(opt_flags).is_on(Opt::Borrow) {
647                continue;
648            }
649            // Only DIRECT tail recursion conflicts with a borrowed param (TCE
650            // reassigns it). A `Set/Let x = self(args); Return x` pair can keep
651            // the borrow — pair-TCE yields to it in the `is_tce` gate below.
652            if is_tail_recursive(*name, body) {
653                continue;
654            }
655            if detect_accumulator_pattern(*name, body).is_some() {
656                continue;
657            }
658            // Skip functions with escape blocks — raw Rust code may assume specific param types
659            if body_contains_escape(body) {
660                continue;
661            }
662            let indices: HashSet<usize> = params.iter().enumerate()
663                .filter(|(_, (sym, param_type))| {
664                    readonly_params.is_readonly(*name, *sym)
665                        && is_vec_type_expr(param_type, interner)
666                })
667                .map(|(i, _)| i)
668                .collect();
669            if !indices.is_empty() {
670                let give_indices = collect_give_arg_indices(*name, stmts);
671                let filtered: HashSet<usize> = indices.difference(&give_indices).copied().collect();
672                if !filtered.is_empty() {
673                    borrow_params_map.insert(*name, filtered);
674                    crate::optimize::mark_fired(Opt::Borrow);
675                }
676            }
677        }
678    }
679
680    // Mutable borrow analysis: detect Seq params that are only mutated via SetIndex
681    // (element-only, no Push/Pop) and returned. These get &mut [T] instead of owned Vec<T>.
682    let mutable_borrow_params = MutableBorrowParams::analyze(stmts, &callgraph, type_env);
683
684    let mut mut_borrow_params_map: HashMap<Symbol, HashSet<usize>> = HashMap::new();
685    for stmt in stmts {
686        if let Stmt::FunctionDef { name, params, body, is_native, is_exported, opt_flags, .. } = stmt {
687            if *is_native || *is_exported || mutual_tce_members.contains(name) {
688                continue;
689            }
690            // Respect ## No Borrow / ## No Optimize annotations
691            if !crate::optimize::active_config().merged(opt_flags).is_on(Opt::Borrow) {
692                continue;
693            }
694            if is_tail_recursive(*name, body) || detect_accumulator_pattern(*name, body).is_some() {
695                continue;
696            }
697            if body_contains_escape(body) {
698                continue;
699            }
700            // Skip if already has readonly borrow params (readonly takes precedence)
701            let readonly_indices = borrow_params_map.get(name).cloned().unwrap_or_default();
702            let indices: HashSet<usize> = params.iter().enumerate()
703                .filter(|(i, (sym, param_type))| {
704                    // Under value semantics this in-place lowering stays EXACT:
705                    // every call site is the consuming `Set x to f(x, ...)` shape
706                    // (enforced by `collect_incompatible_mut_borrow_callsites`)
707                    // and the call site `cow()`s the handle first, so the
708                    // callee's element writes land on a buffer no other live
709                    // handle can observe.
710                    mutable_borrow_params.is_mutable_borrow(*name, *sym)
711                        && !readonly_indices.contains(i)
712                        && is_vec_type_expr(param_type, interner)
713                })
714                .map(|(i, _)| i)
715                .collect();
716            if !indices.is_empty() {
717                mut_borrow_params_map.insert(*name, indices);
718                crate::optimize::mark_fired(Opt::Borrow);
719            }
720        }
721    }
722
723    // `mutable` collection params (value semantics): passed by shared `&LogosSeq`/
724    // `&LogosMap` so the callee's in-place mutation reaches the caller. Distinct
725    // from the `&mut [T]` borrow opt (which has no `.push()`). Skip functions
726    // already using the readonly borrow opt (mixed readonly+mutable is rare and
727    // would clobber the single-slot call-site encoding).
728    let mut value_mutable_params_map: HashMap<Symbol, HashSet<usize>> = HashMap::new();
729    if crate::semantics::collections::value_semantics_enabled() {
730        for stmt in stmts {
731            if let Stmt::FunctionDef { name, params, is_native: false, .. } = stmt {
732                let idx: HashSet<usize> = params.iter().enumerate()
733                    .filter(|(_, (_, ty))| {
734                        matches!(ty, crate::ast::stmt::TypeExpr::Mutable { .. })
735                    })
736                    .map(|(i, _)| i)
737                    .collect();
738                if !idx.is_empty() {
739                    value_mutable_params_map.insert(*name, idx);
740                }
741            }
742        }
743    }
744
745    // Pass 2: Compute liveness for all user-defined functions (backward dataflow).
746    // Used by codegen_function_def to enable last-use move optimization (OPT-1C).
747    let liveness = LivenessResult::analyze(stmts);
748
749    // De-Rc Phase 4: functions whose every Return is a uniquely-owned fresh Seq
750    // return `Vec<T>` instead of `LogosSeq<T>` — removing the per-call Rc
751    // clone/borrow and unblocking de-Rc on the locals that capture the result
752    // (`Set left to mergeSort(left)`). The mergesort allocation keystone.
753    let mut vec_return_fns = collect_vec_return_fns(stmts, interner, &borrow_params_map, &mut_borrow_params_map);
754    // Step 3b: functions returning a fixed-size buffer return `[T; N]` by value (a stack array). Disjoint
755    // from vec-return (the array is the stricter, zero-heap representation) — exclude them from vec-return
756    // so the array path is authoritative.
757    let array_return_fns = super::affine_array::collect_array_return_fns(stmts, &borrow_params_map, interner);
758    vec_return_fns.retain(|f| !array_return_fns.contains_key(f));
759    // Fixed-array borrow-parameter propagation: a read-borrow param passed only fixed `[T; N]` arrays
760    // becomes `&[T; N]` (LLVM elides its constant-index bounds checks).
761    let fixed_array_param_map = fixed_array_params(stmts, &borrow_params_map, &mut_borrow_params_map, &vec_return_fns, &array_return_fns, interner);
762
763    // Build function return type map for variable type inference at call sites.
764    // Phase 4: a return-type-de-Rc'd function returns `Vec<T>`, so callers infer
765    // the result var as `Vec` (not `LogosSeq`) — keeping its uses Rc-free. Step 3b:
766    // an array-return function's callers infer the result var as `[T; N]`.
767    let mut fn_returns_map: HashMap<Symbol, String> = stmts.iter().filter_map(|s| {
768        if let Stmt::FunctionDef { name, return_type: Some(rt), .. } = s {
769            let ty = codegen_type_expr(rt, interner);
770            let ty = if let Some(info) = array_return_fns.get(name) {
771                format!("[{}; {}]", info.elem_ty, info.len)
772            } else if vec_return_fns.contains(name) {
773                ty.replacen("LogosSeq<", "Vec<", 1)
774            } else {
775                ty
776            };
777            Some((*name, ty))
778        } else {
779            None
780        }
781    }).collect();
782
783    // Overflow-promoting return: the whole-program set of `Int`-returning functions whose value can
784    // exceed i64 (a running product/power, or a call to another such function). Their signature
785    // becomes `-> LogosInt`, and a caller's binding of the result is itself promotable. Record the
786    // `|__bigint` sentinel so a call result classifies as Int (exact-arith path) yet stores LogosInt.
787    let bigint_fns = super::bigint_promote::bigint_returning_fns(stmts, interner);
788    for f in &bigint_fns {
789        fn_returns_map.insert(*f, "i64|__bigint".to_string());
790    }
791    // Record the bignum-returning set for the expression codegen, so an INLINE call to such a
792    // function is detected as a promoted `LogosInt` operand and routed through the exact helper
793    // (a bare `LogosInt <op> LogosInt` has no operator impl — see `mentions_bigint_var`).
794    super::expr::set_bigint_returning_fns(&bigint_fns);
795
796    // O1 borrow hoisting: analyze the oracle ONCE on this exact statement
797    // slice (loop alias snapshots are keyed by Stmt address; codegen walks
798    // the same Stmts, so the pointers match). One forward pass with bounded
799    // per-loop fixpoints; gated for very large programs and by LOGOS_HOIST=0.
800    const MAX_HOIST_ORACLE_STMTS: usize = 5_000;
801    let oracle: Option<std::rc::Rc<crate::optimize::OracleFacts>> =
802        if super::hoist::hoisting_disabled() || stmts.len() > MAX_HOIST_ORACLE_STMTS {
803            None
804        } else {
805            Some(std::rc::Rc::new(crate::optimize::oracle_analyze_with_entry_guards(stmts, interner)))
806        };
807
808    // Phase 32/38: Emit function definitions before main
809    for (top_idx, stmt) in stmts.iter().enumerate() {
810        let fn_emit_start = line_count(&output) + 1;
811        if let Stmt::FunctionDef { name, params, generics, body, return_type, is_native, native_path, is_exported, export_target, opt_flags } = stmt {
812            if mutual_tce_members.contains(name) {
813                // Part of a mutual pair — emit merged function when we see the first member
814                if !mutual_tce_emitted.contains(name) {
815                    // Find the pair this function belongs to
816                    if let Some((a, b)) = mutual_tce_pairs.iter().find(|(a, b)| *a == *name || *b == *name) {
817                        output.push_str(&codegen_mutual_tce_pair(*a, *b, stmts, interner, &lww_fields, &mv_fields, &async_functions, &boxed_fields, registry, type_env));
818                        mutual_tce_emitted.insert(*a);
819                        mutual_tce_emitted.insert(*b);
820                    }
821                }
822                // Skip individual emission — already emitted as part of merged pair
823            } else {
824                output.push_str(&codegen_function_def(*name, generics, params, body, stmts, return_type.as_ref().copied(), *is_native, *native_path, *is_exported, *export_target, interner, &lww_fields, &mv_fields, &async_functions, &boxed_fields, registry, &pure_functions, type_env, &borrow_params_map, &mut_borrow_params_map, &value_mutable_params_map, &liveness, opt_flags, &fn_returns_map, &vec_return_fns, &array_return_fns, &fixed_array_param_map, oracle.as_ref(), &bigint_fns));
825            }
826            if let Some((spans, _)) = mapping {
827                record_emitted_lines(&output, fn_emit_start, spans.get(top_idx).copied(), &mut line_records);
828            }
829        }
830    }
831
832    // Universal ABI: Emit accessor/free functions for reference types in C exports
833    if c_exports_exist {
834        let ref_types = collect_c_export_reference_types(stmts, interner, registry);
835        for ref_ty in &ref_types {
836            output.push_str(&codegen_c_accessors(ref_ty, interner, registry));
837        }
838    }
839
840    // Grand Challenge: Collect variables that need to be mutable
841    let main_stmts: Vec<&Stmt> = stmts.iter()
842        .filter(|s| !matches!(s, Stmt::FunctionDef { .. }))
843        .collect();
844    let mut main_mutable_vars = HashSet::new();
845    for stmt in &main_stmts {
846        collect_mutable_vars_stmt(stmt, &mut main_mutable_vars);
847    }
848
849    // OPT: Detect single-char text variables (emit u8 instead of String)
850    let single_char_vars = collect_single_char_text_vars(stmts, interner);
851
852    // Main function
853    // Phase 51: Use async main when async operations are present
854    if requires_async(stmts) {
855        writeln!(output, "#[tokio::main]").unwrap();
856        writeln!(output, "async fn main() {{").unwrap();
857    } else {
858        writeln!(output, "fn main() {{").unwrap();
859        writeln!(output, "    std::thread::Builder::new()").unwrap();
860        writeln!(output, "        .stack_size(67_108_864)").unwrap();
861        writeln!(output, "        .spawn(_logos_main)").unwrap();
862        writeln!(output, "        .unwrap().join().unwrap();").unwrap();
863        writeln!(output, "}}").unwrap();
864        writeln!(output, "fn _logos_main() {{").unwrap();
865    }
866    // Phase 53: Inject VFS when file operations or persistence is used
867    if requires_vfs(stmts) {
868        writeln!(output, "    let vfs: std::sync::Arc<dyn logicaffeine_system::fs::Vfs + Send + Sync> = std::sync::Arc::from(logicaffeine_system::fs::get_platform_vfs());").unwrap();
869    }
870    let mut main_ctx = RefinementContext::from_type_env(type_env);
871    // O1 borrow hoisting: seed the oracle (pointer-keyed loop alias
872    // snapshots must match the Stmts codegen walks).
873    if let Some(o) = &oracle {
874        main_ctx.set_oracle(o.clone());
875    }
876    // Local Vec optimization: detect which collection vars escape main
877    let main_escaping = collect_escaping_collection_vars(stmts, interner);
878    main_ctx.set_escaping_vars(main_escaping);
879    // Overflow-promoting Int bindings: which integer vars must store `LogosInt`.
880    main_ctx.set_promotable_candidates(crate::codegen::bigint_promote::promotable_int_vars(stmts, &bigint_fns));
881    // OPT: Register single-char text vars as u8 type for optimized codegen
882    for sym in &single_char_vars {
883        main_ctx.register_variable_type(*sym, "__single_char_u8".to_string());
884    }
885    // O3: scalarize fixed-size, non-escaping Seqs to `[T; N]` arrays (Main only).
886    let scalarizable_seqs = collect_scalarizable_seqs(stmts, interner);
887    // AoS interleaving: fuse co-indexed same-type groups (round-robin pushes)
888    // into one `[[T; W]; N]` backing array reusing the first member's symbol, so
889    // per-entity fields are memory-adjacent (C's struct-array layout) and LLVM
890    // packs them instead of gathering separate arrays with shuffles. Members are
891    // registered as AoS columns INSTEAD of plain `[T; N]` scalarized arrays.
892    let aos_groups = collect_interleaved_groups(stmts, &scalarizable_seqs, interner);
893    let aos_names = RustNames::new(interner);
894    let mut aos_member_syms: HashSet<Symbol> = HashSet::new();
895    for group in &aos_groups {
896        let width = group.members.len();
897        // The fused backing array reuses the first member's emitted identifier.
898        // RustNames is a deterministic resolve+sanitize, so this matches the name
899        // every access site produces for that symbol.
900        let backing = aos_names.ident(group.members[0]);
901        for (col, m) in group.members.iter().enumerate() {
902            main_ctx.register_variable_type(
903                *m,
904                format!("__aos:{}:{}:{}:{}:{}", backing, col, width, group.len, group.elem_ty),
905            );
906            main_ctx.init_array_fill(*m);
907            aos_member_syms.insert(*m);
908        }
909    }
910    for (sym, info) in &scalarizable_seqs {
911        if aos_member_syms.contains(sym) {
912            continue; // handled as an AoS column above
913        }
914        main_ctx.register_variable_type(*sym, format!("[{}; {}]", info.elem_ty, info.len));
915        main_ctx.init_array_fill(*sym);
916    }
917    // Step 3b: register the `[T; N]` type for Main's result variables bound to an array-return fn (the
918    // per-function registration in `codegen_function_def` does not cover the top-level Main body).
919    for (sym, ty) in super::affine_array::array_var_types(stmts, None, &array_return_fns) {
920        let name = interner.resolve(sym).to_string();
921        let mut syms: HashSet<Symbol> = HashSet::new();
922        collect_named_syms(stmts, &name, interner, &mut syms);
923        for s in syms {
924            main_ctx.register_variable_type(s, ty.clone());
925        }
926    }
927    // O2 de-Rc: Seqs proven to never need reference semantics → plain Vec<T>
928    // (no Rc/RefCell). A var already scalarized to `[T; N]` (O3) wins — exclude it.
929    let mut de_rc_seqs = collect_de_rc_seqs(stmts, interner, &borrow_params_map, &mut_borrow_params_map, &vec_return_fns, false);
930    for (sym, _) in &scalarizable_seqs {
931        // A `[T; N]`-scalarized var already gets the win — it preempts de-Rc here.
932        if de_rc_seqs.remove(sym) {
933            crate::optimize::mark_preempted(Opt::Scalarize, Opt::Unbox);
934        }
935    }
936    // Straight-line-push fixed buffers in Main → `[T; K]` (borrow-aware — the scalarizer disqualifies any
937    // call-arg, even a read-only borrow, so Main's small input buffers stayed heap Vecs). The O3 `Let`/
938    // `Push` handlers emit the stack array + `buf[k]=expr` fills.
939    let main_sl = super::affine_array::detect_straightline_buffers(stmts, &de_rc_seqs, &borrow_params_map, interner);
940    for (sym, (elem_ty, len)) in &main_sl {
941        let ty = format!("[{}; {}]", elem_ty, len);
942        de_rc_seqs.remove(sym);
943        let name = interner.resolve(*sym).to_string();
944        let mut syms: HashSet<Symbol> = HashSet::new();
945        collect_named_syms(stmts, &name, interner, &mut syms);
946        for s in syms {
947            main_ctx.register_variable_type(s, ty.clone());
948        }
949    }
950    // Append-only worklist → pre-sized buffer + register tail (BFS frontier).
951    let main_worklists = super::worklist::detect_worklists(stmts, &de_rc_seqs, interner);
952    // Affine read-only array → delete it, substitute the closed form at reads
953    // (CSR offset array `adjStarts[v] == v*5` becomes C's `v*5` shift). An O3
954    // scalarized (`[T;N]`) or worklist symbol is already claimed — exclude it.
955    let mut main_affine = super::affine_array::detect_affine_arrays(stmts, &de_rc_seqs, interner);
956    main_affine.retain(|sym, _| {
957        // A scalarized sequence is already claimed — Scalarize preempts Affine.
958        if scalarizable_seqs.contains_key(sym) {
959            crate::optimize::mark_preempted(Opt::Scalarize, Opt::Affine);
960            return false;
961        }
962        !main_worklists.contains_key(sym)
963    });
964    for (sym, info) in &main_affine {
965        main_ctx.register_variable_type(
966            *sym,
967            format!("__affine_array:{}:{}:{}", info.coeff, info.offset, info.trip),
968        );
969    }
970    // i64→i32 element-width narrowing (gated by LOGOS_NARROW, default off): a
971    // `Seq of Int` whose every value provably fits i32 is stored as `Vec<i32>`.
972    // Exclude deleted (affine) and worklist sequences — they are not plain Vecs.
973    let main_narrowed = narrow_seqs(stmts, &de_rc_seqs, &main_affine, &main_worklists, interner);
974    for sym in main_narrowed.keys() {
975        // Register the i32 element type so the borrow-hoist and indexed-read
976        // dispatch treat the buffer as `&[i32]`/`Vec<i32>`; the decl paths emit
977        // the matching `Vec<i32>` and the access sites convert.
978        main_ctx.register_variable_type(*sym, "Vec<i32>".to_string());
979    }
980    main_ctx.set_de_rc_vars(de_rc_seqs);
981    main_ctx.set_worklists(main_worklists);
982    main_ctx.set_affine_arrays(main_affine);
983    main_ctx.set_narrowed(main_narrowed);
984    // Non-aliased local `Map of Int to Int` → specialized `LogosI64Map`, or the
985    // keys-only `LogosI64Set` when the value is never read.
986    let main_i64 = super::i64_map::detect_i64_maps(stmts, interner);
987    // Dense tier: maps whose key domain the oracle proved bounded within their
988    // capacity hint lower to a direct-addressed flat array (no hashing/probing).
989    if let Some(o) = oracle.as_deref() {
990        let dense = super::i64_map::detect_dense_i64_maps(&main_i64, o, interner);
991        let (i32m, i32s) = super::i64_map::detect_i32_maps(&main_i64, &dense, o);
992        main_ctx.set_dense_i64(dense);
993        main_ctx.set_i32_maps(i32m, i32s);
994    }
995    main_ctx.set_i64_sets(main_i64.sets);
996    main_ctx.set_i64_maps(main_i64.maps);
997    // Pre-size push-built Vecs that a later counted loop index-reads to a bound.
998    // A deleted affine array must not also be pre-sized (its declaration is gone).
999    let mut main_presize = super::peephole::detect_vec_presize(stmts, interner);
1000    main_presize.retain(|sym, _| main_ctx.affine_array(*sym).is_none());
1001    main_ctx.set_vec_presize(main_presize);
1002    // Loop-invariant positive divisors → precomputed `LogosDivU64` magic multiply.
1003    main_ctx.set_fast_div(super::fast_div::detect_fast_div(stmts, oracle.as_deref(), interner));
1004    // Register function param-role info on main context for call-site lowering:
1005    // readonly borrow (`&[T]`), element-mutating borrow (`&mut [T]`), and
1006    // value-semantics `mutable` collection (`&LogosSeq`/`&LogosMap`). A function
1007    // can play several at once, so all its roles pack into ONE slot (else the last
1008    // registered would clobber the rest and every call site mis-lower the others).
1009    register_fn_roles(&mut main_ctx, &borrow_params_map, &mut_borrow_params_map, &value_mutable_params_map);
1010    // Register function return types for variable type inference at call sites
1011    for (fn_sym, rt_str) in &fn_returns_map {
1012        main_ctx.register_fn_return(*fn_sym, rt_str.clone());
1013    }
1014    let mut main_synced_vars = HashSet::new();  // Phase 52: Track synced variables in main
1015    // Phase 56: Pre-scan for Mount+Sync combinations
1016    let main_var_caps = analyze_variable_capabilities(stmts, interner);
1017    {
1018        let stmt_refs: Vec<&Stmt> = stmts.iter().collect();
1019        let mut i = 0;
1020        while i < stmt_refs.len() {
1021            // Skip function definitions - they're already emitted above
1022            if matches!(stmt_refs[i], Stmt::FunctionDef { .. }) {
1023                i += 1;
1024                continue;
1025            }
1026            // An affine read-only array is deleted entirely — emit no declaration.
1027            // (Its build push is suppressed and every read substitutes the closed
1028            // form.) Skip the decl here, before any peephole pattern can
1029            // re-materialize it as a pre-sized/with_capacity Vec.
1030            if let Stmt::Let { var, value, .. } = stmt_refs[i] {
1031                if matches!(value, Expr::New { .. }) && main_ctx.affine_array(*var).is_some() {
1032                    i += 1;
1033                    continue;
1034                }
1035            }
1036            // Statement-sequence peepholes (naive-search, cascade fold, presize,
1037            // for-range, swap, …) — one shared chain for every block context.
1038            if let Some((code, skip)) = super::peephole::try_block_peepholes(&stmt_refs, i, interner, 1, &main_mutable_vars, &mut main_ctx, &lww_fields, &mv_fields, &mut main_synced_vars, &main_var_caps, &async_functions, &main_pipe_vars, &boxed_fields, registry, type_env) {
1039                let emit_start = line_count(&output) + 1;
1040                output.push_str(&code);
1041                if let Some((spans, _)) = mapping {
1042                    // A peephole fuses statements i..=i+skip: map its lines to
1043                    // the merged span of everything it consumed.
1044                    let merged = match (spans.get(i), spans.get(i + skip)) {
1045                        (Some(a), Some(b)) if a.start < b.end => Some(LogosSpan::new(a.start, b.end)),
1046                        (Some(a), _) => Some(*a),
1047                        _ => None,
1048                    };
1049                    record_emitted_lines(&output, emit_start, merged, &mut line_records);
1050                }
1051                i += 1 + skip;
1052                continue;
1053            }
1054            let emit_start = line_count(&output) + 1;
1055            output.push_str(&codegen_stmt(stmt_refs[i], interner, 1, &main_mutable_vars, &mut main_ctx, &lww_fields, &mv_fields, &mut main_synced_vars, &main_var_caps, &async_functions, &main_pipe_vars, &boxed_fields, registry, type_env));
1056            if let Some((spans, _)) = mapping {
1057                record_emitted_lines(&output, emit_start, spans.get(i).copied(), &mut line_records);
1058            }
1059            i += 1;
1060        }
1061    }
1062    writeln!(output, "}}").unwrap();
1063
1064    let map = mapping.map(|(spans, logos_source)| {
1065        let mut builder = SourceMapBuilder::new(logos_source);
1066        for (line, span) in line_records {
1067            builder.record_line_at(line, span);
1068        }
1069        record_var_origins(stmts, spans, interner, &mut builder);
1070        builder.build()
1071    });
1072    (output, map)
1073}
1074
1075/// Newlines emitted so far — brackets each statement's generated line range.
1076fn line_count(s: &str) -> u32 {
1077    s.bytes().filter(|&b| b == b'\n').count() as u32
1078}
1079
1080/// Map every line an emission produced (`start..=` the current line) to the
1081/// statement's span. Zero-width spans are prelude sentinels — skipped, so
1082/// prelude-generated code never claims user-source positions.
1083fn record_emitted_lines(
1084    output: &str,
1085    start_line: u32,
1086    span: Option<LogosSpan>,
1087    records: &mut Vec<(u32, LogosSpan)>,
1088) {
1089    let Some(span) = span else { return };
1090    if span.start >= span.end {
1091        return;
1092    }
1093    let mut end_line = line_count(output);
1094    if !output.ends_with('\n') {
1095        end_line += 1;
1096    }
1097    for line in start_line..=end_line.max(start_line) {
1098        records.push((line, span));
1099    }
1100}
1101
1102/// Ownership-role priority: when one variable plays several roles, the move
1103/// is what a borrow-check error will be about.
1104fn role_rank(role: OwnershipRole) -> u8 {
1105    match role {
1106        // The move itself: E0382 phrasing hangs off it.
1107        OwnershipRole::GiveObject => 7,
1108        // Zone membership is structural — E0597 escape phrasing needs it even
1109        // when the variable is also shown or set inside the zone.
1110        OwnershipRole::ZoneLocal => 6,
1111        OwnershipRole::ShowObject => 5,
1112        OwnershipRole::SetTarget => 4,
1113        OwnershipRole::GiveRecipient => 3,
1114        OwnershipRole::ShowRecipient => 2,
1115        OwnershipRole::LetBinding => 1,
1116    }
1117}
1118
1119/// Walk the program recording each variable's Rust name → LOGOS origin.
1120/// Nested statements attribute to their containing top-level statement's
1121/// span; higher-priority roles win when a variable plays several.
1122fn record_var_origins(
1123    stmts: &[Stmt],
1124    stmt_spans: &[LogosSpan],
1125    interner: &Interner,
1126    builder: &mut SourceMapBuilder,
1127) {
1128    use std::collections::HashMap as Map;
1129    let mut best: Map<String, (u8, Symbol, LogosSpan, OwnershipRole)> = Map::new();
1130
1131    fn ident_of(expr: &Expr, interner: &Interner) -> Option<(String, Symbol)> {
1132        if let Expr::Identifier(sym) = expr {
1133            Some((super::escape_rust_ident(interner.resolve(*sym)), *sym))
1134        } else {
1135            None
1136        }
1137    }
1138
1139    fn note(
1140        best: &mut Map<String, (u8, Symbol, LogosSpan, OwnershipRole)>,
1141        name: String,
1142        sym: Symbol,
1143        span: LogosSpan,
1144        role: OwnershipRole,
1145    ) {
1146        let rank = role_rank(role);
1147        match best.get(&name) {
1148            Some((existing, ..)) if *existing >= rank => {}
1149            _ => {
1150                best.insert(name, (rank, sym, span, role));
1151            }
1152        }
1153    }
1154
1155    fn walk(
1156        stmts: &[Stmt],
1157        span: LogosSpan,
1158        in_zone: bool,
1159        interner: &Interner,
1160        best: &mut Map<String, (u8, Symbol, LogosSpan, OwnershipRole)>,
1161    ) {
1162        for stmt in stmts {
1163            match stmt {
1164                Stmt::Let { var, .. } => {
1165                    let role = if in_zone { OwnershipRole::ZoneLocal } else { OwnershipRole::LetBinding };
1166                    let name = super::escape_rust_ident(interner.resolve(*var));
1167                    note(best, name, *var, span, role);
1168                }
1169                Stmt::Set { target, .. } => {
1170                    let name = super::escape_rust_ident(interner.resolve(*target));
1171                    note(best, name, *target, span, OwnershipRole::SetTarget);
1172                }
1173                Stmt::Give { object, recipient } => {
1174                    if let Some((name, sym)) = ident_of(object, interner) {
1175                        note(best, name, sym, span, OwnershipRole::GiveObject);
1176                    }
1177                    if let Some((name, sym)) = ident_of(recipient, interner) {
1178                        note(best, name, sym, span, OwnershipRole::GiveRecipient);
1179                    }
1180                }
1181                Stmt::Show { object, recipient } => {
1182                    if let Some((name, sym)) = ident_of(object, interner) {
1183                        note(best, name, sym, span, OwnershipRole::ShowObject);
1184                    }
1185                    if let Some((name, sym)) = ident_of(recipient, interner) {
1186                        note(best, name, sym, span, OwnershipRole::ShowRecipient);
1187                    }
1188                }
1189                Stmt::Zone { body, .. } => walk(body, span, true, interner, best),
1190                Stmt::If { then_block, else_block, .. } => {
1191                    walk(then_block, span, in_zone, interner, best);
1192                    if let Some(else_block) = else_block {
1193                        walk(else_block, span, in_zone, interner, best);
1194                    }
1195                }
1196                Stmt::While { body, .. } => walk(body, span, in_zone, interner, best),
1197                Stmt::Repeat { body, .. } => walk(body, span, in_zone, interner, best),
1198                Stmt::FunctionDef { body, .. } => walk(body, span, in_zone, interner, best),
1199                _ => {}
1200            }
1201        }
1202    }
1203
1204    for (i, stmt) in stmts.iter().enumerate() {
1205        let Some(span) = stmt_spans.get(i).copied() else { continue };
1206        if span.start >= span.end {
1207            continue;
1208        }
1209        walk(std::slice::from_ref(stmt), span, false, interner, &mut best);
1210    }
1211
1212    for (name, (_, sym, span, role)) in best {
1213        builder.record_var(&name, sym, span, role);
1214    }
1215}
1216
1217/// Phase 32/38: Generate a function definition.
1218/// Phase 38: Updated for native functions and TypeExpr types.
1219/// Phase 49: Accepts lww_fields for LWWRegister SetField handling.
1220/// Phase 103: Accepts registry for polymorphic enum type inference.
1221fn codegen_function_def(
1222    name: Symbol,
1223    generics: &[Symbol],
1224    params: &[(Symbol, &TypeExpr)],
1225    body: &[Stmt],
1226    all_stmts: &[Stmt],
1227    return_type: Option<&TypeExpr>,
1228    is_native: bool,
1229    native_path: Option<Symbol>,
1230    is_exported: bool,
1231    export_target: Option<Symbol>,
1232    interner: &Interner,
1233    lww_fields: &HashSet<(String, String)>,
1234    mv_fields: &HashSet<(String, String)>,  // Phase 49b: MVRegister fields
1235    async_functions: &HashSet<Symbol>,  // Phase 54
1236    boxed_fields: &HashSet<(String, String, String)>,  // Phase 102
1237    registry: &TypeRegistry,  // Phase 103
1238    pure_functions: &HashSet<Symbol>,
1239    type_env: &crate::analysis::types::TypeEnv,
1240    borrow_params_map: &HashMap<Symbol, HashSet<usize>>,
1241    mut_borrow_params_map: &HashMap<Symbol, HashSet<usize>>,
1242    value_mutable_params_map: &HashMap<Symbol, HashSet<usize>>,
1243    liveness: &LivenessResult,
1244    opt_flags: &OptimizationConfig,
1245    fn_returns_map: &HashMap<Symbol, String>,
1246    vec_return_fns: &HashSet<Symbol>,
1247    array_return_fns: &HashMap<Symbol, super::affine_array::ArrayReturnInfo>,
1248    fixed_array_param_map: &HashMap<(Symbol, usize), usize>,
1249    oracle: Option<&std::rc::Rc<crate::optimize::OracleFacts>>,
1250    bigint_fns: &HashSet<Symbol>,
1251) -> String {
1252    let mut output = String::new();
1253    // Overflow-promoting return: this `Int`-returning function's value can exceed i64, so it is
1254    // typed `-> LogosInt` and its return value is not narrowed.
1255    let returns_bigint = bigint_fns.contains(&name);
1256    // De-Rc Phase 4: this function returns an owned `Vec<T>` (every Return is a
1257    // uniquely-owned fresh Seq) instead of `LogosSeq<T>`.
1258    let returns_vec = vec_return_fns.contains(&name);
1259    // Step 3b: this function returns a fixed-size stack array `[T; N]` by value.
1260    let array_return = array_return_fns.get(&name);
1261    let names = RustNames::new(interner);
1262    let raw_name = names.raw(name);
1263    let func_name = names.ident(name);
1264    let export_target_lower = export_target.map(|s| interner.resolve(s).to_lowercase());
1265
1266    // Phase 54: Detect which parameters are used as pipe senders
1267    let pipe_sender_params = collect_pipe_sender_params(body);
1268
1269    // FFI: Exported functions need special signatures
1270    let is_c_export_early = is_exported && matches!(export_target_lower.as_deref(), None | Some("c"));
1271
1272    // TCE: Detect tail recursion eligibility (respects ## No TCO / ## No Optimize)
1273    let no_tco = !crate::optimize::active_config().merged(opt_flags).is_on(Opt::Tco);
1274    // A direct `Return self(args)` always TCE's. The `Set/Let x = self(args);
1275    // Return x` pair TCE's too — UNLESS the function is borrow/mut-borrow-eligible
1276    // (an in-place array recursion like quicksort), where that rewrite is the
1277    // better lowering and pair-TCE would force an owned, cloned parameter. Such
1278    // functions recurse only O(log n) deep, so the constant-stack guarantee is
1279    // moot for them anyway.
1280    let has_tail_pair = body_has_top_level_tail_pair(name, body, params.len());
1281    // A borrow/mut-borrow-eligible function keeps the borrow rather than pair-TCE —
1282    // Borrow preempts Tco (pair) for it.
1283    if has_tail_pair
1284        && (borrow_params_map.contains_key(&name) || mut_borrow_params_map.contains_key(&name))
1285    {
1286        crate::optimize::mark_preempted(Opt::Borrow, Opt::Tco);
1287    }
1288    let pair_tce_ok = has_tail_pair
1289        && !borrow_params_map.contains_key(&name)
1290        && !mut_borrow_params_map.contains_key(&name);
1291    let is_tce = !is_native && !is_c_export_early && !no_tco
1292        && (is_tail_recursive(name, body) || pair_tce_ok);
1293    if is_tce {
1294        crate::optimize::mark_fired(Opt::Tco);
1295    }
1296    let param_syms: Vec<Symbol> = params.iter().map(|(s, _)| *s).collect();
1297
1298    // Accumulator Introduction: Detect non-tail single-call + / * patterns
1299    let acc_info = if !is_tce && !is_native && !is_c_export_early {
1300        detect_accumulator_pattern(name, body)
1301    } else {
1302        None
1303    };
1304    let is_acc = acc_info.is_some();
1305
1306    // Closed-form: Detect double recursion f(0)=base, f(d)=k+f(d-1)+f(d-1)
1307    // Emits ((base+k) << d) - k instead of memoization — matches GCC/LLVM -O2.
1308    let closed_form_info = if !is_tce && !is_acc && !is_native && !is_c_export_early {
1309        detect_double_recursion_closed_form(name, params, body, interner)
1310    } else {
1311        None
1312    };
1313    let is_closed_form = closed_form_info.is_some();
1314
1315    // Memoization: Detect pure multi-call recursive functions with hashable params
1316    // Respects ## No Memo / ## No Optimize annotations
1317    let no_memo = !crate::optimize::active_config().merged(opt_flags).is_on(Opt::Memo);
1318    let is_memo = !is_tce && !is_acc && !is_closed_form && !is_native && !is_c_export_early && !no_memo
1319        && should_memoize(name, body, params, return_type, pure_functions.contains(&name), interner);
1320    if is_memo {
1321        crate::optimize::mark_fired(Opt::Memo);
1322    }
1323    // A tail-recursive function that WOULD otherwise memoize is claimed by TCE
1324    // instead — Tco preempts Memo (eval `should_memoize` only here, where `is_memo`
1325    // short-circuited it via `!is_tce`, so it is computed at most once).
1326    if is_tce
1327        && !no_memo
1328        && should_memoize(name, body, params, return_type, pure_functions.contains(&name), interner)
1329    {
1330        crate::optimize::mark_preempted(Opt::Tco, Opt::Memo);
1331    }
1332
1333    let needs_mut_params = is_tce || is_acc;
1334
1335    // Peephole: respect ## No Peephole / ## No Optimize annotations
1336    let no_peephole = !crate::optimize::active_config().merged(opt_flags).is_on(Opt::Peephole);
1337
1338    // Get borrow indices for this function (empty set if none)
1339    let borrow_indices = borrow_params_map.get(&name).cloned().unwrap_or_default();
1340
1341    // Get mutable borrow indices (element-only mutation, return same param)
1342    let mut_borrow_indices = mut_borrow_params_map.get(&name).cloned().unwrap_or_default();
1343
1344    // Compute mutable vars early for param mutability detection
1345    let func_mutable_vars = collect_mutable_vars(body);
1346
1347    // Build parameter list using TypeExpr
1348    let params_str: Vec<String> = params.iter().enumerate()
1349        .map(|(i, (param_name, param_type))| {
1350            let pname = names.ident(*param_name);
1351            let ty = codegen_type_expr(param_type, interner);
1352            // `mutable` collection param (value semantics) → shared `&LogosSeq`/
1353            // `&LogosMap`: push/set take &self, so mutations reach the caller's
1354            // allocation in place (the by-reference escape hatch).
1355            if crate::semantics::collections::value_semantics_enabled()
1356                && matches!(param_type, crate::ast::stmt::TypeExpr::Mutable { .. })
1357            {
1358                format!("{}: &{}", pname, ty)
1359            } else if pipe_sender_params.contains(param_name) {
1360                format!("{}: tokio::sync::mpsc::Sender<{}>", pname, ty)
1361            } else if borrow_indices.contains(&i) {
1362                // Read-only Vec param → borrow as `&[T]`, or `&[T; N]` when every caller passes a fixed
1363                // `[T; N]` array (LLVM then elides the constant-index bounds checks).
1364                let slice_ty = vec_to_slice_type(&ty);
1365                let final_ty = match fixed_array_param_map.get(&(name, i)) {
1366                    Some(n) => slice_ty.strip_prefix("&[").and_then(|s| s.strip_suffix(']'))
1367                        .map(|elem| format!("&[{}; {}]", elem, n)).unwrap_or(slice_ty),
1368                    None => slice_ty,
1369                };
1370                format!("{}: {}", pname, final_ty)
1371            } else if mut_borrow_indices.contains(&i) {
1372                // Element-only mutation Vec param → borrow as &mut [T]
1373                let slice_ty = vec_to_mut_slice_type(&ty);
1374                format!("{}: {}", pname, slice_ty)
1375            } else if needs_mut_params || func_mutable_vars.contains(param_name) {
1376                format!("mut {}: {}", pname, ty)
1377            } else {
1378                format!("{}: {}", pname, ty)
1379            }
1380        })
1381        .collect();
1382
1383    // Get return type string from TypeExpr or infer from body
1384    // If this function has &mut borrow params and returns one of them,
1385    // suppress the return type since the mutation happens in-place.
1386    let has_mut_borrow = !mut_borrow_indices.is_empty();
1387    let return_type_str = if returns_bigint {
1388        // Overflow-promoting: an `Int` return whose value can exceed i64 is a `LogosInt`.
1389        Some("logicaffeine_data::LogosInt".to_string())
1390    } else if has_mut_borrow {
1391        None // No return type — mutation is in-place via &mut [T]
1392    } else if let Some(info) = array_return {
1393        // Step 3b: `LogosSeq<T>` → a fixed-size stack array `[T; N]` returned by value.
1394        Some(format!("[{}; {}]", info.elem_ty, info.len))
1395    } else if returns_vec {
1396        // Phase 4: `LogosSeq<T>` → owned `Vec<T>`. Replace the `LogosSeq<` head
1397        // of the codegen'd type (`LogosSeq<i64>` → `Vec<i64>`).
1398        return_type
1399            .map(|t| codegen_type_expr(t, interner).replacen("LogosSeq<", "Vec<", 1))
1400    } else {
1401        return_type
1402            .map(|t| codegen_type_expr(t, interner))
1403            .or_else(|| infer_return_type_from_body(body, params, interner))
1404    };
1405
1406    // Phase 51/54: Check if function is async (includes transitive async detection)
1407    let is_async = async_functions.contains(&name);
1408    let fn_keyword = if is_async { "async fn" } else { "fn" };
1409
1410    // FFI: Exported functions need special signatures
1411    let is_c_export = is_c_export_early;
1412
1413    // FFI: Check if C export needs type marshaling
1414    // Triggers for: Text params/return, reference types, Result return, refinement params
1415    let needs_c_marshaling = is_c_export && {
1416        let has_text_param = params.iter().any(|(_, ty)| is_text_type(ty, interner));
1417        let has_text_return = return_type.map_or(false, |ty| is_text_type(ty, interner));
1418        let has_ref_param = params.iter().any(|(_, ty)| {
1419            classify_type_for_c_abi(ty, interner, registry) == CAbiClass::ReferenceType
1420        });
1421        let has_ref_return = return_type.map_or(false, |ty| {
1422            classify_type_for_c_abi(ty, interner, registry) == CAbiClass::ReferenceType
1423        });
1424        let has_result_return = return_type.map_or(false, |ty| is_result_type(ty, interner));
1425        let has_refinement_param = params.iter().any(|(_, ty)| {
1426            matches!(ty, TypeExpr::Refinement { .. })
1427        });
1428        // Char crosses the C ABI as uint32_t; route through the marshal path so
1429        // the wrapper validates it via char::from_u32 instead of exposing a raw
1430        // Rust `char` (an out-of-range u32 from C is UB).
1431        let has_char_param = params.iter().any(|(_, ty)| is_char_type(ty, interner));
1432        let has_char_return = return_type.map_or(false, |ty| is_char_type(ty, interner));
1433        has_text_param || has_text_return || has_ref_param || has_ref_return
1434            || has_result_return || has_refinement_param || has_char_param || has_char_return
1435    };
1436
1437    if needs_c_marshaling {
1438        // Generate two-function pattern: inner function + C ABI wrapper
1439        return codegen_c_export_with_marshaling(
1440            name, params, body, return_type, interner,
1441            lww_fields, mv_fields, async_functions, boxed_fields, registry, type_env,
1442        );
1443    }
1444
1445    // Build function signature
1446    let (vis_prefix, abi_prefix) = if is_exported {
1447        match export_target_lower.as_deref() {
1448            None | Some("c") => ("pub ", "extern \"C\" "),
1449            Some("wasm") => ("pub ", ""),
1450            _ => ("pub ", ""),
1451        }
1452    } else {
1453        ("", "")
1454    };
1455
1456    // Generic functions: "fn identity<T>" — resolve each generic Symbol to its name
1457    let generics_str = if generics.is_empty() {
1458        String::new()
1459    } else {
1460        let params_list: Vec<&str> = generics.iter()
1461            .map(|sym| interner.resolve(*sym))
1462            .collect();
1463        format!("<{}>", params_list.join(", "))
1464    };
1465
1466    let signature = if let Some(ref ret_ty) = return_type_str {
1467        if ret_ty != "()" {
1468            format!("{}{}{} {}{}({}) -> {}", vis_prefix, abi_prefix, fn_keyword, func_name, generics_str, params_str.join(", "), ret_ty)
1469        } else {
1470            format!("{}{}{} {}{}({})", vis_prefix, abi_prefix, fn_keyword, func_name, generics_str, params_str.join(", "))
1471        }
1472    } else {
1473        format!("{}{}{} {}{}({})", vis_prefix, abi_prefix, fn_keyword, func_name, generics_str, params_str.join(", "))
1474    };
1475
1476    // Emit #[inline] for small non-recursive, non-exported functions
1477    // Closed-form functions are tiny (2 lines) and should always inline.
1478    if is_closed_form || (!is_tce && !is_acc && should_inline(name, body, is_native, is_exported, is_async)) {
1479        writeln!(output, "#[inline]").unwrap();
1480    }
1481
1482    // FFI: Emit export attributes before the function
1483    if is_exported {
1484        match export_target_lower.as_deref() {
1485            None | Some("c") => {
1486                writeln!(output, "#[export_name = \"logos_{}\"]", raw_name).unwrap();
1487            }
1488            Some("wasm") => {
1489                writeln!(output, "#[wasm_bindgen]").unwrap();
1490            }
1491            _ => {}
1492        }
1493    }
1494
1495    // Phase 38: Handle native functions
1496    if is_native {
1497        let arg_names: Vec<&str> = params.iter()
1498            .map(|(n, _)| interner.resolve(*n))
1499            .collect();
1500
1501        if let Some(path_sym) = native_path {
1502            // User-defined native path: call the Rust path directly
1503            let path = interner.resolve(path_sym);
1504            // Validate path looks like a valid Rust path (identifiers separated by ::)
1505            let is_valid_path = !path.is_empty() && path.split("::").all(|seg| {
1506                !seg.is_empty() && seg.chars().all(|c| c.is_alphanumeric() || c == '_')
1507            });
1508            if is_valid_path {
1509                writeln!(output, "{} {{", signature).unwrap();
1510                writeln!(output, "    {}({})", path, arg_names.join(", ")).unwrap();
1511                writeln!(output, "}}\n").unwrap();
1512            } else {
1513                writeln!(output, "{} {{", signature).unwrap();
1514                writeln!(output, "    compile_error!(\"Invalid native function path: '{}'. Path must be a valid Rust path like \\\"crate::module::function\\\".\")", path).unwrap();
1515                writeln!(output, "}}\n").unwrap();
1516            }
1517        } else {
1518            // Legacy system functions: use map_native_function()
1519            if let Some((module, core_fn)) = map_native_function(raw_name) {
1520                writeln!(output, "{} {{", signature).unwrap();
1521                writeln!(output, "    logicaffeine_system::{}::{}({})", module, core_fn, arg_names.join(", ")).unwrap();
1522                writeln!(output, "}}\n").unwrap();
1523            } else {
1524                writeln!(output, "{} {{", signature).unwrap();
1525                writeln!(output, "    compile_error!(\"Unknown system native function: '{}'. Use `is native \\\"crate::path\\\"` syntax for user-defined native functions.\")", raw_name).unwrap();
1526                writeln!(output, "}}\n").unwrap();
1527            }
1528        }
1529    } else {
1530        // Non-native: emit body (also used for exported functions which have bodies)
1531        writeln!(output, "{} {{", signature).unwrap();
1532
1533        // Entry precondition guard (BCE for recursive 1-based partitions): make
1534        // the function's `1 <= lo` and `hi <= len` precondition explicit so LLVM
1535        // drops the per-access bounds checks across the hot partition loop.
1536        // Gated on `lo < hi` (the indexing path) so it never fires for the
1537        // base-case range; only emitted for pure (no-I/O) functions, so the
1538        // abort is equivalent to the out-of-range access it pre-empts. Emitted
1539        // even for a tail-call-eliminated partition (value semantics makes the
1540        // `Set result to qs(...)` threading TCE-able): the assert runs on the
1541        // INITIAL params before the loop (sound), and every in-loop access keeps
1542        // its own oracle `assert_unchecked` hint.
1543        if !is_acc {
1544            if let Some(g) = super::entry_guard::detect_entry_guard(params, body, interner) {
1545                let arr = names.ident(g.arr);
1546                let lo = names.ident(g.lo);
1547                let hi = names.ident(g.hi);
1548                writeln!(
1549                    output,
1550                    "    if ({lo}) < ({hi}) {{ let __{arr}_glen = {arr}.len() as i64; assert!(({lo}) >= 1 && ({hi}) <= __{arr}_glen, \"LOGOS precondition guard: 1-based index range\"); }}"
1551                )
1552                .unwrap();
1553            }
1554        }
1555
1556        // Wrap exported C functions in catch_unwind for panic safety
1557        let wrap_catch_unwind = is_c_export;
1558        if wrap_catch_unwind {
1559            writeln!(output, "    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {{").unwrap();
1560        }
1561
1562        let mut func_ctx = RefinementContext::new();
1563        // O1 borrow hoisting: same oracle, keyed by the loop Stmts in this
1564        // body (sub-slice of the program codegen analyzed).
1565        if let Some(o) = oracle {
1566            func_ctx.set_oracle(o.clone());
1567        }
1568        // Local Vec optimization: detect which collection vars escape this function
1569        let func_escaping = collect_escaping_collection_vars(body, interner);
1570        func_ctx.set_escaping_vars(func_escaping);
1571        // Overflow-promoting Int bindings in this function body (a call to a bignum function counts
1572        // as a bignum-producing RHS). A promoted variable that is RETURNED makes this function
1573        // `-> LogosInt` (`returns_bigint`, computed by the whole-program fixpoint).
1574        func_ctx.set_promotable_candidates(crate::codegen::bigint_promote::promotable_int_vars(body, bigint_fns));
1575        // O2 de-Rc: function-LOCAL Seqs that never need reference semantics →
1576        // plain Vec<T>. The use-scan disqualifies any returned/escaping/aliased
1577        // handle, so only genuinely-local buffers (sieve flags, scratch arrays)
1578        // de-Rc. Parameters are not candidates (Phase 3 handles those).
1579        let func_de_rc = collect_de_rc_seqs(body, interner, borrow_params_map, mut_borrow_params_map, vec_return_fns, returns_vec);
1580        let func_worklists = super::worklist::detect_worklists(body, &func_de_rc, interner);
1581        let mut func_affine = super::affine_array::detect_affine_arrays(body, &func_de_rc, interner);
1582        func_affine.retain(|sym, _| !func_worklists.contains_key(sym));
1583        // Constant-table locals → stack arrays `[T; N]` (zero heap, direct index). Detected while
1584        // `func_de_rc` is still borrowable; excluded from affine/narrow so no other pass re-claims them,
1585        // and registered with the `[T; N]` type so the Index codegen emits a direct array read.
1586        let func_const_tables = super::affine_array::detect_const_tables(body, all_stmts, &func_de_rc, borrow_params_map, interner);
1587        // Fixed-size, non-escaping scratch buffers → in-place `[T; N]` via from_fn (disjoint from constant
1588        // tables: those have constant values and hoist; these have per-iteration values and stay in place).
1589        let mut func_scratch = super::affine_array::detect_scratch_buffers(body, &func_de_rc, borrow_params_map, interner);
1590        func_scratch.retain(|sym, _| !func_const_tables.contains_key(sym));
1591        // Zero-init + indexed-write fixed-size buffers → `[T; N]` stack arrays. Disjoint from scratch/const
1592        // (those are push-built read-only; these are indexed-written).
1593        let mut func_indexed = super::affine_array::detect_indexed_buffers(body, &func_de_rc, interner);
1594        func_indexed.retain(|sym, _| !func_const_tables.contains_key(sym) && !func_scratch.contains_key(sym));
1595        func_scratch.retain(|sym, _| !func_indexed.contains_key(sym));
1596        // Straight-line-push fixed buffers → `[T; K]` (borrow-aware; the O3 `Let`/`Push` handlers emit the
1597        // stack array + `buf[k]=expr` fills). Disjoint from the fixed-size passes above.
1598        let mut func_sl = super::affine_array::detect_straightline_buffers(body, &func_de_rc, borrow_params_map, interner);
1599        func_sl.retain(|sym, _| !func_const_tables.contains_key(sym) && !func_scratch.contains_key(sym) && !func_indexed.contains_key(sym));
1600        func_affine.retain(|sym, _| !func_const_tables.contains_key(sym) && !func_scratch.contains_key(sym));
1601        for (sym, info) in &func_affine {
1602            func_ctx.register_variable_type(
1603                *sym,
1604                format!("__affine_array:{}:{}:{}", info.coeff, info.offset, info.trip),
1605            );
1606        }
1607        let mut func_narrowed = narrow_seqs(body, &func_de_rc, &func_affine, &func_worklists, interner);
1608        func_narrowed.retain(|sym, _| !func_const_tables.contains_key(sym) && !func_scratch.contains_key(sym) && !func_indexed.contains_key(sym) && !func_sl.contains_key(sym));
1609        for sym in func_narrowed.keys() {
1610            func_ctx.register_variable_type(*sym, "Vec<i32>".to_string());
1611        }
1612        func_ctx.set_de_rc_vars(func_de_rc);
1613        func_ctx.set_worklists(func_worklists);
1614        func_ctx.set_affine_arrays(func_affine);
1615        func_ctx.set_narrowed(func_narrowed);
1616        let func_i64 = super::i64_map::detect_i64_maps(body, interner);
1617        if let Some(o) = oracle {
1618            let dense = super::i64_map::detect_dense_i64_maps(&func_i64, o.as_ref(), interner);
1619            let (i32m, i32s) = super::i64_map::detect_i32_maps(&func_i64, &dense, o.as_ref());
1620            func_ctx.set_dense_i64(dense);
1621            func_ctx.set_i32_maps(i32m, i32s);
1622        }
1623        func_ctx.set_i64_sets(func_i64.sets);
1624        func_ctx.set_i64_maps(func_i64.maps);
1625        let mut func_presize = super::peephole::detect_vec_presize(body, interner);
1626        func_presize.retain(|sym, _| func_ctx.affine_array(*sym).is_none() && !func_const_tables.contains_key(sym) && !func_scratch.contains_key(sym) && !func_indexed.contains_key(sym) && !func_sl.contains_key(sym));
1627        func_ctx.set_vec_presize(func_presize);
1628        func_ctx.set_fast_div(super::fast_div::detect_fast_div(body, oracle.map(|o| o.as_ref()), interner));
1629        func_ctx.set_returns_vec(returns_vec);
1630        func_ctx.set_returns_bigint(returns_bigint);
1631        // OPT: Detect single-char text vars in function body
1632        let func_single_char_vars = collect_single_char_text_vars(body, interner);
1633        for sym in &func_single_char_vars {
1634            func_ctx.register_variable_type(*sym, "__single_char_u8".to_string());
1635        }
1636        let mut func_synced_vars = HashSet::new();  // Phase 52: Track synced variables in function
1637        // Phase 56: Pre-scan for Mount+Sync combinations in function body
1638        let func_var_caps = analyze_variable_capabilities(body, interner);
1639
1640        // Phase 50: Register parameter types for capability Check resolution
1641        // Borrow-optimized params get &[T] or &mut [T] type so downstream codegen handles them correctly
1642        let value_mutable_indices = value_mutable_params_map.get(&name).cloned().unwrap_or_default();
1643        for (i, (param_name, param_type)) in params.iter().enumerate() {
1644            let type_name = codegen_type_expr(param_type, interner);
1645            if borrow_indices.contains(&i) {
1646                let slice_ty = vec_to_slice_type(&type_name);
1647                let reg_ty = match fixed_array_param_map.get(&(name, i)) {
1648                    Some(n) => slice_ty.strip_prefix("&[").and_then(|s| s.strip_suffix(']'))
1649                        .map(|elem| format!("&[{}; {}]", elem, n)).unwrap_or(slice_ty),
1650                    None => slice_ty,
1651                };
1652                func_ctx.register_variable_type(*param_name, reg_ty);
1653            } else if mut_borrow_indices.contains(&i) {
1654                func_ctx.register_variable_type(*param_name, vec_to_mut_slice_type(&type_name));
1655            } else {
1656                func_ctx.register_variable_type(*param_name, type_name);
1657            }
1658            // A `mutable` collection param is a shared `&LogosSeq`/`&LogosMap`:
1659            // its mutations must reach the caller in place, so it is exempt from
1660            // copy-on-write (and cannot call the `&mut self` `cow()` regardless).
1661            if value_mutable_indices.contains(&i) {
1662                func_ctx.register_mutable_collection_param(*param_name);
1663            }
1664        }
1665
1666        // Register function param-role info on func context for call-site lowering
1667        // (readonly `&[T]` / element `&mut [T]` / value-semantics `mutable`). All of
1668        // a function's roles pack into one slot — see the main-context registration.
1669        register_fn_roles(&mut func_ctx, borrow_params_map, mut_borrow_params_map, value_mutable_params_map);
1670        // Register function return types for variable type inference at call sites
1671        for (fn_sym, rt_str) in fn_returns_map {
1672            func_ctx.register_fn_return(*fn_sym, rt_str.clone());
1673        }
1674
1675        // Phase 54: Functions receive pipe senders as parameters, no local pipe declarations
1676        let func_pipe_vars = HashSet::new();
1677
1678        // Emit the constant-table stack arrays once at the top of the body (before every emission path);
1679        // their original heap-`Vec` `Let`/`Push` build is filtered out (`is_const_table_stmt`), and reads
1680        // hit the `[T; N]` array via the registered type. Zero-alloc constant tables — MD5's shift words.
1681        for (sym, info) in &func_const_tables {
1682            writeln!(
1683                output,
1684                "    let {}: [{}; {}] = [{}];",
1685                names.ident(*sym),
1686                info.elem_ty,
1687                info.values.len(),
1688                info.values.join(", ")
1689            )
1690            .unwrap();
1691            // Register the `[T; N]` type for EVERY body symbol resolving to this table's name — the
1692            // parser mints distinct symbols for the same identifier in different positions (a bare call
1693            // argument vs the `Let` binding), and `variable_types` is symbol-keyed, so a single `Let`-
1694            // symbol registration would miss at a use site. Registered LAST so it also wins over any
1695            // fn-return inference of the binding. (Const-table names are unique in the function body.)
1696            let ty = format!("[{}; {}]", info.elem_ty, info.values.len());
1697            let name = interner.resolve(*sym).to_string();
1698            let mut syms: HashSet<Symbol> = HashSet::new();
1699            collect_named_syms(body, &name, interner, &mut syms);
1700            for s in syms {
1701                func_ctx.register_variable_type(s, ty.clone());
1702            }
1703        }
1704        func_ctx.set_const_tables(func_const_tables);
1705
1706        // Register the `[T; N]` type for every use-site symbol of each scratch buffer (same reason as the
1707        // constant tables above: `variable_types` is symbol-keyed and the parser mints distinct symbols
1708        // per occurrence). The fill loop is emitted in place as `from_fn` by the `Stmt::Repeat` handler;
1709        // only the `new Seq` DECL is filtered (the from_fn becomes the binding).
1710        for (sym, info) in &func_scratch {
1711            let ty = format!("[{}; {}]", info.elem_ty, info.len);
1712            let name = interner.resolve(*sym).to_string();
1713            let mut syms: HashSet<Symbol> = HashSet::new();
1714            collect_named_syms(body, &name, interner, &mut syms);
1715            for s in syms {
1716                func_ctx.register_variable_type(s, ty.clone());
1717            }
1718        }
1719        func_ctx.set_scratch_buffers(func_scratch);
1720
1721        // Register the `[T; N]` type for every use-site symbol of each indexed buffer, so the O3 `[T; N]`
1722        // path emits `let mut buf: [T; N] = [0; N]` and the SetIndex/Index codegen store/read the array.
1723        for (sym, info) in &func_indexed {
1724            let ty = format!("[{}; {}]", info.elem_ty, info.len);
1725            let name = interner.resolve(*sym).to_string();
1726            let mut syms: HashSet<Symbol> = HashSet::new();
1727            collect_named_syms(body, &name, interner, &mut syms);
1728            for s in syms {
1729                func_ctx.register_variable_type(s, ty.clone());
1730            }
1731        }
1732        func_ctx.set_indexed_buffers(func_indexed);
1733
1734        // Straight-line-push buffers: register `[T; K]` so the O3 `Let`/`Push` handlers emit the stack
1735        // array and `buf[k]=expr` fills. (init_array_fill runs inside the O3 `Let` handler.)
1736        for (sym, (elem_ty, len)) in &func_sl {
1737            let ty = format!("[{}; {}]", elem_ty, len);
1738            let name = interner.resolve(*sym).to_string();
1739            let mut syms: HashSet<Symbol> = HashSet::new();
1740            collect_named_syms(body, &name, interner, &mut syms);
1741            for s in syms {
1742                func_ctx.register_variable_type(s, ty.clone());
1743            }
1744        }
1745
1746        // Step 3b: register the `[T; N]` type for this function's own fixed-size return buffer and for every
1747        // caller result variable bound to an array-return fn — for every name-matching symbol, so the O3
1748        // `[T; N]` scalarization (stack decl + indexed-write fill) and the array-aware call/borrow/iterate
1749        // codegen all dispatch on it. Registered here (after const-table/scratch) so nothing overwrites it.
1750        for (sym, ty) in super::affine_array::array_var_types(body, array_return, array_return_fns) {
1751            let name = interner.resolve(sym).to_string();
1752            let mut syms: HashSet<Symbol> = HashSet::new();
1753            collect_named_syms(body, &name, interner, &mut syms);
1754            for s in syms {
1755                func_ctx.register_variable_type(s, ty.clone());
1756            }
1757        }
1758        // A LOOP-built return buffer (the digest) fills its `[T; N]` through a runtime cursor. Register the
1759        // return buffer (the `Return out` identifier) so the `Let` declares the cursor and each `Push`
1760        // becomes `out[cursor]=v; cursor+=1` instead of the compile-time O3 fill (which only counts static
1761        // push sites — a loop would write slot 0 repeatedly).
1762        if array_return.map_or(false, |i| i.loop_built) {
1763            if let Some(Stmt::Return { value: Some(Expr::Identifier(out)) }) = body.last() {
1764                let cursor = format!("__{}_fill", names.ident(*out));
1765                let name = interner.resolve(*out).to_string();
1766                let mut syms: HashSet<Symbol> = HashSet::new();
1767                collect_named_syms(body, &name, interner, &mut syms);
1768                for s in syms {
1769                    func_ctx.set_loop_fill_array(s, cursor.clone());
1770                }
1771            }
1772        }
1773
1774        if is_tce {
1775            // TCE: Wrap body in loop, use TCE-aware statement emitter
1776            writeln!(output, "    loop {{").unwrap();
1777            let stmt_refs: Vec<&Stmt> = body.iter().filter(|s| !is_affine_array_decl(s, &func_ctx) && !is_const_table_stmt(s, &func_ctx)).collect();
1778            let mut si = 0;
1779            while si < stmt_refs.len() {
1780                // Self-tail-call PAIR: `Set/Let x = self(args); Return x` lowers to
1781                // the same loop-back as a direct `Return self(args)` (the binding
1782                // flows straight into the return). Matches the VM and tree-walker.
1783                if si + 1 < stmt_refs.len() {
1784                    if let Some(call_args) = crate::tail_call::tail_pair_args(
1785                        stmt_refs[si],
1786                        stmt_refs[si + 1],
1787                        name,
1788                        param_syms.len(),
1789                    ) {
1790                        output.push_str(&codegen_tce_loopback(
1791                            call_args,
1792                            &param_syms,
1793                            interner,
1794                            2,
1795                            &mut func_ctx,
1796                            &mut func_synced_vars,
1797                            async_functions,
1798                        ));
1799                        si += 2;
1800                        continue;
1801                    }
1802                }
1803                if !no_peephole {
1804                    if let Some((code, skip)) = super::peephole::try_block_peepholes(&stmt_refs, si, interner, 2, &func_mutable_vars, &mut func_ctx, lww_fields, mv_fields, &mut func_synced_vars, &func_var_caps, async_functions, &func_pipe_vars, boxed_fields, registry, type_env) {
1805                        output.push_str(&code);
1806                        si += 1 + skip;
1807                        continue;
1808                    }
1809                }
1810                output.push_str(&codegen_stmt_tce(stmt_refs[si], name, &param_syms, interner, 2, &func_mutable_vars, &mut func_ctx, lww_fields, mv_fields, &mut func_synced_vars, &func_var_caps, async_functions, &func_pipe_vars, boxed_fields, registry, type_env));
1811                si += 1;
1812            }
1813            writeln!(output, "    }}").unwrap();
1814        } else if let Some(ref acc) = acc_info {
1815            // Accumulator Introduction: Wrap body in loop with accumulator variable
1816            writeln!(output, "    let mut __acc: i64 = {};", acc.identity).unwrap();
1817            writeln!(output, "    loop {{").unwrap();
1818            let stmt_refs: Vec<&Stmt> = body.iter().filter(|s| !is_affine_array_decl(s, &func_ctx) && !is_const_table_stmt(s, &func_ctx)).collect();
1819            let mut si = 0;
1820            while si < stmt_refs.len() {
1821                if !no_peephole {
1822                    if let Some((code, skip)) = super::peephole::try_block_peepholes(&stmt_refs, si, interner, 2, &func_mutable_vars, &mut func_ctx, lww_fields, mv_fields, &mut func_synced_vars, &func_var_caps, async_functions, &func_pipe_vars, boxed_fields, registry, type_env) {
1823                        output.push_str(&code);
1824                        si += 1 + skip;
1825                        continue;
1826                    }
1827                }
1828                output.push_str(&codegen_stmt_acc(stmt_refs[si], name, &param_syms, acc, interner, 2, &func_mutable_vars, &mut func_ctx, lww_fields, mv_fields, &mut func_synced_vars, &func_var_caps, async_functions, &func_pipe_vars, boxed_fields, registry, type_env));
1829                si += 1;
1830            }
1831            writeln!(output, "    }}").unwrap();
1832        } else if let Some(ref cf) = closed_form_info {
1833            // Closed-form: f(d) = ((base + k) << d) - k
1834            let param_name = names.ident(params[0].0);
1835            let base_plus_k = cf.base + cf.k;
1836            writeln!(output, "    if {} == 0 {{ return {}; }}", param_name, cf.base).unwrap();
1837            // Multiply by 2^d with a shift that yields 0 once d reaches the i64
1838            // bit width, matching the recurrence's repeated WRAPPING doubling
1839            // (a raw `<< d` is UB in debug and masks the count in release for
1840            // d >= 64, diverging from the interpreter/VM).
1841            let pow2d = format!(
1842                "(if ({p} as u64) >= 64 {{ 0i64 }} else {{ 1i64 << {p} }})",
1843                p = param_name
1844            );
1845            if cf.k == 0 {
1846                writeln!(output, "    {}i64.wrapping_mul({})", base_plus_k, pow2d).unwrap();
1847            } else {
1848                writeln!(output, "    ({}i64.wrapping_mul({})).wrapping_sub({})", base_plus_k, pow2d, cf.k).unwrap();
1849            }
1850        } else if is_memo {
1851            // Memoization: Wrap body in closure with thread-local cache
1852            let ret_ty = return_type_str.as_deref().unwrap_or("i64");
1853            let memo_name = format!("__MEMO_{}", func_name.to_uppercase());
1854
1855            // Build key type and key expression
1856            let (key_type, key_expr, copy_method) = if params.len() == 1 {
1857                let ty = codegen_type_expr(params[0].1, interner);
1858                let pname = interner.resolve(params[0].0).to_string();
1859                let copy = if is_copy_type_expr(params[0].1, interner) { "copied" } else { "cloned" };
1860                (ty, pname, copy)
1861            } else {
1862                let types: Vec<String> = params.iter().map(|(_, t)| codegen_type_expr(t, interner)).collect();
1863                let names: Vec<String> = params.iter().map(|(n, _)| interner.resolve(*n).to_string()).collect();
1864                let copy = if params.iter().all(|(_, t)| is_copy_type_expr(t, interner)) { "copied" } else { "cloned" };
1865                (format!("({})", types.join(", ")), format!("({})", names.join(", ")), copy)
1866            };
1867
1868            writeln!(output, "    use std::cell::RefCell;").unwrap();
1869            writeln!(output, "    thread_local! {{").unwrap();
1870            writeln!(output, "        static {}: RefCell<FxHashMap<{}, {}>> = RefCell::new(FxHashMap::default());", memo_name, key_type, ret_ty).unwrap();
1871            writeln!(output, "    }}").unwrap();
1872            writeln!(output, "    if let Some(__v) = {}.with(|c| c.borrow().get(&{}).{}()) {{", memo_name, key_expr, copy_method).unwrap();
1873            writeln!(output, "        return __v;").unwrap();
1874            writeln!(output, "    }}").unwrap();
1875            writeln!(output, "    let __memo_result = (|| -> {} {{", ret_ty).unwrap();
1876            let stmt_refs: Vec<&Stmt> = body.iter().filter(|s| !is_affine_array_decl(s, &func_ctx) && !is_const_table_stmt(s, &func_ctx)).collect();
1877            let mut si = 0;
1878            while si < stmt_refs.len() {
1879                if !no_peephole {
1880                    if let Some((code, skip)) = super::peephole::try_block_peepholes(&stmt_refs, si, interner, 2, &func_mutable_vars, &mut func_ctx, lww_fields, mv_fields, &mut func_synced_vars, &func_var_caps, async_functions, &func_pipe_vars, boxed_fields, registry, type_env) {
1881                        output.push_str(&code);
1882                        si += 1 + skip;
1883                        continue;
1884                    }
1885                }
1886                output.push_str(&codegen_stmt(stmt_refs[si], interner, 2, &func_mutable_vars, &mut func_ctx, lww_fields, mv_fields, &mut func_synced_vars, &func_var_caps, async_functions, &func_pipe_vars, boxed_fields, registry, type_env));
1887                si += 1;
1888            }
1889            writeln!(output, "    }})();").unwrap();
1890            writeln!(output, "    {}.with(|c| c.borrow_mut().insert({}, __memo_result));", memo_name, key_expr).unwrap();
1891            writeln!(output, "    __memo_result").unwrap();
1892        } else {
1893            let stmt_refs: Vec<&Stmt> = body.iter().filter(|s| !is_affine_array_decl(s, &func_ctx) && !is_const_table_stmt(s, &func_ctx)).collect();
1894            let mut si = 0;
1895            while si < stmt_refs.len() {
1896                if !no_peephole {
1897                    if let Some((code, skip)) = super::peephole::try_block_peepholes(&stmt_refs, si, interner, 1, &func_mutable_vars, &mut func_ctx, lww_fields, mv_fields, &mut func_synced_vars, &func_var_caps, async_functions, &func_pipe_vars, boxed_fields, registry, type_env) {
1898                        output.push_str(&code);
1899                        si += 1 + skip;
1900                        continue;
1901                    }
1902                }
1903                // OPT-1C: Set liveness for this statement so codegen_stmt can move
1904                // dead non-Copy args instead of cloning them.
1905                func_ctx.set_live_vars_after(liveness.live_after(name, si).clone());
1906                output.push_str(&codegen_stmt(stmt_refs[si], interner, 1, &func_mutable_vars, &mut func_ctx, lww_fields, mv_fields, &mut func_synced_vars, &func_var_caps, async_functions, &func_pipe_vars, boxed_fields, registry, type_env));
1907                si += 1;
1908            }
1909        }
1910
1911        if wrap_catch_unwind {
1912            writeln!(output, "    }})) {{").unwrap();
1913            writeln!(output, "        Ok(__v) => __v,").unwrap();
1914            writeln!(output, "        Err(__panic) => {{").unwrap();
1915            writeln!(output, "            let __msg = if let Some(s) = __panic.downcast_ref::<String>() {{ s.clone() }} else if let Some(s) = __panic.downcast_ref::<&str>() {{ s.to_string() }} else {{ \"Unknown panic\".to_string() }};").unwrap();
1916            writeln!(output, "            logos_set_last_error(__msg);").unwrap();
1917            // Determine default for panic case based on return type
1918            if let Some(ref ret_str) = return_type_str {
1919                if ret_str != "()" {
1920                    writeln!(output, "            Default::default()").unwrap();
1921                }
1922            }
1923            writeln!(output, "        }}").unwrap();
1924            writeln!(output, "    }}").unwrap();
1925        }
1926
1927        writeln!(output, "}}\n").unwrap();
1928    }
1929
1930    output
1931}
1932
1933/// Phase 38: Map native function names to logicaffeine_system module paths.
1934/// For system functions only — user-defined native paths bypass this entirely.
1935/// Returns None for unknown functions (caller emits compile_error!).
1936fn map_native_function(name: &str) -> Option<(&'static str, &'static str)> {
1937    match name {
1938        "read" => Some(("file", "read")),
1939        "write" => Some(("file", "write")),
1940        "now" => Some(("time", "now")),
1941        "sleep" => Some(("time", "sleep")),
1942        "randomInt" => Some(("random", "randomInt")),
1943        "randomFloat" => Some(("random", "randomFloat")),
1944        "get" => Some(("env", "get")),
1945        "args" => Some(("env", "args")),
1946        "parseInt" => Some(("text", "parseInt")),
1947        "parseFloat" => Some(("text", "parseFloat")),
1948        "chr" => Some(("text", "chr")),
1949        "format" => Some(("fmt", "format")),
1950        // ML-KEM (Kyber) forward + inverse NTT — the verified scalar+AVX2 i16 kernels.
1951        "mlkemNtt" => Some(("ntt", "mlkem_ntt")),
1952        // ── Word16-carrier ML-KEM (coefficients as Word16, no i64↔i16 round-trip; bytes stay Int).
1953        // mlkemNttW16/mlkemInvNttW16 are now pure-Logos lane NTTs in crypto.lg (the native kernels
1954        // `mlkem_ntt_w16`/`mlkem_inv_ntt_w16` are retired to dev/test oracles, proven byte-equal).
1955        "mlkemBaseMulW16" => Some(("ntt", "mlkem_base_mul_w16_seq")),
1956        "toMontW16" => Some(("ntt", "mlkem_to_mont_w16_seq")),
1957        "cbd2W16" => Some(("ntt", "mlkem_cbd2_w16_from_int")),
1958        "compressW16" => Some(("ntt", "mlkem_compress_w16_seq")),
1959        "decompressW16" => Some(("ntt", "mlkem_decompress_w16_seq")),
1960        "byteEncodeW16" => Some(("ntt", "mlkem_byte_encode_w16_to_int")),
1961        "byteDecodeW16" => Some(("ntt", "mlkem_byte_decode_w16_from_int")),
1962        "sampleAW16" => Some(("ntt", "mlkem_sample_a_w16_from_int")),
1963        "sampleMatrixW16" => Some(("ntt", "mlkem_sample_matrix_w16_from_int")),
1964        // 4-way-batched CBD noise (count polys per call, one 4-way SHAKE256 per four PRF streams) —
1965        // the Logos ML-KEM keygen/encrypt call this once instead of `count` scalar `mlkemPrfNoise`s.
1966        "mlkemNoiseBatch" => Some(("mlkem", "mlkem_noise_batch_from_int")),
1967        // High-level PQC primitives the Logos handshake orchestrates (ML-KEM-768 + ML-DSA-65).
1968        "mlkemKeypair" => Some(("mlkem", "mlkem_keypair_seq")),
1969        "mlkemEncapsKem" => Some(("mlkem", "mlkem_encaps_seq")),
1970        "mlkemDecapsKem" => Some(("mlkem", "mlkem_decaps_seq")),
1971        "mldsaKeypair" => Some(("mldsa", "mldsa_keypair_seq")),
1972        "mldsaSign" => Some(("mldsa", "mldsa_sign_seq")),
1973        "mldsaVerify" => Some(("mldsa", "mldsa_verify_seq")),
1974        // `chacha20Encrypt` is no longer native — it is the Logos lane cipher in crypto.lg (the
1975        // 8-way `laneChaCha20Block` + serialize + XOR), compiling to AVX2 and proven == RFC.
1976        // `poly1305Mac` is no longer native — it is the Logos lane MAC in crypto.lg (clamp + the
1977        // 4-way `poly1305Group` over `Lanes4Word64` → `vpmuludq` + scalar tail + finalize).
1978        "addModQW16" => Some(("ntt", "mlkem_add_mod_q_w16")),
1979        "subModQW16" => Some(("ntt", "mlkem_sub_mod_q_w16")),
1980        "zerosW16" => Some(("ntt", "mlkem_zeros_w16")),
1981        "mlkemInvNtt" => Some(("ntt", "mlkem_inv_ntt")),
1982        "mlkemBaseMul" => Some(("ntt", "mlkem_base_mul")),
1983        // ML-KEM CBD noise sampling (η=2 from 128 bytes, η=3 from 192 bytes).
1984        "cbd2" => Some(("ntt", "mlkem_cbd2")),
1985        "cbd3" => Some(("ntt", "mlkem_cbd3")),
1986        // ML-KEM serialization: Compress/Decompress + ByteEncode/ByteDecode (FIPS 203 §4.2.1).
1987        "compress" => Some(("ntt", "mlkem_compress")),
1988        "decompress" => Some(("ntt", "mlkem_decompress")),
1989        "byteEncode" => Some(("ntt", "mlkem_byte_encode")),
1990        "byteDecode" => Some(("ntt", "mlkem_byte_decode")),
1991        // ML-KEM uniform sampling: SampleNTT + matrix-entry expansion Â[i][j].
1992        "sampleNtt" => Some(("ntt", "mlkem_sample_ntt")),
1993        "sampleA" => Some(("ntt", "mlkem_sample_a")),
1994        // poly_tomont — the Montgomery rescale after basemul-accumulation.
1995        "toMont" => Some(("ntt", "mlkem_to_mont")),
1996        // SHA-3 / SHAKE (FIPS-202) — the symmetric/hash layer.
1997        "sha3_256" => Some(("keccak", "sha3_256")),
1998        "sha3_512" => Some(("keccak", "sha3_512")),
1999        "shake128" => Some(("keccak", "shake128")),
2000        "shake256" => Some(("keccak", "shake256")),
2001        _ => None,
2002    }
2003}