Skip to main content

ling_codegen/cranelift/
translate.rs

1//! Backend-agnostic MIR → Cranelift translation shared by the JIT and AOT
2//! object emitters. All helpers operate purely on a `FunctionBuilder` plus the
3//! per-function symbol maps, so both backends build identical code from the same
4//! source of truth.
5
6use super::numtype::NumberTypes;
7use super::runtime;
8use cranelift::codegen::ir::{FuncRef, GlobalValue, InstBuilder, StackSlotData, StackSlotKind};
9use cranelift::prelude::*;
10use cranelift_frontend::FunctionBuilder;
11use ling_ast::ast::{BinOp, UnOp};
12use ling_mir::ir::*;
13use std::collections::HashMap;
14
15pub(crate) fn int_zero(builder: &mut FunctionBuilder) -> Value {
16    builder.ins().iconst(types::I64, 0)
17}
18
19pub(crate) fn int_one(builder: &mut FunctionBuilder) -> Value {
20    builder.ins().iconst(types::I64, 1)
21}
22
23/// Translate every basic block of `func` into `builder`, handling Cranelift's
24/// deferred block sealing so loops (back-edges) build correctly. `blocks` must be
25/// pre-created, one per MIR basic block, and `ctx` fully populated.
26pub(crate) fn build_function_body(
27    builder: &mut FunctionBuilder,
28    func: &MirFunction,
29    blocks: &[Block],
30    ctx: &TransCtx,
31) {
32    let vars = ctx.vars;
33    let pred_count = count_predecessors(func);
34    let mut sealed = vec![false; func.basic_blocks.len()];
35    let mut filled_pred = vec![0u32; func.basic_blocks.len()];
36
37    for bi in 0..func.basic_blocks.len() {
38        builder.switch_to_block(blocks[bi]);
39        if bi == 0 && !sealed[bi] {
40            builder.seal_block(blocks[bi]);
41            sealed[bi] = true;
42        }
43        if bi == 0 {
44            builder.append_block_params_for_function_params(blocks[bi]);
45            let params: Vec<Value> = builder.block_params(blocks[bi]).to_vec();
46            for (j, val) in params.iter().enumerate() {
47                if let Some(&var) = vars.get(&Local(j + 1)) {
48                    builder.def_var(var, *val);
49                }
50            }
51        }
52
53        for stmt in &func.basic_blocks[bi].statements {
54            translate_stmt(stmt, builder, ctx);
55        }
56        if let Some(term) = &func.basic_blocks[bi].terminator {
57            translate_terminator(term, builder, blocks, ctx);
58            match &term.kind {
59                TerminatorKind::Goto { target } => {
60                    filled_pred[target.0] += 1;
61                    if filled_pred[target.0] == pred_count[target.0] && !sealed[target.0] {
62                        builder.seal_block(blocks[target.0]);
63                        sealed[target.0] = true;
64                    }
65                },
66                TerminatorKind::SwitchInt { targets, otherwise, .. } => {
67                    for (_, t) in targets {
68                        filled_pred[t.0] += 1;
69                        if filled_pred[t.0] == pred_count[t.0] && !sealed[t.0] {
70                            builder.seal_block(blocks[t.0]);
71                            sealed[t.0] = true;
72                        }
73                    }
74                    filled_pred[otherwise.0] += 1;
75                    if filled_pred[otherwise.0] == pred_count[otherwise.0] && !sealed[otherwise.0] {
76                        builder.seal_block(blocks[otherwise.0]);
77                        sealed[otherwise.0] = true;
78                    }
79                },
80                _ => {},
81            }
82        }
83    }
84
85    for (i, block) in blocks.iter().enumerate() {
86        if !sealed[i] {
87            builder.seal_block(*block);
88        }
89    }
90}
91
92pub(crate) fn count_predecessors(func: &MirFunction) -> Vec<u32> {
93    let mut pred_count = vec![0u32; func.basic_blocks.len()];
94    for bb in &func.basic_blocks {
95        if let Some(term) = &bb.terminator {
96            match &term.kind {
97                TerminatorKind::Goto { target } => pred_count[target.0] += 1,
98                TerminatorKind::SwitchInt { targets, otherwise, .. } => {
99                    for (_, t) in targets {
100                        pred_count[t.0] += 1;
101                    }
102                    pred_count[otherwise.0] += 1;
103                },
104                _ => {},
105            }
106        }
107    }
108    pred_count
109}
110
111pub(crate) fn max_local_index(func: &MirFunction) -> usize {
112    let mut max = 0usize;
113    // Check all locals in statements
114    for bb in &func.basic_blocks {
115        for stmt in &bb.statements {
116            if let StatementKind::Assign(local, rval) = &stmt.kind {
117                max = max.max(local.0);
118                collect_local_from_rvalue(rval, &mut max);
119            }
120            if let StatementKind::SetAttr(obj, _, val) = &stmt.kind {
121                if let Operand::Copy(l) | Operand::Move(l) = obj {
122                    max = max.max(l.0);
123                }
124                if let Operand::Copy(l) | Operand::Move(l) = val {
125                    max = max.max(l.0);
126                }
127            }
128            if let StatementKind::SetIndex(obj, idx, val) = &stmt.kind {
129                if let Operand::Copy(l) | Operand::Move(l) = obj {
130                    max = max.max(l.0);
131                }
132                if let Operand::Copy(l) | Operand::Move(l) = idx {
133                    max = max.max(l.0);
134                }
135                if let Operand::Copy(l) | Operand::Move(l) = val {
136                    max = max.max(l.0);
137                }
138            }
139            if let StatementKind::StorageLive(l)
140            | StatementKind::StorageDead(l)
141            | StatementKind::Drop(l) = &stmt.kind
142            {
143                max = max.max(l.0);
144            }
145        }
146        if let Some(term) = &bb.terminator {
147            if let TerminatorKind::SwitchInt {
148                discr: Operand::Copy(l) | Operand::Move(l), ..
149            } = &term.kind
150            {
151                max = max.max(l.0);
152            }
153        }
154    }
155    // Ensure at least arg_count + 1 locals (for params + return)
156    max = max.max(func.arg_count);
157    max
158}
159
160pub(crate) fn collect_local_from_rvalue(rval: &Rvalue, max: &mut usize) {
161    match rval {
162        Rvalue::Use(op) | Rvalue::UnaryOp(_, op) => {
163            if let Operand::Copy(l) | Operand::Move(l) = op {
164                *max = (*max).max(l.0);
165            }
166        },
167        Rvalue::BinaryOp(_, lhs, rhs) => {
168            if let Operand::Copy(l) | Operand::Move(l) = lhs {
169                *max = (*max).max(l.0);
170            }
171            if let Operand::Copy(l) | Operand::Move(l) = rhs {
172                *max = (*max).max(l.0);
173            }
174        },
175        Rvalue::Call { args, .. } => {
176            for arg in args {
177                if let Operand::Copy(l) | Operand::Move(l) = arg {
178                    *max = (*max).max(l.0);
179                }
180            }
181        },
182        Rvalue::Aggregate(_, ops) => {
183            for op in ops {
184                if let Operand::Copy(l) | Operand::Move(l) = op {
185                    *max = (*max).max(l.0);
186                }
187            }
188        },
189        Rvalue::GetAttr(op, _) | Rvalue::GetIndex(op, _) => {
190            if let Operand::Copy(l) | Operand::Move(l) = op {
191                *max = (*max).max(l.0);
192            }
193        },
194        Rvalue::Ref(l) | Rvalue::MutRef(l) => {
195            *max = (*max).max(l.0);
196        },
197        _ => {},
198    }
199}
200
201/// All per-function symbol tables plus static type info, bundled so the
202/// translation routines take one context instead of a long parameter list.
203pub(crate) struct TransCtx<'a> {
204    pub vars: &'a HashMap<Local, Variable>,
205    pub string_gvs: &'a HashMap<String, GlobalValue>,
206    pub builtin_gvs: &'a HashMap<String, GlobalValue>,
207    pub runtime_refs: &'a HashMap<String, FuncRef>,
208    pub func_refs: &'a HashMap<String, FuncRef>,
209    pub nt: &'a NumberTypes,
210    pub fname: &'a str,
211}
212
213pub(crate) fn translate_stmt(stmt: &Statement, builder: &mut FunctionBuilder, ctx: &TransCtx) {
214    if let StatementKind::Assign(local, rvalue) = &stmt.kind {
215        let val = translate_rvalue(rvalue, builder, ctx);
216        if let Some(&var) = ctx.vars.get(local) {
217            builder.def_var(var, val);
218        }
219    }
220}
221
222pub(crate) fn translate_rvalue(
223    rvalue: &Rvalue,
224    builder: &mut FunctionBuilder,
225    ctx: &TransCtx,
226) -> Value {
227    match rvalue {
228        Rvalue::Use(op) => translate_op(op, builder, ctx),
229        Rvalue::BinaryOp(op, lhs, rhs) => {
230            let tys = OperandTypes {
231                both_num: ctx.nt.operand_is_num(ctx.fname, lhs)
232                    && ctx.nt.operand_is_num(ctx.fname, rhs),
233                l_bool: ctx.nt.operand_is_bool(ctx.fname, lhs),
234                r_bool: ctx.nt.operand_is_bool(ctx.fname, rhs),
235            };
236            let lv = translate_op(lhs, builder, ctx);
237            let rv = translate_op(rhs, builder, ctx);
238            translate_binop(op, builder, lv, rv, tys, ctx.runtime_refs)
239        },
240        Rvalue::UnaryOp(op, operand) => {
241            let v = translate_op(operand, builder, ctx);
242            translate_unop(op, builder, v, ctx.runtime_refs)
243        },
244        Rvalue::Call { func: callee, args } => {
245            let callee_name = match callee {
246                Operand::Constant(Constant::Function(n)) => n.clone(),
247                _ => String::new(),
248            };
249            let mut cal_args = Vec::new();
250            for arg in args {
251                cal_args.push(translate_op(arg, builder, ctx));
252            }
253            if let Some(&fr) = ctx.func_refs.get(&callee_name) {
254                let inst = builder.ins().call(fr, &cal_args);
255                builder.inst_results(inst)[0]
256            } else {
257                emit_builtin_call(
258                    callee_name,
259                    cal_args,
260                    builder,
261                    ctx.builtin_gvs,
262                    ctx.runtime_refs,
263                )
264            }
265        },
266        Rvalue::Aggregate(_, ops) => {
267            let mut list = emit_runtime_call0(builder, "__ling_list_new", ctx.runtime_refs);
268            for op in ops {
269                let v = translate_op(op, builder, ctx);
270                list = emit_runtime_call2(builder, "__ling_list_push", list, v, ctx.runtime_refs);
271            }
272            list
273        },
274        Rvalue::GetIndex(obj, idx) => {
275            let obj_val = translate_op(obj, builder, ctx);
276            let idx_val = translate_op(idx, builder, ctx);
277            emit_runtime_call2(
278                builder,
279                "__ling_list_get",
280                obj_val,
281                idx_val,
282                ctx.runtime_refs,
283            )
284        },
285        _ => int_zero(builder),
286    }
287}
288
289pub(crate) fn translate_op(op: &Operand, builder: &mut FunctionBuilder, ctx: &TransCtx) -> Value {
290    let TransCtx { vars, string_gvs, runtime_refs, .. } = ctx;
291    match op {
292        Operand::Copy(l) | Operand::Move(l) => builder.use_var(vars[l]),
293        Operand::Constant(c) => match c {
294            Constant::I64(v) => {
295                let bits = (*v as f64).to_bits();
296                builder.ins().iconst(types::I64, bits as i64)
297            },
298            Constant::F64(v) => builder.ins().iconst(types::I64, *v as i64),
299            Constant::Bool(b) => builder.ins().iconst(
300                types::I64,
301                if *b {
302                    runtime::TAG_TRUE as i64
303                } else {
304                    runtime::TAG_FALSE as i64
305                },
306            ),
307            Constant::Str(s) => {
308                if let Some(&gv) = string_gvs.get(s.as_str()) {
309                    let ptr = builder.ins().symbol_value(types::I64, gv);
310                    let len = builder.ins().iconst(types::I64, s.len() as i64);
311                    let fr = *runtime_refs.get("__ling_str_new").unwrap();
312                    let inst = builder.ins().call(fr, &[ptr, len]);
313                    builder.inst_results(inst)[0]
314                } else {
315                    int_zero(builder)
316                }
317            },
318            Constant::Function(_) | Constant::GlobalData(_) | Constant::None => {
319                builder.ins().iconst(types::I64, runtime::TAG_UNIT as i64)
320            },
321        },
322    }
323}
324
325/// Static operand types feeding a binary op, used to pick the fastest lowering.
326#[derive(Clone, Copy)]
327pub(crate) struct OperandTypes {
328    pub both_num: bool,
329    pub l_bool: bool,
330    pub r_bool: bool,
331}
332
333pub(crate) fn translate_binop(
334    op: &BinOp,
335    builder: &mut FunctionBuilder,
336    lv: Value,
337    rv: Value,
338    tys: OperandTypes,
339    runtime_refs: &HashMap<String, FuncRef>,
340) -> Value {
341    let OperandTypes { both_num, l_bool, r_bool } = tys;
342    // When both operands are statically known numbers, emit the raw f64 op with no
343    // tag check or runtime fallback — the hot path for numeric code.
344    if both_num {
345        match op {
346            BinOp::Add => return raw_f64_binop(builder, lv, rv, |b, a, v| b.ins().fadd(a, v)),
347            BinOp::Sub => return raw_f64_binop(builder, lv, rv, |b, a, v| b.ins().fsub(a, v)),
348            BinOp::Mul => return raw_f64_binop(builder, lv, rv, |b, a, v| b.ins().fmul(a, v)),
349            BinOp::Div => return raw_f64_binop(builder, lv, rv, |b, a, v| b.ins().fdiv(a, v)),
350            BinOp::Rem => {
351                return raw_f64_binop(builder, lv, rv, |b, a, v| {
352                    let div = b.ins().fdiv(a, v);
353                    let trunc = b.ins().trunc(div);
354                    let prod = b.ins().fmul(trunc, v);
355                    b.ins().fsub(a, prod)
356                })
357            },
358            BinOp::Eq => return raw_f64_cmp(builder, lv, rv, FloatCC::Equal),
359            BinOp::Ne => return raw_f64_cmp(builder, lv, rv, FloatCC::NotEqual),
360            BinOp::Lt => return raw_f64_cmp(builder, lv, rv, FloatCC::LessThan),
361            BinOp::Le => return raw_f64_cmp(builder, lv, rv, FloatCC::LessThanOrEqual),
362            BinOp::Gt => return raw_f64_cmp(builder, lv, rv, FloatCC::GreaterThan),
363            BinOp::Ge => return raw_f64_cmp(builder, lv, rv, FloatCC::GreaterThanOrEqual),
364            _ => {},
365        }
366    }
367    match op {
368        BinOp::Add => emit_f64_or_runtime(
369            builder,
370            lv,
371            rv,
372            "__ling_add",
373            |b, a, v| b.ins().fadd(a, v),
374            runtime_refs,
375        ),
376        BinOp::Sub => emit_f64_or_runtime(
377            builder,
378            lv,
379            rv,
380            "__ling_sub",
381            |b, a, v| b.ins().fsub(a, v),
382            runtime_refs,
383        ),
384        BinOp::Mul => emit_f64_or_runtime(
385            builder,
386            lv,
387            rv,
388            "__ling_mul",
389            |b, a, v| b.ins().fmul(a, v),
390            runtime_refs,
391        ),
392        BinOp::Div => emit_f64_or_runtime(
393            builder,
394            lv,
395            rv,
396            "__ling_div",
397            |b, a, v| b.ins().fdiv(a, v),
398            runtime_refs,
399        ),
400        BinOp::Rem => emit_f64_or_runtime(
401            builder,
402            lv,
403            rv,
404            "__ling_rem",
405            |b, a, v| {
406                let div = b.ins().fdiv(a, v);
407                let trunc = b.ins().trunc(div);
408                let prod = b.ins().fmul(trunc, v);
409                b.ins().fsub(a, prod)
410            },
411            runtime_refs,
412        ),
413        BinOp::Eq => {
414            emit_f64_cmp_or_runtime(builder, lv, rv, "__ling_eq", FloatCC::Equal, runtime_refs)
415        },
416        BinOp::Ne => emit_f64_cmp_or_runtime(
417            builder,
418            lv,
419            rv,
420            "__ling_ne",
421            FloatCC::NotEqual,
422            runtime_refs,
423        ),
424        BinOp::Lt => emit_f64_cmp_or_runtime(
425            builder,
426            lv,
427            rv,
428            "__ling_lt",
429            FloatCC::LessThan,
430            runtime_refs,
431        ),
432        BinOp::Le => emit_f64_cmp_or_runtime(
433            builder,
434            lv,
435            rv,
436            "__ling_le",
437            FloatCC::LessThanOrEqual,
438            runtime_refs,
439        ),
440        BinOp::Gt => emit_f64_cmp_or_runtime(
441            builder,
442            lv,
443            rv,
444            "__ling_gt",
445            FloatCC::GreaterThan,
446            runtime_refs,
447        ),
448        BinOp::Ge => emit_f64_cmp_or_runtime(
449            builder,
450            lv,
451            rv,
452            "__ling_ge",
453            FloatCC::GreaterThanOrEqual,
454            runtime_refs,
455        ),
456        BinOp::And => emit_short_circuit_and(builder, lv, rv, l_bool, r_bool, runtime_refs),
457        BinOp::Or => emit_short_circuit_or(builder, lv, rv, l_bool, r_bool, runtime_refs),
458        _ => emit_runtime_call2(builder, "__ling_add", lv, rv, runtime_refs),
459    }
460}
461
462/// Raw f64 arithmetic on NaN-boxed operands: bitcast in, compute, bitcast out.
463pub(crate) fn raw_f64_binop(
464    builder: &mut FunctionBuilder,
465    a: Value,
466    b: Value,
467    f64_op: impl FnOnce(&mut FunctionBuilder, Value, Value) -> Value,
468) -> Value {
469    let fa = i64_as_f64(builder, a);
470    let fb = i64_as_f64(builder, b);
471    let r = f64_op(builder, fa, fb);
472    f64_as_i64(builder, r)
473}
474
475/// Raw f64 comparison producing a NaN-boxed boolean.
476pub(crate) fn raw_f64_cmp(builder: &mut FunctionBuilder, a: Value, b: Value, cc: FloatCC) -> Value {
477    let fa = i64_as_f64(builder, a);
478    let fb = i64_as_f64(builder, b);
479    let cmp = builder.ins().fcmp(cc, fa, fb);
480    let t = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
481    let f = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
482    builder.ins().select(cmp, t, f)
483}
484
485pub(crate) fn translate_unop(
486    op: &UnOp,
487    builder: &mut FunctionBuilder,
488    v: Value,
489    runtime_refs: &HashMap<String, FuncRef>,
490) -> Value {
491    match op {
492        UnOp::Ref | UnOp::Deref => v,
493        UnOp::Neg => emit_f64_or_runtime(
494            builder,
495            v,
496            v,
497            "__ling_neg",
498            |b, a, _| b.ins().fneg(a),
499            runtime_refs,
500        ),
501        UnOp::Not => {
502            let is_num = emit_is_number(builder, v);
503            let block_num = builder.create_block();
504            let block_tag = builder.create_block();
505            let block_merge = builder.create_block();
506            let res_var = builder.declare_var(types::I64);
507
508            builder.ins().brif(is_num, block_num, &[], block_tag, &[]);
509
510            builder.switch_to_block(block_num);
511            let f = i64_as_f64(builder, v);
512            let zero_f = builder.ins().f64const(0.0);
513            let eq_zero = builder.ins().fcmp(FloatCC::Equal, f, zero_f);
514            let one = int_one(builder);
515            let zero = int_zero(builder);
516            let sel = builder.ins().select(eq_zero, one, zero);
517            builder.def_var(res_var, sel);
518            builder.ins().jump(block_merge, &[]);
519            builder.seal_block(block_num);
520
521            builder.switch_to_block(block_tag);
522            let rt_ret = emit_runtime_call1(builder, "__ling_not", v, runtime_refs);
523            builder.def_var(res_var, rt_ret);
524            builder.ins().jump(block_merge, &[]);
525            builder.seal_block(block_tag);
526
527            builder.switch_to_block(block_merge);
528            builder.seal_block(block_merge);
529            builder.use_var(res_var)
530        },
531    }
532}
533
534pub(crate) fn i64_as_f64(builder: &mut FunctionBuilder, v: Value) -> Value {
535    builder.ins().bitcast(types::F64, MemFlags::new(), v)
536}
537
538pub(crate) fn f64_as_i64(builder: &mut FunctionBuilder, v: Value) -> Value {
539    builder.ins().bitcast(types::I64, MemFlags::new(), v)
540}
541
542pub(crate) fn emit_f64_or_runtime(
543    builder: &mut FunctionBuilder,
544    a: Value,
545    b: Value,
546    runtime_fn: &str,
547    f64_op: impl FnOnce(&mut FunctionBuilder, Value, Value) -> Value,
548    runtime_refs: &HashMap<String, FuncRef>,
549) -> Value {
550    let is_a_num = emit_is_number(builder, a);
551    let is_b_num = emit_is_number(builder, b);
552    let both_num = builder.ins().band(is_a_num, is_b_num);
553    let block_fast = builder.create_block();
554    let block_rt = builder.create_block();
555    let block_merge = builder.create_block();
556    let res_var = builder.declare_var(types::I64);
557
558    builder.ins().brif(both_num, block_fast, &[], block_rt, &[]);
559
560    builder.switch_to_block(block_fast);
561    let fa = i64_as_f64(builder, a);
562    let fb = i64_as_f64(builder, b);
563    let fres = f64_op(builder, fa, fb);
564    let if64 = f64_as_i64(builder, fres);
565    builder.def_var(res_var, if64);
566    builder.ins().jump(block_merge, &[]);
567    builder.seal_block(block_fast);
568
569    builder.switch_to_block(block_rt);
570    let rt_ret = emit_runtime_call2(builder, runtime_fn, a, b, runtime_refs);
571    builder.def_var(res_var, rt_ret);
572    builder.ins().jump(block_merge, &[]);
573    builder.seal_block(block_rt);
574
575    builder.switch_to_block(block_merge);
576    builder.seal_block(block_merge);
577    builder.use_var(res_var)
578}
579
580pub(crate) fn emit_f64_cmp_or_runtime(
581    builder: &mut FunctionBuilder,
582    a: Value,
583    b: Value,
584    runtime_fn: &str,
585    cc: FloatCC,
586    runtime_refs: &HashMap<String, FuncRef>,
587) -> Value {
588    let is_a_num = emit_is_number(builder, a);
589    let is_b_num = emit_is_number(builder, b);
590    let both_num = builder.ins().band(is_a_num, is_b_num);
591    let block_fast = builder.create_block();
592    let block_rt = builder.create_block();
593    let block_merge = builder.create_block();
594    let res_var = builder.declare_var(types::I64);
595
596    builder.ins().brif(both_num, block_fast, &[], block_rt, &[]);
597
598    builder.switch_to_block(block_fast);
599    let fa = i64_as_f64(builder, a);
600    let fb = i64_as_f64(builder, b);
601    let cmp = builder.ins().fcmp(cc, fa, fb);
602    let t = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
603    let f = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
604    let sel = builder.ins().select(cmp, t, f);
605    builder.def_var(res_var, sel);
606    builder.ins().jump(block_merge, &[]);
607    builder.seal_block(block_fast);
608
609    builder.switch_to_block(block_rt);
610    let rt_ret = emit_runtime_call2(builder, runtime_fn, a, b, runtime_refs);
611    builder.def_var(res_var, rt_ret);
612    builder.ins().jump(block_merge, &[]);
613    builder.seal_block(block_rt);
614
615    builder.switch_to_block(block_merge);
616    builder.seal_block(block_merge);
617    builder.use_var(res_var)
618}
619
620/// Truthiness of a value: a direct `== TAG_TRUE` test when the value is a known
621/// boolean, otherwise the full runtime truthiness path.
622pub(crate) fn truthy_of(
623    builder: &mut FunctionBuilder,
624    val: Value,
625    is_bool: bool,
626    runtime_refs: &HashMap<String, FuncRef>,
627) -> Value {
628    if is_bool {
629        builder
630            .ins()
631            .icmp_imm(IntCC::Equal, val, runtime::TAG_TRUE as i64)
632    } else {
633        emit_is_truthy(builder, val, runtime_refs)
634    }
635}
636
637pub(crate) fn emit_short_circuit_and(
638    builder: &mut FunctionBuilder,
639    lv: Value,
640    rv: Value,
641    l_bool: bool,
642    r_bool: bool,
643    runtime_refs: &HashMap<String, FuncRef>,
644) -> Value {
645    let l_is_truthy = truthy_of(builder, lv, l_bool, runtime_refs);
646    let block_false = builder.create_block();
647    let block_true = builder.create_block();
648    let block_merge = builder.create_block();
649    let res_var = builder.declare_var(types::I64);
650
651    builder
652        .ins()
653        .brif(l_is_truthy, block_true, &[], block_false, &[]);
654
655    builder.switch_to_block(block_false);
656    let f = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
657    builder.def_var(res_var, f);
658    builder.ins().jump(block_merge, &[]);
659    builder.seal_block(block_false);
660
661    builder.switch_to_block(block_true);
662    let r_is_truthy = truthy_of(builder, rv, r_bool, runtime_refs);
663    let t = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
664    let f2 = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
665    let sel = builder.ins().select(r_is_truthy, t, f2);
666    builder.def_var(res_var, sel);
667    builder.ins().jump(block_merge, &[]);
668    builder.seal_block(block_true);
669
670    builder.switch_to_block(block_merge);
671    builder.seal_block(block_merge);
672    builder.use_var(res_var)
673}
674
675pub(crate) fn emit_short_circuit_or(
676    builder: &mut FunctionBuilder,
677    lv: Value,
678    rv: Value,
679    l_bool: bool,
680    r_bool: bool,
681    runtime_refs: &HashMap<String, FuncRef>,
682) -> Value {
683    let l_is_truthy = truthy_of(builder, lv, l_bool, runtime_refs);
684    let block_true = builder.create_block();
685    let block_false = builder.create_block();
686    let block_merge = builder.create_block();
687    let res_var = builder.declare_var(types::I64);
688
689    builder
690        .ins()
691        .brif(l_is_truthy, block_true, &[], block_false, &[]);
692
693    builder.switch_to_block(block_true);
694    let t = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
695    builder.def_var(res_var, t);
696    builder.ins().jump(block_merge, &[]);
697    builder.seal_block(block_true);
698
699    builder.switch_to_block(block_false);
700    let r_is_truthy = truthy_of(builder, rv, r_bool, runtime_refs);
701    let t2 = builder.ins().iconst(types::I64, runtime::TAG_TRUE as i64);
702    let f = builder.ins().iconst(types::I64, runtime::TAG_FALSE as i64);
703    let sel = builder.ins().select(r_is_truthy, t2, f);
704    builder.def_var(res_var, sel);
705    builder.ins().jump(block_merge, &[]);
706    builder.seal_block(block_false);
707
708    builder.switch_to_block(block_merge);
709    builder.seal_block(block_merge);
710    builder.use_var(res_var)
711}
712
713pub(crate) fn emit_is_number(builder: &mut FunctionBuilder, val: Value) -> Value {
714    let shifted = builder.ins().ushr_imm(val, 56);
715    let tag = builder.ins().iconst(types::I64, 0x7F);
716    builder.ins().icmp(IntCC::NotEqual, shifted, tag)
717}
718
719pub(crate) fn emit_is_truthy(
720    builder: &mut FunctionBuilder,
721    val: Value,
722    _runtime_refs: &HashMap<String, FuncRef>,
723) -> Value {
724    let is_num = emit_is_number(builder, val);
725    let block_num = builder.create_block();
726    let block_tag = builder.create_block();
727    let block_merge = builder.create_block();
728    let res_var = builder.declare_var(types::I64);
729
730    builder.ins().brif(is_num, block_num, &[], block_tag, &[]);
731
732    builder.switch_to_block(block_num);
733    let f = i64_as_f64(builder, val);
734    let zero = builder.ins().f64const(0.0);
735    let is_nonzero = builder.ins().fcmp(FloatCC::NotEqual, f, zero);
736    let one = int_one(builder);
737    let zero2 = int_zero(builder);
738    let sel = builder.ins().select(is_nonzero, one, zero2);
739    builder.def_var(res_var, sel);
740    builder.ins().jump(block_merge, &[]);
741    builder.seal_block(block_num);
742
743    builder.switch_to_block(block_tag);
744    let is_true = builder
745        .ins()
746        .icmp_imm(IntCC::Equal, val, runtime::TAG_TRUE as i64);
747    let is_false = builder
748        .ins()
749        .icmp_imm(IntCC::Equal, val, runtime::TAG_FALSE as i64);
750    let is_unit = builder
751        .ins()
752        .icmp_imm(IntCC::Equal, val, runtime::TAG_UNIT as i64);
753    // Truthy = is_true || (!is_false && !is_unit)
754    let is_false_or_unit = builder.ins().bor(is_false, is_unit);
755    let one_i64 = int_one(builder);
756    let zero_i64 = int_zero(builder);
757    let is_non_nil = builder.ins().select(is_false_or_unit, zero_i64, one_i64);
758    let is_true_i64 = builder.ins().select(is_true, one_i64, zero_i64);
759    let result_i64 = builder.ins().bor(is_true_i64, is_non_nil);
760    builder.def_var(res_var, result_i64);
761    builder.ins().jump(block_merge, &[]);
762    builder.seal_block(block_tag);
763
764    builder.switch_to_block(block_merge);
765    builder.seal_block(block_merge);
766    builder.use_var(res_var)
767}
768
769pub(crate) fn emit_runtime_call0(
770    builder: &mut FunctionBuilder,
771    name: &str,
772    runtime_refs: &HashMap<String, FuncRef>,
773) -> Value {
774    let fr = *runtime_refs
775        .get(name)
776        .unwrap_or_else(|| panic!("runtime fn not found: {}", name));
777    let inst = builder.ins().call(fr, &[]);
778    builder.inst_results(inst)[0]
779}
780
781pub(crate) fn emit_runtime_call1(
782    builder: &mut FunctionBuilder,
783    name: &str,
784    a: Value,
785    runtime_refs: &HashMap<String, FuncRef>,
786) -> Value {
787    let fr = *runtime_refs
788        .get(name)
789        .unwrap_or_else(|| panic!("runtime fn not found: {}", name));
790    let inst = builder.ins().call(fr, &[a]);
791    builder.inst_results(inst)[0]
792}
793
794pub(crate) fn emit_runtime_call2(
795    builder: &mut FunctionBuilder,
796    name: &str,
797    a: Value,
798    b: Value,
799    runtime_refs: &HashMap<String, FuncRef>,
800) -> Value {
801    let fr = *runtime_refs
802        .get(name)
803        .unwrap_or_else(|| panic!("runtime fn not found: {}", name));
804    let inst = builder.ins().call(fr, &[a, b]);
805    builder.inst_results(inst)[0]
806}
807
808pub(crate) fn emit_runtime_call4(
809    builder: &mut FunctionBuilder,
810    name: &str,
811    a: Value,
812    b: Value,
813    c: Value,
814    d: Value,
815    runtime_refs: &HashMap<String, FuncRef>,
816) -> Value {
817    let fr = *runtime_refs
818        .get(name)
819        .unwrap_or_else(|| panic!("runtime fn not found: {}", name));
820    let inst = builder.ins().call(fr, &[a, b, c, d]);
821    builder.inst_results(inst)[0]
822}
823
824pub(crate) fn emit_builtin_call(
825    name: String,
826    args: Vec<Value>,
827    builder: &mut FunctionBuilder,
828    builtin_gvs: &HashMap<String, GlobalValue>,
829    runtime_refs: &HashMap<String, FuncRef>,
830) -> Value {
831    // Fast-path for commonly-used builtins with direct JIT implementations
832    match name.as_str() {
833        "print" | "println" | "พิมพ์" | "印" | "打印" | "印刷" => {
834            if !args.is_empty() {
835                for arg in &args[..args.len() - 1] {
836                    emit_runtime_call1(builder, "__ling_print_val", *arg, runtime_refs);
837                }
838                emit_runtime_call1(
839                    builder,
840                    "__ling_print_val",
841                    args[args.len() - 1],
842                    runtime_refs,
843                );
844            }
845            return emit_runtime_call0(builder, "__ling_print_newline", runtime_refs);
846        },
847        "sin" => return unbox_f64_or_call(builder, args, "__ling_sin", runtime_refs),
848        "cos" => return unbox_f64_or_call(builder, args, "__ling_cos", runtime_refs),
849        "sqrt" => return unbox_f64_or_call(builder, args, "__ling_sqrt", runtime_refs),
850        "abs" => return unbox_f64_or_call(builder, args, "__ling_abs", runtime_refs),
851        "floor" => return unbox_f64_or_call(builder, args, "__ling_floor", runtime_refs),
852        "ceil" => return unbox_f64_or_call(builder, args, "__ling_ceil", runtime_refs),
853        "round" => return unbox_f64_or_call(builder, args, "__ling_round", runtime_refs),
854        "time_now" | "เวลาปัจจุบัน" | "当前时间" | "経過時間" | "현재시간" =>
855        {
856            return emit_runtime_call0(builder, "__ling_time_now", runtime_refs);
857        },
858        "len" | "str_len" | "ความยาว" | "长度" | "長さ" | "길이" => {
859            if !args.is_empty() {
860                return emit_runtime_call1(builder, "__ling_str_len", args[0], runtime_refs);
861            } else {
862                return builder.ins().iconst(types::I64, runtime::TAG_UNIT as i64);
863            }
864        },
865        _ => {},
866    }
867    // Fallback: dispatch through __ling_builtin for any builtin not handled above
868    if let Some(&name_gv) = builtin_gvs.get(&name) {
869        let name_ptr = builder.ins().symbol_value(types::I64, name_gv);
870        let name_len = builder.ins().iconst(types::I64, name.len() as i64);
871        let num_args = args.len();
872        // Always allocate a stack slot (even for 0 args) so args_ptr is valid
873        let slot_size = std::cmp::max(num_args * 8, 8) as u32;
874        let args_slot = builder.create_sized_stack_slot(StackSlotData::new(
875            StackSlotKind::ExplicitSlot,
876            slot_size,
877            8,
878        ));
879        let args_ptr = builder.ins().stack_addr(types::I64, args_slot, 0);
880        for (i, arg) in args.iter().enumerate() {
881            let off = builder.ins().iconst(types::I64, (i * 8) as i64);
882            let elem_ptr = builder.ins().iadd(args_ptr, off);
883            builder.ins().store(MemFlags::new(), *arg, elem_ptr, 0);
884        }
885        let args_len = builder.ins().iconst(types::I64, num_args as i64);
886        emit_runtime_call4(
887            builder,
888            "__ling_builtin",
889            name_ptr,
890            name_len,
891            args_ptr,
892            args_len,
893            runtime_refs,
894        )
895    } else {
896        builder.ins().iconst(types::I64, runtime::TAG_UNIT as i64)
897    }
898}
899
900pub(crate) fn unbox_f64_or_call(
901    builder: &mut FunctionBuilder,
902    args: Vec<Value>,
903    runtime_fn: &str,
904    runtime_refs: &HashMap<String, FuncRef>,
905) -> Value {
906    if args.is_empty() {
907        return builder.ins().iconst(types::I64, runtime::TAG_UNIT as i64);
908    }
909    let val = args[0];
910    let is_num = emit_is_number(builder, val);
911    let block_fast = builder.create_block();
912    let block_slow = builder.create_block();
913    let block_merge = builder.create_block();
914    let res_var = builder.declare_var(types::I64);
915
916    builder.ins().brif(is_num, block_fast, &[], block_slow, &[]);
917
918    builder.switch_to_block(block_fast);
919    let f = i64_as_f64(builder, val);
920    let fr = *runtime_refs
921        .get(runtime_fn)
922        .unwrap_or_else(|| panic!("runtime fn not found: {}", runtime_fn));
923    let inst = builder.ins().call(fr, &[f]);
924    let f_result = builder.inst_results(inst)[0];
925    let if64 = f64_as_i64(builder, f_result);
926    builder.def_var(res_var, if64);
927    builder.ins().jump(block_merge, &[]);
928    builder.seal_block(block_fast);
929
930    builder.switch_to_block(block_slow);
931    builder.def_var(res_var, val);
932    builder.ins().jump(block_merge, &[]);
933    builder.seal_block(block_slow);
934
935    builder.switch_to_block(block_merge);
936    builder.seal_block(block_merge);
937    builder.use_var(res_var)
938}
939
940pub(crate) fn translate_terminator(
941    term: &Terminator,
942    builder: &mut FunctionBuilder,
943    blocks: &[Block],
944    ctx: &TransCtx,
945) {
946    match &term.kind {
947        TerminatorKind::Goto { target } => {
948            builder.ins().jump(blocks[target.0], &[]);
949        },
950        TerminatorKind::SwitchInt { discr, targets, otherwise } => {
951            let val = translate_op(discr, builder, ctx);
952            // A strict-bool discriminant (the common loop/if condition) only needs a
953            // direct compare against TAG_TRUE, skipping the general truthiness path.
954            let is_truthy = if ctx.nt.operand_is_bool(ctx.fname, discr) {
955                builder
956                    .ins()
957                    .icmp_imm(IntCC::Equal, val, runtime::TAG_TRUE as i64)
958            } else {
959                emit_is_truthy(builder, val, ctx.runtime_refs)
960            };
961            let mut true_target = otherwise.0;
962            let mut false_target = otherwise.0;
963            for (const_val, target_block) in targets {
964                if *const_val == 1 {
965                    true_target = target_block.0;
966                } else if *const_val == 0 {
967                    false_target = target_block.0;
968                }
969            }
970            if true_target != otherwise.0 && false_target != otherwise.0 {
971                builder.ins().brif(
972                    is_truthy,
973                    blocks[true_target],
974                    &[],
975                    blocks[false_target],
976                    &[],
977                );
978            } else if true_target != otherwise.0 {
979                builder.ins().brif(
980                    is_truthy,
981                    blocks[true_target],
982                    &[],
983                    blocks[otherwise.0],
984                    &[],
985                );
986            } else {
987                builder.ins().jump(blocks[otherwise.0], &[]);
988            }
989        },
990        TerminatorKind::Return => {
991            let ret = builder.use_var(ctx.vars[&Local(0)]);
992            builder.ins().return_(&[ret]);
993        },
994        TerminatorKind::Unreachable => {
995            builder.ins().trap(TrapCode::INTEGER_OVERFLOW);
996        },
997    }
998}