灵符lingfu

ling-lang

↓ 0 downloads

Ling - The Omniglot Systems Language

Keywords systems, polyglot, ai, crypto, game
lingfu install ling-lang
LicenseApache-2.0 OR MIT
AuthorsLing Lin, Sanny Lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (995)

crates/ling-codegen/src/cranelift/translate.rs
//! Backend-agnostic MIR → Cranelift translation shared by the JIT and AOT
//! object emitters. All helpers operate purely on a `FunctionBuilder` plus the
//! per-function symbol maps, so both backends build identical code from the same
//! source of truth.

use super::branchfuse;
use super::numtype::{int_literal_value, NumberTypes};
use super::runtime;
use cranelift::codegen::ir::{FuncRef, GlobalValue, InstBuilder, StackSlotData, StackSlotKind};
use cranelift::prelude::*;
use cranelift_frontend::FunctionBuilder;
use ling_ast::ast::{BinOp, UnOp};
use ling_mir::ir::*;
use std::collections::HashMap;

pub(crate) fn int_zero(builder: &mut FunctionBuilder) -> Value {
    builder.ins().iconst(types::I64, 0)
}

pub(crate) fn int_one(builder: &mut FunctionBuilder) -> Value {
    builder.ins().iconst(types::I64, 1)
}

/// Translate every basic block of `func` into `builder`, handling Cranelift's
/// deferred block sealing so loops (back-edges) build correctly. `blocks` must be
/// pre-created, one per MIR basic block, and `ctx` fully populated.
pub(crate) fn build_function_body(
    builder: &mut FunctionBuilder,
    func: &MirFunction,
    blocks: &[Block],
    ctx: &TransCtx,
) {
    let vars = ctx.vars;
    let pred_count = count_predecessors(func);
    let mut sealed = vec![false; func.basic_blocks.len()];
    let mut filled_pred = vec![0u32; func.basic_blocks.len()];
    let fusion = branchfuse::FunctionFusion::build(func, ctx.nt);

    for bi in 0..func.basic_blocks.len() {
        builder.switch_to_block(blocks[bi]);
        if bi == 0 && !sealed[bi] {
            builder.seal_block(blocks[bi]);
            sealed[bi] = true;
        }
        if bi == 0 {
            builder.append_block_params_for_function_params(blocks[bi]);
            let params: Vec<Value> = builder.block_params(blocks[bi]).to_vec();
            for (j, val) in params.iter().enumerate() {
                if let Some(&var) = vars.get(&Local(j + 1)) {
                    // The ABI always passes the boxed representation (callers
                    // don't know the callee's internal Int-vs-Boxed choice
                    // for its own parameters — see Rvalue::Call in
                    // translate_rvalue, which always emits boxed arguments).
                    // If numtype proved this parameter integral, the rest of
                    // the function body reads it raw (translate_op_int's
                    // `local_is_int` case skips unboxing), so it must be
                    // unboxed here at entry to match, not left as the
                    // incoming boxed bit pattern.
                    let stored = if ctx.nt.local_is_int(ctx.fname, j + 1) {
                        boxed_to_int(builder, *val)
                    } else {
                        *val
                    };
                    builder.def_var(var, stored);
                }
            }
        }

        // A planned branch fusion absorbs its condition's defining statements
        // (a suffix of this block); they are emitted inside the terminator.
        let plan = fusion.plan_for(bi);
        for (si, stmt) in func.basic_blocks[bi].statements.iter().enumerate() {
            let skipped = plan.is_some_and(|p| p.skips(si));
            if !skipped {
                translate_stmt(stmt, builder, ctx);
            }
        }
        if let Some(term) = &func.basic_blocks[bi].terminator {
            translate_terminator(term, builder, blocks, ctx, plan);
            match &term.kind {
                TerminatorKind::Goto { target } => {
                    filled_pred[target.0] += 1;
                    if filled_pred[target.0] == pred_count[target.0] && !sealed[target.0] {
                        builder.seal_block(blocks[target.0]);
                        sealed[target.0] = true;
                    }
                },
                TerminatorKind::SwitchInt { targets, otherwise, .. } => {
                    for (_, t) in targets {
                        filled_pred[t.0] += 1;
                        if filled_pred[t.0] == pred_count[t.0] && !sealed[t.0] {
                            builder.seal_block(blocks[t.0]);
                            sealed[t.0] = true;
                        }
                    }
                    filled_pred[otherwise.0] += 1;
                    if filled_pred[otherwise.0] == pred_count[otherwise.0] && !sealed[otherwise.0] {
                        builder.seal_block(blocks[otherwise.0]);
                        sealed[otherwise.0] = true;
                    }
                },
                _ => {},
            }
        }
    }

    for (i, block) in blocks.iter().enumerate() {
        if !sealed[i] {
            builder.seal_block(*block);
        }
    }
}

pub(crate) fn count_predecessors(func: &MirFunction) -> Vec<u32> {
    let mut pred_count = vec![0u32; func.basic_blocks.len()];
    for bb in &func.basic_blocks {
        if let Some(term) = &bb.terminator {
            match &term.kind {
                TerminatorKind::Goto { target } => pred_count[target.0] += 1,
                TerminatorKind::SwitchInt { targets, otherwise, .. } => {
                    for (_, t) in targets {
                        pred_count[t.0] += 1;
                    }
                    pred_count[otherwise.0] += 1;
                },
                _ => {},
            }
        }
    }
    pred_count
}

pub(crate) fn max_local_index(func: &MirFunction) -> usize {
    let mut max = 0usize;
    // Check all locals in statements
    for bb in &func.basic_blocks {
        for stmt in &bb.statements {
            if let StatementKind::Assign(local, rval) = &stmt.kind {
                max = max.max(local.0);
                collect_local_from_rvalue(rval, &mut max);
            }
            if let StatementKind::SetAttr(obj, _, val) = &stmt.kind {
                if let Operand::Copy(l) | Operand::Move(l) = obj {
                    max = max.max(l.0);
                }
                if let Operand::Copy(l) | Operand::Move(l) = val {
                    max = max.max(l.0);
                }
            }
            if let StatementKind::SetIndex(obj, idx, val) = &stmt.kind {
                if let Operand::Copy(l) | Operand::Move(l) = obj {
                    max = max.max(l.0);
                }
                if let Operand::Copy(l) | Operand::Move(l) = idx {
                    max = max.max(l.0);
                }
                if let Operand::Copy(l) | Operand::Move(l) = val {
                    max = max.max(l.0);
                }
            }
            if let StatementKind::StorageLive(l)
            | StatementKind::StorageDead(l)
            | StatementKind::Drop(l) = &stmt.kind
            {
                max = max.max(l.0);
            }
        }
        if let Some(term) = &bb.terminator {
            if let TerminatorKind::SwitchInt {
                discr: Operand::Copy(l) | Operand::Move(l), ..
            } = &term.kind
            {
                max = max.max(l.0);
            }
        }
    }
    // Ensure at least arg_count + 1 locals (for params + return)
    max = max.max(func.arg_count);
    max
}

pub(crate) fn collect_local_from_rvalue(rval: &Rvalue, max: &mut usize) {
    match rval {
        Rvalue::Use(op) | Rvalue::UnaryOp(_, op) => {
            if let Operand::Copy(l) | Operand::Move(l) = op {
                *max = (*max).max(l.0);
            }
        },
        Rvalue::BinaryOp(_, lhs, rhs) => {
            if let Operand::Copy(l) | Operand::Move(l) = lhs {
                *max = (*max).max(l.0);
            }
            if let Operand::Copy(l) | Operand::Move(l) = rhs {
                *max = (*max).max(l.0);
            }
        },
        Rvalue::Call { args, .. } => {
            for arg in args {
                if let Operand::Copy(l) | Operand::Move(l) = arg {
                    *max = (*max).max(l.0);
                }
            }
        },
        Rvalue::Aggregate(_, ops) => {
            for op in ops {
                if let Operand::Copy(l) | Operand::Move(l) = op {
                    *max = (*max).max(l.0);
                }
            }
        },
        Rvalue::GetAttr(op, _) | Rvalue::GetIndex(op, _) => {
            if let Operand::Copy(l) | Operand::Move(l) = op {
                *max = (*max).max(l.0);
            }
        },
        Rvalue::Ref(l) | Rvalue::MutRef(l) => {
            *max = (*max).max(l.0);
        },
        _ => {},
    }
}

/// All per-function symbol tables plus static type info, bundled so the
/// translation routines take one context instead of a long parameter list.
pub(crate) struct TransCtx<'a> {
    pub vars: &'a HashMap<Local, Variable>,
    pub string_gvs: &'a HashMap<String, GlobalValue>,
    pub builtin_gvs: &'a HashMap<String, GlobalValue>,
    pub runtime_refs: &'a HashMap<String, FuncRef>,
    pub func_refs: &'a HashMap<String, FuncRef>,
    pub func_arities: &'a HashMap<String, usize>,
    pub nt: &'a NumberTypes,
    pub fname: &'a str,
}

/// The static register representation of a value the translator produces.
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Repr {
    /// NaN-boxed: a raw `f64` bit pattern or a tagged singleton. The default and
    /// the only representation crossing call/print/return boundaries.
    Boxed,
    /// A raw native `i64` integer — only for locals/temps `numtype` proved
    /// integral, kept unboxed so loops avoid the NaN-box round-trip.
    Int,
}

pub(crate) fn translate_stmt(stmt: &Statement, builder: &mut FunctionBuilder, ctx: &TransCtx) {
    if let StatementKind::Assign(local, rvalue) = &stmt.kind {
        let (val, repr) = translate_rvalue(rvalue, builder, ctx);
        if let Some(&var) = ctx.vars.get(local) {
            // Coerce the produced value into the destination local's static
            // representation. The analysis guarantees an `Int` local only has
            // integer writers, so (Boxed, dest=Int) is the rare divisor-fallback
            // case where an integer rides the float path — a truncating round-trip
            // recovers it exactly.
            let dest_int = ctx.nt.local_is_int(ctx.fname, local.0);
            let stored = match (repr, dest_int) {
                (Repr::Int, false) => int_to_boxed(builder, val),
                (Repr::Boxed, true) => boxed_to_int(builder, val),
                _ => val,
            };
            builder.def_var(var, stored);
        }
    }
}

pub(crate) fn translate_rvalue(
    rvalue: &Rvalue,
    builder: &mut FunctionBuilder,
    ctx: &TransCtx,
) -> (Value, Repr) {
    match rvalue {
        Rvalue::Use(op) => {
            if ctx.nt.operand_is_int(ctx.fname, op) {
                (translate_op_int(op, builder, ctx), Repr::Int)
            } else {
                (translate_op(op, builder, ctx), Repr::Boxed)
            }
        },
        Rvalue::BinaryOp(op, lhs, rhs) => translate_binop_rvalue(op, lhs, rhs, builder, ctx),
        Rvalue::UnaryOp(UnOp::Neg, operand) if ctx.nt.operand_is_int(ctx.fname, operand) => {
            let v = translate_op_int(operand, builder, ctx);
            (builder.ins().ineg(v), Repr::Int)
        },
        Rvalue::UnaryOp(op, operand) => {
            let v = translate_op(operand, builder, ctx);
            (
                translate_unop(op, builder, v, ctx.runtime_refs),
                Repr::Boxed,
            )
        },
        Rvalue::Call { func: callee, args } => {
            let callee_name = match callee {
                Operand::Constant(Constant::Function(n)) => n.clone(),
                _ => String::new(),
            };
            let is_sys_or_kernel =
                callee_name.starts_with("ling_kernel_") || callee_name.starts_with("ling_sys_");
            // Only these two intrinsics scan their string argument for a NUL
            // terminator (`ling_kernel_vga_write_str`/`ling_kernel_panic` in
            // ling-kernel's lib.rs); every other kernel intrinsic reads the
            // length-prefixed `ling_str_new` layout via `strings::bytes_of_ptr`.
            // A `Constant::Str` argument must therefore be boxed through the
            // normal `ling_str_new` path for those, exactly like any computed
            // string already is — otherwise MIR constant-propagation folding a
            // `bind`-then-call back into a direct literal call (which it does
            // for any non-trivial `__main__`, since copy/const-prop aren't
            // skipped there) silently reinterprets raw literal bytes as a
            // length header.
            let raw_cstr_kernel_fn = matches!(
                callee_name.as_str(),
                "ling_kernel_vga_write_str" | "ling_kernel_panic"
            );
            let mut cal_args = Vec::new();
            if is_sys_or_kernel {
                for arg in args {
                    if !raw_cstr_kernel_fn {
                        if let Operand::Constant(Constant::Str(_)) = arg {
                            let boxed = translate_op(arg, builder, ctx);
                            cal_args.push(dynamic_to_raw(builder, boxed));
                            continue;
                        }
                    }
                    cal_args.push(translate_op_int(arg, builder, ctx));
                }
            } else {
                for arg in args {
                    cal_args.push(translate_op(arg, builder, ctx));
                }
            }
            let v: (Value, Repr) = if let Some(&fr) = ctx.func_refs.get(&callee_name) {
                if let Some(&arity) = ctx.func_arities.get(&callee_name) {
                    if cal_args.len() < arity {
                        let unit = if is_sys_or_kernel {
                            builder.ins().iconst(types::I64, 0)
                        } else {
                            builder.ins().iconst(types::I64, runtime::TAG_UNIT as i64)
                        };
                        cal_args.resize(arity, unit);
                    } else {
                        cal_args.truncate(arity);
                    }
                }
                let inst = builder.ins().call(fr, &cal_args);
                let raw = builder.inst_results(inst)[0];
                if is_sys_or_kernel {
                    (dynamic_from_raw(builder, raw), Repr::Boxed)
                } else {
                    (raw, Repr::Boxed)
                }
            } else {
                (
                    emit_builtin_call(
                        callee_name,
                        cal_args,
                        builder,
                        ctx.builtin_gvs,
                        ctx.runtime_refs,
                    ),
                    Repr::Boxed,
                )
            };
            v
        },
        Rvalue::Aggregate(_, ops) => {
            let mut list = emit_runtime_call0(builder, "__ling_list_new", ctx.runtime_refs);
            for op in ops {
                let v = translate_op(op, builder, ctx);
                list = emit_runtime_call2(builder, "__ling_list_push", list, v, ctx.runtime_refs);
            }
            (list, Repr::Boxed)
        },
        Rvalue::GetIndex(obj, idx) => {
            let obj_val = translate_op(obj, builder, ctx);
            let idx_val = translate_op(idx, builder, ctx);
            let v = emit_runtime_call2(
                builder,
                "__ling_list_get",
                obj_val,
                idx_val,
                ctx.runtime_refs,
            );
            (v, Repr::Boxed)
        },
        Rvalue::InlineAsm(template) => {
            if let Some(&gv) = ctx.string_gvs.get(template.as_str()) {
                let ptr = builder.ins().symbol_value(types::I64, gv);
                let len = builder.ins().iconst(types::I64, template.len() as i64);
                if let Some(&fr) = ctx.func_refs.get("ling_kernel_asm_exec") {
                    let inst = builder.ins().call(fr, &[ptr, len]);
                    let raw = builder.inst_results(inst)[0];
                    (int_to_boxed(builder, raw), Repr::Boxed)
                } else {
                    (int_zero(builder), Repr::Boxed)
                }
            } else {
                (int_zero(builder), Repr::Boxed)
            }
        },
        _ => (int_zero(builder), Repr::Boxed),
    }
}

/// Lower a binary op, preferring native `i64` arithmetic when both operands are
/// statically integral (no NaN-box bitcasts, integer compares, strength-reducible
/// `%`). Falls back to the boxed `f64`/runtime path otherwise.
fn translate_binop_rvalue(
    op: &BinOp,
    lhs: &Operand,
    rhs: &Operand,
    builder: &mut FunctionBuilder,
    ctx: &TransCtx,
) -> (Value, Repr) {
    let both_int = ctx.nt.operand_is_int(ctx.fname, lhs) && ctx.nt.operand_is_int(ctx.fname, rhs);
    if both_int {
        match op {
            BinOp::Add | BinOp::Sub | BinOp::Mul => {
                let a = translate_op_int(lhs, builder, ctx);
                let b = translate_op_int(rhs, builder, ctx);
                let r = match op {
                    BinOp::Add => builder.ins().iadd(a, b),
                    BinOp::Sub => builder.ins().isub(a, b),
                    _ => builder.ins().imul(a, b),
                };
                return (r, Repr::Int);
            },
            // `srem` traps on a zero divisor and on `INT_MIN % -1`, which the f64
            // path never does. Only take the integer path for a safe constant
            // divisor; variable divisors fall through to the boxed float remainder.
            BinOp::Rem => {
                if let Some(c) = int_literal_value(rhs) {
                    if c != 0 && c != -1 {
                        let a = translate_op_int(lhs, builder, ctx);
                        let b = builder.ins().iconst(types::I64, c);
                        return (builder.ins().srem(a, b), Repr::Int);
                    }
                }
            },
            BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => {
                let a = translate_op_int(lhs, builder, ctx);
                let b = translate_op_int(rhs, builder, ctx);
                let cc = match op {
                    BinOp::Eq => IntCC::Equal,
                    BinOp::Ne => IntCC::NotEqual,
                    BinOp::Lt => IntCC::SignedLessThan,
                    BinOp::Le => IntCC::SignedLessThanOrEqual,
                    BinOp::Gt => IntCC::SignedGreaterThan,
                    _ => IntCC::SignedGreaterThanOrEqual,
                };
                let cmp = builder.ins().icmp(cc, a, b);
                let t = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
                let f = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
                return (builder.ins().select(cmp, t, f), Repr::Boxed);
            },
            _ => {},
        }
    }
    let tys = OperandTypes {
        both_num: ctx.nt.operand_is_num(ctx.fname, lhs) && ctx.nt.operand_is_num(ctx.fname, rhs),
        l_bool: ctx.nt.operand_is_bool(ctx.fname, lhs),
        r_bool: ctx.nt.operand_is_bool(ctx.fname, rhs),
    };
    let lv = translate_op(lhs, builder, ctx);
    let rv = translate_op(rhs, builder, ctx);
    (
        translate_binop(op, builder, lv, rv, tys, ctx.runtime_refs),
        Repr::Boxed,
    )
}

/// Evaluate an operand as a NaN-boxed value (the universal representation).
/// Integer-typed locals are stored raw, so they are boxed back here on demand.
pub(crate) fn translate_op(op: &Operand, builder: &mut FunctionBuilder, ctx: &TransCtx) -> Value {
    let TransCtx { vars, string_gvs, runtime_refs, .. } = ctx;
    match op {
        Operand::Copy(l) | Operand::Move(l) => {
            let raw = builder.use_var(vars[l]);
            if ctx.nt.local_is_int(ctx.fname, l.0) {
                int_to_boxed(builder, raw)
            } else {
                raw
            }
        },
        Operand::Constant(c) => match c {
            Constant::I64(v) => {
                let bits = (*v as f64).to_bits();
                builder.ins().iconst(types::I64, bits as i64)
            },
            Constant::F64(v) => builder.ins().iconst(types::I64, *v as i64),
            Constant::Bool(b) => builder.ins().iconst(
                types::I64,
                if *b {
                    runtime::TAG_TRUE as i64
                } else {
                    runtime::TAG_FALSE as i64
                },
            ),
            Constant::Str(s) => {
                if let Some(&gv) = string_gvs.get(s.as_str()) {
                    let ptr = builder.ins().symbol_value(types::I64, gv);
                    let len = builder.ins().iconst(types::I64, s.len() as i64);
                    let fr = *runtime_refs.get("__ling_str_new").unwrap();
                    let inst = builder.ins().call(fr, &[ptr, len]);
                    builder.inst_results(inst)[0]
                } else {
                    int_zero(builder)
                }
            },
            Constant::Function(_) | Constant::GlobalData(_) | Constant::None => {
                builder.ins().iconst(types::I64, runtime::TAG_UNIT as i64)
            },
        },
    }
}

/// Evaluate an operand as a raw native `i64`. Called only on operands the type
/// analysis proved integral (an `Int` local or an `I64` literal); any other input
/// is converted defensively so a misclassification degrades to correct-but-slower
/// rather than to garbage.
pub(crate) fn translate_op_int(
    op: &Operand,
    builder: &mut FunctionBuilder,
    ctx: &TransCtx,
) -> Value {
    match op {
        // Ling has no integer-literal syntax: every source literal lowers to
        // Constant::F64 (see numtype::constant_is_int's doc comment), so
        // int_literal_value's F64 case is the common one here — a literal
        // used directly as an operand, e.g. `n % 2` or a call argument like
        // `modexp(base, 65537, 65521)`.
        Operand::Constant(_) if int_literal_value(op).is_some() => {
            builder.ins().iconst(types::I64, int_literal_value(op).unwrap())
        },
        Operand::Copy(l) | Operand::Move(l) if ctx.nt.local_is_int(ctx.fname, l.0) => {
            builder.use_var(ctx.vars[l])
        },
        // Raw pointer to the (NUL-terminated) string data — not boxed via
        // `ling_str_new`. Kernel intrinsics (`ling_kernel_vga_write_str` and
        // friends) are the only raw-int callers that ever see a `Str`
        // constant, and they all take a bare pointer, so this avoids
        // depending on the boxed/managed-string runtime (`ling_str_new`,
        // `ling_alloc`) that a `#![no_std]` kernel build doesn't provide.
        Operand::Constant(Constant::Str(s)) => {
            if let Some(&gv) = ctx.string_gvs.get(s.as_str()) {
                builder.ins().symbol_value(types::I64, gv)
            } else {
                builder.ins().iconst(types::I64, 0)
            }
        },
        _ => {
            let boxed = translate_op(op, builder, ctx);
            dynamic_to_raw(builder, boxed)
        },
    }
}

/// Static operand types feeding a binary op, used to pick the fastest lowering.
#[derive(Clone, Copy)]
pub(crate) struct OperandTypes {
    pub both_num: bool,
    pub l_bool: bool,
    pub r_bool: bool,
}

pub(crate) fn translate_binop(
    op: &BinOp,
    builder: &mut FunctionBuilder,
    lv: Value,
    rv: Value,
    tys: OperandTypes,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let OperandTypes { both_num, l_bool, r_bool } = tys;
    // When both operands are statically known numbers, emit the raw f64 op with no
    // tag check or runtime fallback — the hot path for numeric code.
    if both_num {
        match op {
            BinOp::Add => return raw_f64_binop(builder, lv, rv, |b, a, v| b.ins().fadd(a, v)),
            BinOp::Sub => return raw_f64_binop(builder, lv, rv, |b, a, v| b.ins().fsub(a, v)),
            BinOp::Mul => return raw_f64_binop(builder, lv, rv, |b, a, v| b.ins().fmul(a, v)),
            BinOp::Div => return raw_f64_binop(builder, lv, rv, |b, a, v| b.ins().fdiv(a, v)),
            BinOp::Rem => {
                return raw_f64_binop(builder, lv, rv, |b, a, v| {
                    let div = b.ins().fdiv(a, v);
                    let trunc = b.ins().trunc(div);
                    let prod = b.ins().fmul(trunc, v);
                    b.ins().fsub(a, prod)
                })
            },
            BinOp::Eq => return raw_f64_cmp(builder, lv, rv, FloatCC::Equal),
            BinOp::Ne => return raw_f64_cmp(builder, lv, rv, FloatCC::NotEqual),
            BinOp::Lt => return raw_f64_cmp(builder, lv, rv, FloatCC::LessThan),
            BinOp::Le => return raw_f64_cmp(builder, lv, rv, FloatCC::LessThanOrEqual),
            BinOp::Gt => return raw_f64_cmp(builder, lv, rv, FloatCC::GreaterThan),
            BinOp::Ge => return raw_f64_cmp(builder, lv, rv, FloatCC::GreaterThanOrEqual),
            _ => {},
        }
    }
    match op {
        BinOp::Add => emit_f64_or_runtime(
            builder,
            lv,
            rv,
            "__ling_add",
            |b, a, v| b.ins().fadd(a, v),
            runtime_refs,
        ),
        BinOp::Sub => emit_f64_or_runtime(
            builder,
            lv,
            rv,
            "__ling_sub",
            |b, a, v| b.ins().fsub(a, v),
            runtime_refs,
        ),
        BinOp::Mul => emit_f64_or_runtime(
            builder,
            lv,
            rv,
            "__ling_mul",
            |b, a, v| b.ins().fmul(a, v),
            runtime_refs,
        ),
        BinOp::Div => emit_f64_or_runtime(
            builder,
            lv,
            rv,
            "__ling_div",
            |b, a, v| b.ins().fdiv(a, v),
            runtime_refs,
        ),
        BinOp::Rem => emit_f64_or_runtime(
            builder,
            lv,
            rv,
            "__ling_rem",
            |b, a, v| {
                let div = b.ins().fdiv(a, v);
                let trunc = b.ins().trunc(div);
                let prod = b.ins().fmul(trunc, v);
                b.ins().fsub(a, prod)
            },
            runtime_refs,
        ),
        BinOp::Eq => {
            emit_f64_cmp_or_runtime(builder, lv, rv, "__ling_eq", FloatCC::Equal, runtime_refs)
        },
        BinOp::Ne => emit_f64_cmp_or_runtime(
            builder,
            lv,
            rv,
            "__ling_ne",
            FloatCC::NotEqual,
            runtime_refs,
        ),
        BinOp::Lt => emit_f64_cmp_or_runtime(
            builder,
            lv,
            rv,
            "__ling_lt",
            FloatCC::LessThan,
            runtime_refs,
        ),
        BinOp::Le => emit_f64_cmp_or_runtime(
            builder,
            lv,
            rv,
            "__ling_le",
            FloatCC::LessThanOrEqual,
            runtime_refs,
        ),
        BinOp::Gt => emit_f64_cmp_or_runtime(
            builder,
            lv,
            rv,
            "__ling_gt",
            FloatCC::GreaterThan,
            runtime_refs,
        ),
        BinOp::Ge => emit_f64_cmp_or_runtime(
            builder,
            lv,
            rv,
            "__ling_ge",
            FloatCC::GreaterThanOrEqual,
            runtime_refs,
        ),
        BinOp::And => emit_short_circuit_and(builder, lv, rv, l_bool, r_bool, runtime_refs),
        BinOp::Or => emit_short_circuit_or(builder, lv, rv, l_bool, r_bool, runtime_refs),
        _ => emit_runtime_call2(builder, "__ling_add", lv, rv, runtime_refs),
    }
}

/// Raw f64 arithmetic on NaN-boxed operands: bitcast in, compute, bitcast out.
pub(crate) fn raw_f64_binop(
    builder: &mut FunctionBuilder,
    a: Value,
    b: Value,
    f64_op: impl FnOnce(&mut FunctionBuilder, Value, Value) -> Value,
) -> Value {
    let fa = i64_as_f64(builder, a);
    let fb = i64_as_f64(builder, b);
    let r = f64_op(builder, fa, fb);
    f64_as_i64(builder, r)
}

/// Raw f64 comparison producing a NaN-boxed boolean.
pub(crate) fn raw_f64_cmp(builder: &mut FunctionBuilder, a: Value, b: Value, cc: FloatCC) -> Value {
    let fa = i64_as_f64(builder, a);
    let fb = i64_as_f64(builder, b);
    let cmp = builder.ins().fcmp(cc, fa, fb);
    let t = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
    let f = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
    builder.ins().select(cmp, t, f)
}

pub(crate) fn translate_unop(
    op: &UnOp,
    builder: &mut FunctionBuilder,
    v: Value,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    match op {
        UnOp::Ref | UnOp::Deref => v,
        UnOp::Neg => emit_f64_or_runtime(
            builder,
            v,
            v,
            "__ling_neg",
            |b, a, _| b.ins().fneg(a),
            runtime_refs,
        ),
        UnOp::Not => {
            let is_num = emit_is_number(builder, v);
            let block_num = builder.create_block();
            let block_tag = builder.create_block();
            let block_merge = builder.create_block();
            let res_var = builder.declare_var(types::I64);

            builder.ins().brif(is_num, block_num, &[], block_tag, &[]);

            builder.switch_to_block(block_num);
            let f = i64_as_f64(builder, v);
            let zero_f = builder.ins().f64const(0.0);
            let eq_zero = builder.ins().fcmp(FloatCC::Equal, f, zero_f);
            let one = int_one(builder);
            let zero = int_zero(builder);
            let sel = builder.ins().select(eq_zero, one, zero);
            builder.def_var(res_var, sel);
            builder.ins().jump(block_merge, &[]);
            builder.seal_block(block_num);

            builder.switch_to_block(block_tag);
            let rt_ret = emit_runtime_call1(builder, "__ling_not", v, runtime_refs);
            builder.def_var(res_var, rt_ret);
            builder.ins().jump(block_merge, &[]);
            builder.seal_block(block_tag);

            builder.switch_to_block(block_merge);
            builder.seal_block(block_merge);
            builder.use_var(res_var)
        },
    }
}

pub(crate) fn i64_as_f64(builder: &mut FunctionBuilder, v: Value) -> Value {
    builder.ins().bitcast(types::F64, MemFlags::new(), v)
}

pub(crate) fn f64_as_i64(builder: &mut FunctionBuilder, v: Value) -> Value {
    builder.ins().bitcast(types::I64, MemFlags::new(), v)
}

/// NaN-box a raw integer: widen to `f64`, then reinterpret its bits. Exact for
/// any value within ±2^53 (Ling numbers are `f64`, so larger integers were never
/// representable losslessly anyway).
pub(crate) fn int_to_boxed(builder: &mut FunctionBuilder, v: Value) -> Value {
    let f = builder.ins().fcvt_from_sint(types::F64, v);
    f64_as_i64(builder, f)
}

/// Unbox a NaN-boxed number to a raw integer, truncating toward zero. Saturating
/// (`fcvt_to_sint_sat`) so a stray non-integer or NaN can never trap.
pub(crate) fn boxed_to_int(builder: &mut FunctionBuilder, v: Value) -> Value {
    let f = i64_as_f64(builder, v);
    builder.ins().fcvt_to_sint_sat(types::I64, f)
}

/// Unbox a NaN-boxed *value of unknown kind* to a raw `u64` — used for
/// `ling_kernel_*` call arguments, which always want the raw payload (a
/// pointer for a heap value, a truncated integer for a number), never the
/// tagged bit pattern itself. `boxed_to_int` alone is wrong here: it always
/// treats its input as a boxed float, so a tagged heap value (e.g. a real
/// Ling string built via `ling_str_new`/concatenation) would have its tag
/// bits reinterpreted as float bits and saturate to 0 — silently handing
/// the kernel intrinsic a null pointer instead of the string's address.
pub(crate) fn dynamic_to_raw(builder: &mut FunctionBuilder, v: Value) -> Value {
    let is_num = emit_is_number(builder, v);
    let f = i64_as_f64(builder, v);
    let as_int = builder.ins().fcvt_to_sint_sat(types::I64, f);
    let ptr_mask = builder
        .ins()
        .iconst(types::I64, 0x0000_FFFF_FFFF_FFFFu64 as i64);
    let as_ptr = builder.ins().band(v, ptr_mask);
    builder.ins().select(is_num, as_int, as_ptr)
}

/// Box a `ling_kernel_*` call's raw `u64` return value — the mirror of
/// `dynamic_to_raw` for arguments, needed for the same reason. Most kernel
/// intrinsics return a plain small integer (a byte, a 0/1 flag) that wants
/// `int_to_boxed`'s float-NaN-boxing, but a few (`ling_kernel_read_line`)
/// return an *already-tagged* heap value built kernel-side via
/// `ling_str_new` — reboxing that as if it were an integer would
/// reinterpret its pointer bits as a signed integer and convert *that* to
/// a float, corrupting it. Distinguished the same way `dynamic_to_raw`
/// distinguishes its input: top byte `0x7F` marks an already-tagged value,
/// and no kernel-returned plain integer is remotely large enough
/// (~9.2 * 10^18) to collide with that by accident.
pub(crate) fn dynamic_from_raw(builder: &mut FunctionBuilder, raw: Value) -> Value {
    let shifted = builder.ins().ushr_imm(raw, 56);
    let already_tagged = builder.ins().icmp_imm(IntCC::Equal, shifted, 0x7F);
    let boxed_as_int = int_to_boxed(builder, raw);
    builder.ins().select(already_tagged, raw, boxed_as_int)
}

pub(crate) fn emit_f64_or_runtime(
    builder: &mut FunctionBuilder,
    a: Value,
    b: Value,
    runtime_fn: &str,
    f64_op: impl FnOnce(&mut FunctionBuilder, Value, Value) -> Value,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let is_a_num = emit_is_number(builder, a);
    let is_b_num = emit_is_number(builder, b);
    let both_num = builder.ins().band(is_a_num, is_b_num);
    let block_fast = builder.create_block();
    let block_rt = builder.create_block();
    let block_merge = builder.create_block();
    let res_var = builder.declare_var(types::I64);

    builder.ins().brif(both_num, block_fast, &[], block_rt, &[]);

    builder.switch_to_block(block_fast);
    let fa = i64_as_f64(builder, a);
    let fb = i64_as_f64(builder, b);
    let fres = f64_op(builder, fa, fb);
    let if64 = f64_as_i64(builder, fres);
    builder.def_var(res_var, if64);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_fast);

    builder.switch_to_block(block_rt);
    let rt_ret = emit_runtime_call2(builder, runtime_fn, a, b, runtime_refs);
    builder.def_var(res_var, rt_ret);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_rt);

    builder.switch_to_block(block_merge);
    builder.seal_block(block_merge);
    builder.use_var(res_var)
}

pub(crate) fn emit_f64_cmp_or_runtime(
    builder: &mut FunctionBuilder,
    a: Value,
    b: Value,
    runtime_fn: &str,
    cc: FloatCC,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let is_a_num = emit_is_number(builder, a);
    let is_b_num = emit_is_number(builder, b);
    let both_num = builder.ins().band(is_a_num, is_b_num);
    let block_fast = builder.create_block();
    let block_rt = builder.create_block();
    let block_merge = builder.create_block();
    let res_var = builder.declare_var(types::I64);

    builder.ins().brif(both_num, block_fast, &[], block_rt, &[]);

    builder.switch_to_block(block_fast);
    let fa = i64_as_f64(builder, a);
    let fb = i64_as_f64(builder, b);
    let cmp = builder.ins().fcmp(cc, fa, fb);
    let t = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
    let f = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
    let sel = builder.ins().select(cmp, t, f);
    builder.def_var(res_var, sel);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_fast);

    builder.switch_to_block(block_rt);
    let rt_ret = emit_runtime_call2(builder, runtime_fn, a, b, runtime_refs);
    builder.def_var(res_var, rt_ret);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_rt);

    builder.switch_to_block(block_merge);
    builder.seal_block(block_merge);
    builder.use_var(res_var)
}

/// Truthiness of a value: a direct `== TAG_TRUE` test when the value is a known
/// boolean, otherwise the full runtime truthiness path.
pub(crate) fn truthy_of(
    builder: &mut FunctionBuilder,
    val: Value,
    is_bool: bool,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    if is_bool {
        builder
            .ins()
            .icmp_imm(IntCC::Equal, val, runtime::TAG_TRUE as i64)
    } else {
        emit_is_truthy(builder, val, runtime_refs)
    }
}

pub(crate) fn emit_short_circuit_and(
    builder: &mut FunctionBuilder,
    lv: Value,
    rv: Value,
    l_bool: bool,
    r_bool: bool,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let l_is_truthy = truthy_of(builder, lv, l_bool, runtime_refs);
    let block_false = builder.create_block();
    let block_true = builder.create_block();
    let block_merge = builder.create_block();
    let res_var = builder.declare_var(types::I64);

    builder
        .ins()
        .brif(l_is_truthy, block_true, &[], block_false, &[]);

    builder.switch_to_block(block_false);
    let f = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
    builder.def_var(res_var, f);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_false);

    builder.switch_to_block(block_true);
    let r_is_truthy = truthy_of(builder, rv, r_bool, runtime_refs);
    let t = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
    let f2 = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
    let sel = builder.ins().select(r_is_truthy, t, f2);
    builder.def_var(res_var, sel);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_true);

    builder.switch_to_block(block_merge);
    builder.seal_block(block_merge);
    builder.use_var(res_var)
}

pub(crate) fn emit_short_circuit_or(
    builder: &mut FunctionBuilder,
    lv: Value,
    rv: Value,
    l_bool: bool,
    r_bool: bool,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let l_is_truthy = truthy_of(builder, lv, l_bool, runtime_refs);
    let block_true = builder.create_block();
    let block_false = builder.create_block();
    let block_merge = builder.create_block();
    let res_var = builder.declare_var(types::I64);

    builder
        .ins()
        .brif(l_is_truthy, block_true, &[], block_false, &[]);

    builder.switch_to_block(block_true);
    let t = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
    builder.def_var(res_var, t);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_true);

    builder.switch_to_block(block_false);
    let r_is_truthy = truthy_of(builder, rv, r_bool, runtime_refs);
    let t2 = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
    let f = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
    let sel = builder.ins().select(r_is_truthy, t2, f);
    builder.def_var(res_var, sel);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_false);

    builder.switch_to_block(block_merge);
    builder.seal_block(block_merge);
    builder.use_var(res_var)
}

pub(crate) fn emit_is_number(builder: &mut FunctionBuilder, val: Value) -> Value {
    let shifted = builder.ins().ushr_imm(val, 56);
    let tag = builder.ins().iconst(types::I64, 0x7F);
    builder.ins().icmp(IntCC::NotEqual, shifted, tag)
}

pub(crate) fn emit_is_truthy(
    builder: &mut FunctionBuilder,
    val: Value,
    _runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let is_num = emit_is_number(builder, val);
    let block_num = builder.create_block();
    let block_tag = builder.create_block();
    let block_merge = builder.create_block();
    let res_var = builder.declare_var(types::I64);

    builder.ins().brif(is_num, block_num, &[], block_tag, &[]);

    builder.switch_to_block(block_num);
    let f = i64_as_f64(builder, val);
    let zero = builder.ins().f64const(0.0);
    let is_nonzero = builder.ins().fcmp(FloatCC::NotEqual, f, zero);
    let one = int_one(builder);
    let zero2 = int_zero(builder);
    let sel = builder.ins().select(is_nonzero, one, zero2);
    builder.def_var(res_var, sel);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_num);

    builder.switch_to_block(block_tag);
    let is_true = builder
        .ins()
        .icmp_imm(IntCC::Equal, val, runtime::TAG_TRUE as i64);
    let is_false = builder
        .ins()
        .icmp_imm(IntCC::Equal, val, runtime::TAG_FALSE as i64);
    let is_unit = builder
        .ins()
        .icmp_imm(IntCC::Equal, val, runtime::TAG_UNIT as i64);
    // Truthy = is_true || (!is_false && !is_unit)
    let is_false_or_unit = builder.ins().bor(is_false, is_unit);
    let one_i64 = int_one(builder);
    let zero_i64 = int_zero(builder);
    let is_non_nil = builder.ins().select(is_false_or_unit, zero_i64, one_i64);
    let is_true_i64 = builder.ins().select(is_true, one_i64, zero_i64);
    let result_i64 = builder.ins().bor(is_true_i64, is_non_nil);
    builder.def_var(res_var, result_i64);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_tag);

    builder.switch_to_block(block_merge);
    builder.seal_block(block_merge);
    builder.use_var(res_var)
}

pub(crate) fn emit_runtime_call0(
    builder: &mut FunctionBuilder,
    name: &str,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let fr = *runtime_refs
        .get(name)
        .unwrap_or_else(|| panic!("runtime fn not found: {}", name));
    let inst = builder.ins().call(fr, &[]);
    builder.inst_results(inst)[0]
}

pub(crate) fn emit_runtime_call1(
    builder: &mut FunctionBuilder,
    name: &str,
    a: Value,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let fr = *runtime_refs
        .get(name)
        .unwrap_or_else(|| panic!("runtime fn not found: {}", name));
    let inst = builder.ins().call(fr, &[a]);
    builder.inst_results(inst)[0]
}

pub(crate) fn emit_runtime_call2(
    builder: &mut FunctionBuilder,
    name: &str,
    a: Value,
    b: Value,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let fr = *runtime_refs
        .get(name)
        .unwrap_or_else(|| panic!("runtime fn not found: {}", name));
    let inst = builder.ins().call(fr, &[a, b]);
    builder.inst_results(inst)[0]
}

pub(crate) fn emit_runtime_call3(
    builder: &mut FunctionBuilder,
    name: &str,
    a: Value,
    b: Value,
    c: Value,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let fr = *runtime_refs
        .get(name)
        .unwrap_or_else(|| panic!("runtime fn not found: {}", name));
    let inst = builder.ins().call(fr, &[a, b, c]);
    builder.inst_results(inst)[0]
}

pub(crate) fn emit_runtime_call4(
    builder: &mut FunctionBuilder,
    name: &str,
    a: Value,
    b: Value,
    c: Value,
    d: Value,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    let fr = *runtime_refs
        .get(name)
        .unwrap_or_else(|| panic!("runtime fn not found: {}", name));
    let inst = builder.ins().call(fr, &[a, b, c, d]);
    builder.inst_results(inst)[0]
}

pub(crate) fn emit_builtin_call(
    name: String,
    args: Vec<Value>,
    builder: &mut FunctionBuilder,
    builtin_gvs: &HashMap<String, GlobalValue>,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    // Fast-path for commonly-used builtins with direct JIT implementations
    match name.as_str() {
        "print" | "println" | "พิมพ์" | "印" | "打印" | "印刷" => {
            if !args.is_empty() {
                for arg in &args[..args.len() - 1] {
                    emit_runtime_call1(builder, "__ling_print_val", *arg, runtime_refs);
                }
                emit_runtime_call1(
                    builder,
                    "__ling_print_val",
                    args[args.len() - 1],
                    runtime_refs,
                );
            }
            return emit_runtime_call0(builder, "__ling_print_newline", runtime_refs);
        },
        "sin" => return unbox_f64_or_call(builder, args, "__ling_sin", runtime_refs),
        "cos" => return unbox_f64_or_call(builder, args, "__ling_cos", runtime_refs),
        "sqrt" => return unbox_f64_or_call(builder, args, "__ling_sqrt", runtime_refs),
        "abs" => return unbox_f64_or_call(builder, args, "__ling_abs", runtime_refs),
        "floor" => return unbox_f64_or_call(builder, args, "__ling_floor", runtime_refs),
        "ceil" => return unbox_f64_or_call(builder, args, "__ling_ceil", runtime_refs),
        "round" => return unbox_f64_or_call(builder, args, "__ling_round", runtime_refs),
        "time_now" | "เวลาปัจจุบัน" | "当前时间" | "経過時間" | "현재시간" =>
        {
            return emit_runtime_call0(builder, "__ling_time_now", runtime_refs);
        },
        "len" | "str_len" | "ความยาว" | "长度" | "長さ" | "길이" => {
            if !args.is_empty() {
                return emit_runtime_call1(builder, "__ling_str_len", args[0], runtime_refs);
            } else {
                return builder.ins().iconst(types::I64, runtime::TAG_UNIT as i64);
            }
        },
        // List ops dispatch straight to their O(1) ABI symbols, bypassing the
        // generic `__ling_builtin` → interpreter path (which deep-copies the Vec
        // on every push/get). Names mirror the interpreter's alias sets.
        "list_new" | "รายการใหม่" | "新建列表" | "新規リスト" | "새목록" =>
        {
            return emit_runtime_call0(builder, "__ling_list_new", runtime_refs);
        },
        "list_push"
        | "เพิ่มรายการ"
        | "列表添加"
        | "リスト追加"
        | "목록추가"
        | "افزودن_به_فهرست"
        | "أضف_للقائمة"
        | "הוסף_לרשימה"
        | "فہرست_میں_شامل_کرو"
        | "ajouter_liste"
        | "liste_anhängen"
        | "добавить_в_список"
            if args.len() >= 2 =>
        {
            return emit_runtime_call2(builder, "__ling_list_push", args[0], args[1], runtime_refs);
        },
        "list_get"
        | "รับรายการ"
        | "取元素"
        | "要素取得"
        | "요소가져오기"
        | "دریافت_از_فهرست"
        | "اجلب_من_القائمة"
        | "קבל_מרשימה"
        | "فہرست_سے_حاصل_کرو"
        | "obtenir_liste"
        | "liste_abrufen"
        | "получить_из_списка"
            if args.len() >= 2 =>
        {
            return emit_runtime_call2(builder, "__ling_list_get", args[0], args[1], runtime_refs);
        },
        // list_set was the odd one out: list_get/list_push already bypassed the
        // generic __ling_builtin dispatch (stack-slot arg marshaling + a
        // string-name lookup on every call), but list_set didn't — so any loop
        // mutating a list element every iteration (e.g. spring_rope's symplectic
        // Euler integrator) paid that overhead on every single write.
        "list_set"
        | "ตั้งรายการ"
        | "设元素"
        | "要素設定"
        | "요소설정"
        | "تنظیم_عنصر_فهرست"
        | "عيّن_عنصر_القائمة"
        | "קבע_איבר_רשימה"
        | "فہرست_سیٹ"
            if args.len() >= 3 =>
        {
            return emit_runtime_call3(
                builder,
                "__ling_list_set",
                args[0],
                args[1],
                args[2],
                runtime_refs,
            );
        },
        _ => {},
    }
    // Fallback: dispatch through __ling_builtin for any builtin not handled above
    if let Some(&name_gv) = builtin_gvs.get(&name) {
        let name_ptr = builder.ins().symbol_value(types::I64, name_gv);
        let name_len = builder.ins().iconst(types::I64, name.len() as i64);
        let num_args = args.len();
        // Always allocate a stack slot (even for 0 args) so args_ptr is valid
        let slot_size = std::cmp::max(num_args * 8, 8) as u32;
        let args_slot = builder.create_sized_stack_slot(StackSlotData::new(
            StackSlotKind::ExplicitSlot,
            slot_size,
            8,
        ));
        let args_ptr = builder.ins().stack_addr(types::I64, args_slot, 0);
        for (i, arg) in args.iter().enumerate() {
            let off = builder.ins().iconst(types::I64, (i * 8) as i64);
            let elem_ptr = builder.ins().iadd(args_ptr, off);
            builder.ins().store(MemFlags::new(), *arg, elem_ptr, 0);
        }
        let args_len = builder.ins().iconst(types::I64, num_args as i64);
        emit_runtime_call4(
            builder,
            "__ling_builtin",
            name_ptr,
            name_len,
            args_ptr,
            args_len,
            runtime_refs,
        )
    } else {
        builder.ins().iconst(types::I64, runtime::TAG_UNIT as i64)
    }
}

pub(crate) fn unbox_f64_or_call(
    builder: &mut FunctionBuilder,
    args: Vec<Value>,
    runtime_fn: &str,
    runtime_refs: &HashMap<String, FuncRef>,
) -> Value {
    if args.is_empty() {
        return builder.ins().iconst(types::I64, runtime::TAG_UNIT as i64);
    }
    let val = args[0];
    let is_num = emit_is_number(builder, val);
    let block_fast = builder.create_block();
    let block_slow = builder.create_block();
    let block_merge = builder.create_block();
    let res_var = builder.declare_var(types::I64);

    builder.ins().brif(is_num, block_fast, &[], block_slow, &[]);

    builder.switch_to_block(block_fast);
    let f = i64_as_f64(builder, val);
    let fr = *runtime_refs
        .get(runtime_fn)
        .unwrap_or_else(|| panic!("runtime fn not found: {}", runtime_fn));
    let inst = builder.ins().call(fr, &[f]);
    let f_result = builder.inst_results(inst)[0];
    let if64 = f64_as_i64(builder, f_result);
    builder.def_var(res_var, if64);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_fast);

    builder.switch_to_block(block_slow);
    builder.def_var(res_var, val);
    builder.ins().jump(block_merge, &[]);
    builder.seal_block(block_slow);

    builder.switch_to_block(block_merge);
    builder.seal_block(block_merge);
    builder.use_var(res_var)
}

pub(crate) fn translate_terminator(
    term: &Terminator,
    builder: &mut FunctionBuilder,
    blocks: &[Block],
    ctx: &TransCtx,
    fusion_plan: Option<&branchfuse::PlannedBranch>,
) {
    match &term.kind {
        TerminatorKind::Goto { target } => {
            builder.ins().jump(blocks[target.0], &[]);
        },
        TerminatorKind::SwitchInt { discr, targets, otherwise } => {
            let is_truthy = if let Some(plan) = fusion_plan {
                // Fused path: the condition's comparisons were absorbed from
                // the statement list; emit them as the branch test directly.
                branchfuse::emit_condition(plan.condition(), builder, ctx)
            } else {
                let val = translate_op(discr, builder, ctx);
                // A strict-bool discriminant (the common loop/if condition) only needs a
                // direct compare against TAG_TRUE, skipping the general truthiness path.
                if ctx.nt.operand_is_bool(ctx.fname, discr) {
                    builder
                        .ins()
                        .icmp_imm(IntCC::Equal, val, runtime::TAG_TRUE as i64)
                } else {
                    emit_is_truthy(builder, val, ctx.runtime_refs)
                }
            };
            let mut true_target = otherwise.0;
            let mut false_target = otherwise.0;
            for (const_val, target_block) in targets {
                if *const_val == 1 {
                    true_target = target_block.0;
                } else if *const_val == 0 {
                    false_target = target_block.0;
                }
            }
            if true_target != otherwise.0 && false_target != otherwise.0 {
                builder.ins().brif(
                    is_truthy,
                    blocks[true_target],
                    &[],
                    blocks[false_target],
                    &[],
                );
            } else if true_target != otherwise.0 {
                builder.ins().brif(
                    is_truthy,
                    blocks[true_target],
                    &[],
                    blocks[otherwise.0],
                    &[],
                );
            } else {
                builder.ins().jump(blocks[otherwise.0], &[]);
            }
        },
        TerminatorKind::Return => {
            let ret = builder.use_var(ctx.vars[&Local(0)]);
            // The ABI always returns the boxed representation — callers (see
            // Rvalue::Call in translate_rvalue) unconditionally treat a
            // call's result as Repr::Boxed, since they don't know the
            // callee's internal Int-vs-Boxed choice for its own return slot.
            // Local(0) itself may be stored raw when `local_is_int` proved
            // the return integral (translate_stmt's assignment coercion
            // keeps it consistent with that internal representation), so it
            // must be boxed here at the actual return, not just left as-is.
            let ret = if ctx.nt.local_is_int(ctx.fname, 0) { int_to_boxed(builder, ret) } else { ret };
            builder.ins().return_(&[ret]);
        },
        TerminatorKind::Unreachable => {
            builder.ins().trap(TrapCode::INTEGER_OVERFLOW);
        },
    }
}

← Back to index