Skip to main content

ling_codegen/
bytecode.rs

1use crate::CodegenBackend;
2use ling_ast::ast::BinOp;
3use ling_ast::ast::UnOp;
4use ling_mir::ir::*;
5use std::collections::HashMap;
6
7// ─── Opcodes ─────────────────────────────────────────────────────────────────
8
9const OP_NOP: u8 = 0;
10const OP_PUSHI64: u8 = 1;
11const OP_PUSHF64: u8 = 2;
12const OP_PUSHSTR: u8 = 3;
13const OP_PUSHBOOL: u8 = 4;
14const OP_PUSHNONE: u8 = 5;
15const OP_POP: u8 = 6;
16const OP_LOADLOCAL: u8 = 7;
17const OP_STORELOCAL: u8 = 8;
18const OP_ADD: u8 = 9;
19const OP_SUB: u8 = 10;
20const OP_MUL: u8 = 11;
21const OP_DIV: u8 = 12;
22const OP_REM: u8 = 13;
23const OP_EQ: u8 = 14;
24const OP_NE: u8 = 15;
25const OP_LT: u8 = 16;
26const OP_LE: u8 = 17;
27const OP_GT: u8 = 18;
28const OP_GE: u8 = 19;
29const OP_AND: u8 = 20;
30const OP_OR: u8 = 21;
31const OP_NOT: u8 = 22;
32const OP_NEG: u8 = 23;
33const OP_CALL: u8 = 24;
34const OP_CALLBUILTIN: u8 = 25;
35const OP_RET: u8 = 26;
36const OP_JUMP: u8 = 27;
37const OP_JUMPIFFALSE: u8 = 28;
38const OP_MAKELIST: u8 = 29;
39const OP_GETINDEX: u8 = 30;
40const OP_HALT: u8 = 0xFF;
41
42// ─── Values ──────────────────────────────────────────────────────────────────
43
44#[derive(Debug, Clone)]
45pub enum Value {
46    Number(f64),
47    Bool(bool),
48    Str(String),
49    None,
50    List(Vec<Value>),
51}
52
53impl Value {
54    fn is_truthy(&self) -> bool {
55        match self {
56            Value::Bool(b) => *b,
57            Value::Number(n) => *n != 0.0,
58            Value::Str(s) => !s.is_empty(),
59            Value::List(l) => !l.is_empty(),
60            Value::None => false,
61        }
62    }
63
64    fn display(&self) -> String {
65        match self {
66            Value::Number(n) => {
67                if n.fract() == 0.0 && n.is_finite() && n.abs() < 1e15 {
68                    format!("{:.0}", n)
69                } else {
70                    format!("{}", n)
71                }
72            }
73            Value::Bool(b) => b.to_string(),
74            Value::Str(s) => s.clone(),
75            Value::None => "()".to_string(),
76            Value::List(l) => {
77                let items: Vec<String> = l.iter().map(|v| v.display()).collect();
78                format!("[{}]", items.join(", "))
79            }
80        }
81    }
82}
83
84fn values_equal(a: &Value, b: &Value) -> bool {
85    match (a, b) {
86        (Value::Number(a), Value::Number(b)) => a.to_bits() == b.to_bits(),
87        (Value::Bool(a), Value::Bool(b)) => a == b,
88        (Value::Str(a), Value::Str(b)) => a == b,
89        (Value::None, Value::None) => true,
90        (Value::List(a), Value::List(b)) => {
91            a.len() == b.len() && a.iter().zip(b).all(|(x, y)| values_equal(x, y))
92        }
93        _ => false,
94    }
95}
96
97fn num_cmp<F: Fn(f64, f64) -> bool>(a: &Value, b: &Value, cmp: F) -> bool {
98    match (a, b) {
99        (Value::Number(a), Value::Number(b)) => cmp(*a, *b),
100        _ => false,
101    }
102}
103
104fn resolve_builtin(name: &str) -> Result<(&'static str, usize), String> {
105    match name {
106        "print" | "println" | "พิมพ์" | "印" | "打印" | "印刷" => Ok(("print", 1)),
107        "format" | "รูปแบบ" | "格式" | "フォーマット" | "서식" => Ok(("format", 0)),
108        "len" | "str_len" | "ความยาว" | "长度" | "長さ" | "길이" => Ok(("len", 1)),
109        "to_str" | "str" | "แปลงสตริง" => Ok(("to_str", 1)),
110        "sin" => Ok(("sin", 1)),
111        "cos" => Ok(("cos", 1)),
112        _ => Err(format!("unknown builtin: {}", name)),
113    }
114}
115
116// ─── Chunk ───────────────────────────────────────────────────────────────────
117
118#[derive(Debug, Default)]
119pub struct Chunk {
120    pub code: Vec<u8>,
121    pub floats: Vec<f64>,
122    pub strings: Vec<String>,
123    pub local_count: u16,
124}
125
126impl Chunk {
127    fn w(&mut self, b: u8) { self.code.push(b); }
128    fn w2(&mut self, v: u16) { self.code.extend_from_slice(&v.to_le_bytes()); }
129    fn w8(&mut self, v: i64) { self.code.extend_from_slice(&v.to_le_bytes()); }
130    fn add_float(&mut self, v: f64) -> u16 { let i = self.floats.len(); self.floats.push(v); i as u16 }
131    fn add_str(&mut self, s: &str) -> u16 { let i = self.strings.len(); self.strings.push(s.to_string()); i as u16 }
132}
133
134// ─── Compiled program ────────────────────────────────────────────────────────
135
136#[derive(Debug)]
137pub struct CompiledFunction {
138    pub name: String,
139    pub chunk: Chunk,
140    pub arg_count: u16,
141}
142
143#[derive(Debug)]
144pub struct VmProgram {
145    pub functions: Vec<CompiledFunction>,
146    pub main_index: usize,
147}
148
149// ─── Compiler ────────────────────────────────────────────────────────────────
150
151struct Ctx {
152    chunk: Chunk,
153    local_map: HashMap<Local, u16>,
154    local_next: u16,
155    bb_start: HashMap<usize, usize>,
156    patches: Vec<(usize, usize)>,
157}
158
159pub fn compile_mir_program(mir: &MirProgram) -> VmProgram {
160    let fn_names: Vec<String> = mir.functions.iter().map(|f| f.name.clone()).collect();
161    let mut functions = Vec::new();
162    let mut main_index = 0;
163
164    for (i, mir_fn) in mir.functions.iter().enumerate() {
165        let chunk = compile_fn(mir_fn, &fn_names);
166        if mir_fn.name == "__main__" || mir_fn.name == "start" || mir_fn.name == "เริ่ม" {
167            main_index = i;
168        }
169        functions.push(CompiledFunction {
170            name: mir_fn.name.clone(),
171            chunk,
172            arg_count: mir_fn.arg_count as u16,
173        });
174    }
175
176    if functions.is_empty() {
177        functions.push(CompiledFunction {
178            name: "__main__".into(),
179            chunk: Chunk { code: vec![OP_PUSHNONE, OP_RET], ..Default::default() },
180            arg_count: 0,
181        });
182    }
183
184    VmProgram { functions, main_index }
185}
186
187fn compile_fn(mir_fn: &MirFunction, fn_names: &[String]) -> Chunk {
188    let mut ctx = Ctx {
189        chunk: Chunk::default(),
190        local_map: HashMap::new(),
191        local_next: 0,
192        bb_start: HashMap::new(),
193        patches: Vec::new(),
194    };
195
196    // Local(0) = return slot at slot 0
197    ctx.local_map.insert(Local(0), 0);
198
199    // Args: Local(1)..Local(arg_count) get sequential slots
200    for i in 0..mir_fn.arg_count {
201        let local = Local(i + 1);
202        ctx.local_map.entry(local).or_insert_with(|| {
203            let s = ctx.local_next; ctx.local_next += 1; s
204        });
205    }
206
207    // Explicit locals get slots after args
208    for li in 0..mir_fn.locals.len() {
209        let local = Local(mir_fn.arg_count + 1 + li);
210        ctx.local_map.entry(local).or_insert_with(|| {
211            let s = ctx.local_next; ctx.local_next += 1; s
212        });
213    }
214
215    ctx.chunk.local_count = ctx.local_next;
216
217    for bb_idx in 0..mir_fn.basic_blocks.len() {
218        ctx.bb_start.insert(bb_idx, ctx.chunk.code.len());
219        let bb = &mir_fn.basic_blocks[bb_idx];
220        for stmt in &bb.statements {
221            compile_stmt(stmt, fn_names, &mut ctx);
222        }
223        if let Some(term) = &bb.terminator {
224            compile_term(term, fn_names, &mut ctx);
225        }
226    }
227
228    for &(pos, target) in &ctx.patches {
229        let target_offset = ctx.bb_start.get(&target).copied().unwrap_or(0);
230        ctx.chunk.code[pos..pos + 2].copy_from_slice(&(target_offset as u16).to_le_bytes());
231    }
232
233    ctx.chunk
234}
235
236fn compile_stmt(stmt: &Statement, fn_names: &[String], ctx: &mut Ctx) {
237    match &stmt.kind {
238        StatementKind::Assign(local, rvalue) => {
239            compile_rval(rvalue, fn_names, ctx);
240            let slot = *ctx.local_map.entry(*local).or_insert_with(|| {
241                let s = ctx.local_next; ctx.local_next += 1; s
242            });
243            ctx.chunk.w(OP_STORELOCAL);
244            ctx.chunk.w2(slot);
245        }
246        _ => {}
247    }
248}
249
250fn compile_rval(rv: &Rvalue, fn_names: &[String], ctx: &mut Ctx) {
251    match rv {
252        Rvalue::Use(op) => compile_op(op, fn_names, ctx),
253        Rvalue::BinaryOp(op, l, r) => {
254            compile_op(l, fn_names, ctx);
255            compile_op(r, fn_names, ctx);
256            ctx.chunk.w(match op {
257                BinOp::Add => OP_ADD, BinOp::Sub => OP_SUB,
258                BinOp::Mul => OP_MUL, BinOp::Div => OP_DIV,
259                BinOp::Rem => OP_REM, BinOp::Eq => OP_EQ,
260                BinOp::Ne => OP_NE, BinOp::Lt => OP_LT,
261                BinOp::Le => OP_LE, BinOp::Gt => OP_GT,
262                BinOp::Ge => OP_GE, BinOp::And => OP_AND,
263                BinOp::Or => OP_OR,
264                _ => OP_NOP, // unsupported ops become nop
265            });
266        }
267        Rvalue::UnaryOp(op, o) => {
268            compile_op(o, fn_names, ctx);
269            ctx.chunk.w(match op { UnOp::Not => OP_NOT, UnOp::Neg => OP_NEG, _ => OP_NOP });
270        }
271        Rvalue::Call { func, args } => {
272            for arg in args {
273                compile_op(arg, fn_names, ctx);
274            }
275            match func {
276                Operand::Constant(Constant::Function(name)) => {
277                    if let Some(idx) = fn_names.iter().position(|n| n == name) {
278                        ctx.chunk.w(OP_CALL);
279                        ctx.chunk.w2(idx as u16);
280                    } else {
281                        let sidx = ctx.chunk.add_str(name);
282                        ctx.chunk.w(OP_CALLBUILTIN);
283                        ctx.chunk.w2(sidx);
284                        ctx.chunk.w2(args.len() as u16);
285                    }
286                }
287                _ => {
288                    compile_op(func, fn_names, ctx);
289                    let sidx = ctx.chunk.add_str("__indirect");
290                    ctx.chunk.w(OP_CALLBUILTIN);
291                    ctx.chunk.w2(sidx);
292                    ctx.chunk.w2(args.len() as u16);
293                }
294            }
295        }
296        Rvalue::Aggregate(_kind, ops) => {
297            for op in ops {
298                compile_op(op, fn_names, ctx);
299            }
300            ctx.chunk.w(OP_MAKELIST);
301            ctx.chunk.w2(ops.len() as u16);
302        }
303        Rvalue::GetIndex(obj, idx) => {
304            compile_op(obj, fn_names, ctx);
305            compile_op(idx, fn_names, ctx);
306            ctx.chunk.w(OP_GETINDEX);
307        }
308        _ => { ctx.chunk.w(OP_PUSHNONE); }
309    }
310}
311
312fn compile_op(op: &Operand, _fn_names: &[String], ctx: &mut Ctx) {
313    match op {
314        Operand::Copy(l) | Operand::Move(l) => {
315            let slot = ctx.local_map.get(l).copied().unwrap_or(0);
316            ctx.chunk.w(OP_LOADLOCAL);
317            ctx.chunk.w2(slot);
318        }
319        Operand::Constant(c) => match c {
320            Constant::I64(v) => {
321                ctx.chunk.w(OP_PUSHI64);
322                ctx.chunk.w8(*v);
323            }
324            Constant::F64(bits) => {
325                let idx = ctx.chunk.add_float(f64::from_bits(*bits));
326                ctx.chunk.w(OP_PUSHF64);
327                ctx.chunk.w2(idx);
328            }
329            Constant::Str(s) => {
330                let idx = ctx.chunk.add_str(s);
331                ctx.chunk.w(OP_PUSHSTR);
332                ctx.chunk.w2(idx);
333            }
334            Constant::Bool(b) => {
335                ctx.chunk.w(OP_PUSHBOOL);
336                ctx.chunk.w(if *b { 1 } else { 0 });
337            }
338            _ => { ctx.chunk.w(OP_PUSHNONE); }
339        },
340    }
341}
342
343fn compile_term(term: &Terminator, fn_names: &[String], ctx: &mut Ctx) {
344    match &term.kind {
345        TerminatorKind::Return => {
346            ctx.chunk.w(OP_LOADLOCAL);
347            ctx.chunk.w2(0);
348            ctx.chunk.w(OP_RET);
349        }
350        TerminatorKind::Goto { target } => {
351            ctx.chunk.w(OP_JUMP);
352            let pos = ctx.chunk.code.len();
353            ctx.chunk.w2(0);
354            ctx.patches.push((pos, target.0));
355        }
356        TerminatorKind::SwitchInt { discr, targets, otherwise } => {
357            compile_op(discr, fn_names, ctx);
358            if let Some((val, target)) = targets.first() {
359                ctx.chunk.w(OP_PUSHI64);
360                ctx.chunk.w8(*val);
361                ctx.chunk.w(OP_EQ);
362                ctx.chunk.w(OP_JUMPIFFALSE);
363                let pos = ctx.chunk.code.len();
364                ctx.chunk.w2(0);
365                ctx.patches.push((pos, target.0));
366            }
367            ctx.chunk.w(OP_JUMP);
368            let pos = ctx.chunk.code.len();
369            ctx.chunk.w2(0);
370            ctx.patches.push((pos, otherwise.0));
371        }
372        TerminatorKind::Unreachable => { ctx.chunk.w(OP_HALT); }
373    }
374}
375
376fn read_u8(code: &[u8], ip: usize) -> u8 {
377    code[ip]
378}
379
380fn read_u16(code: &[u8], ip: usize) -> u16 {
381    let mut bytes = [0u8; 2];
382    bytes.copy_from_slice(&code[ip..ip + 2]);
383    u16::from_le_bytes(bytes)
384}
385
386fn read_i64(code: &[u8], ip: usize) -> i64 {
387    let mut bytes = [0u8; 8];
388    bytes.copy_from_slice(&code[ip..ip + 8]);
389    i64::from_le_bytes(bytes)
390}
391
392// ─── VM ──────────────────────────────────────────────────────────────────────
393
394struct Frame {
395    fn_index: usize,
396    ip: usize,
397    base: usize,
398    local_count: usize,
399}
400
401pub struct Vm {
402    stack: Vec<Value>,
403    program: Option<VmProgram>,
404    call_stack: Vec<Frame>,
405}
406
407impl Vm {
408    pub fn new() -> Self {
409        Self { stack: Vec::new(), program: None, call_stack: Vec::new() }
410    }
411
412    pub fn load(&mut self, program: VmProgram) {
413        self.program = Some(program);
414    }
415
416    pub fn run_main(&mut self) -> Result<(), String> {
417        let p = self.program.as_ref().ok_or("no program loaded")?;
418        if p.functions.is_empty() {
419            return Ok(());
420        }
421        let main_idx = p.main_index;
422        let func = &p.functions[main_idx];
423        let lc = func.chunk.local_count as usize;
424        for _ in 0..lc {
425            self.stack.push(Value::None);
426        }
427        self.call_stack.push(Frame { fn_index: main_idx, ip: 0, base: 0, local_count: lc });
428        self.exec()
429    }
430
431    fn exec(&mut self) -> Result<(), String> {
432        loop {
433            if self.call_stack.is_empty() {
434                return Ok(());
435            }
436
437            let frame_ip = self.call_stack.last().unwrap().ip;
438
439            // Pre-extract chunk data through an index to avoid holding cross-references
440            let fn_index = self.call_stack.last().unwrap().fn_index;
441            let (code, floats, strings) = {
442                let p = self.program.as_ref().unwrap();
443                let chunk = &p.functions[fn_index].chunk;
444                // Clone small headers to avoid holding borrow across mutable ops
445                (chunk.code.clone(), chunk.floats.clone(), chunk.strings.clone())
446            };
447            let code_len = code.len();
448
449            if frame_ip >= code_len {
450                self.call_stack.pop();
451                return Err("unexpected end of code".into());
452            }
453
454            let op = read_u8(&code, frame_ip);
455            let mut ip = frame_ip + 1;
456
457            match op {
458                OP_PUSHI64 => {
459                    let v = read_i64(&code, ip);
460                    ip += 8;
461                    self.stack.push(Value::Number(v as f64));
462                }
463                OP_PUSHF64 => {
464                    let idx = read_u16(&code, ip) as usize;
465                    ip += 2;
466                    self.stack.push(Value::Number(floats[idx]));
467                }
468                OP_PUSHSTR => {
469                    let idx = read_u16(&code, ip) as usize;
470                    ip += 2;
471                    self.stack.push(Value::Str(strings[idx].clone()));
472                }
473                OP_PUSHBOOL => {
474                    let b = read_u8(&code, ip) != 0;
475                    ip += 1;
476                    self.stack.push(Value::Bool(b));
477                }
478                OP_PUSHNONE => self.stack.push(Value::None),
479                OP_POP => { self.stack.pop(); }
480                OP_LOADLOCAL => {
481                    let idx = read_u16(&code, ip) as usize;
482                    ip += 2;
483                    let base = self.call_stack.last().unwrap().base;
484                    self.stack.push(self.stack[base + idx].clone());
485                }
486                OP_STORELOCAL => {
487                    let idx = read_u16(&code, ip) as usize;
488                    ip += 2;
489                    let val = self.stack.pop().unwrap();
490                    let base = self.call_stack.last().unwrap().base;
491                    self.stack[base + idx] = val;
492                }
493                OP_ADD | OP_SUB | OP_MUL | OP_DIV | OP_REM => {
494                    let b = self.stack.pop().unwrap();
495                    let a = self.stack.pop().unwrap();
496                    let r = match (op, &a, &b) {
497                        (OP_ADD, Value::Str(s), v) => Value::Str(format!("{}{}", s, v.display())),
498                        (OP_ADD, v, Value::Str(s)) => Value::Str(format!("{}{}", v.display(), s)),
499                        (OP_ADD, Value::Number(a), Value::Number(b)) => Value::Number(a + b),
500                        (OP_SUB, Value::Number(a), Value::Number(b)) => Value::Number(a - b),
501                        (OP_MUL, Value::Number(a), Value::Number(b)) => Value::Number(a * b),
502                        (OP_DIV, Value::Number(a), Value::Number(b)) => Value::Number(a / b),
503                        (OP_REM, Value::Number(a), Value::Number(b)) => Value::Number(a % b),
504                        _ => return Err("type error in arithmetic".into()),
505                    };
506                    self.stack.push(r);
507                }
508                OP_EQ | OP_NE | OP_LT | OP_LE | OP_GT | OP_GE => {
509                    let b = self.stack.pop().unwrap();
510                    let a = self.stack.pop().unwrap();
511                    let r = match op {
512                        OP_EQ => values_equal(&a, &b),
513                        OP_NE => !values_equal(&a, &b),
514                        OP_LT => num_cmp(&a, &b, |a, b| a < b),
515                        OP_LE => num_cmp(&a, &b, |a, b| a <= b),
516                        OP_GT => num_cmp(&a, &b, |a, b| a > b),
517                        OP_GE => num_cmp(&a, &b, |a, b| a >= b),
518                        _ => false,
519                    };
520                    self.stack.push(Value::Bool(r));
521                }
522                OP_AND => {
523                    let b = self.stack.pop().unwrap();
524                    let a = self.stack.pop().unwrap();
525                    self.stack.push(Value::Bool(a.is_truthy() && b.is_truthy()));
526                }
527                OP_OR => {
528                    let b = self.stack.pop().unwrap();
529                    let a = self.stack.pop().unwrap();
530                    self.stack.push(Value::Bool(a.is_truthy() || b.is_truthy()));
531                }
532                OP_NOT => {
533                    let v = self.stack.pop().unwrap();
534                    self.stack.push(Value::Bool(!v.is_truthy()));
535                }
536                OP_NEG => {
537                    let v = self.stack.pop().unwrap();
538                    if let Value::Number(n) = v { self.stack.push(Value::Number(-n)); }
539                    else { return Err("type error: negate non-number".into()); }
540                }
541                OP_CALL => {
542                    let idx = read_u16(&code, ip) as usize;
543                    ip += 2;
544                    let (ac, lc) = {
545                        let p = self.program.as_ref().unwrap();
546                        (p.functions[idx].arg_count as usize, p.functions[idx].chunk.local_count as usize)
547                    };
548                    let extra = lc.saturating_sub(ac);
549                    for _ in 0..extra { self.stack.push(Value::None); }
550                    let new_base = self.stack.len() - lc;
551                    self.call_stack.push(Frame { fn_index: idx, ip: 0, base: new_base, local_count: lc });
552                }
553                OP_CALLBUILTIN => {
554                    let si = read_u16(&code, ip) as usize;
555                    ip += 2;
556                    let count = read_u16(&code, ip) as usize;
557                    ip += 2;
558                    let name = strings[si].clone();
559                    let (canon, _) = resolve_builtin(&name).map_err(|e| format!("{} in {}", e, name))?;
560                    self.call_builtin(canon, count)?;
561                }
562                OP_RET => {
563                    let val = self.stack.pop().unwrap_or(Value::None);
564                    let _frame = self.call_stack.pop().unwrap();
565                    if let Some(caller) = self.call_stack.last() {
566                        self.stack.truncate(caller.base + caller.local_count);
567                        self.stack.push(val);
568                    } else {
569                        self.stack.push(val);
570                        return Ok(());
571                    }
572                }
573                OP_JUMP => {
574                    ip = read_u16(&code, ip) as usize;
575                }
576                OP_JUMPIFFALSE => {
577                    let offset = read_u16(&code, ip) as usize;
578                    ip += 2;
579                    let cond = self.stack.pop().unwrap();
580                    if !cond.is_truthy() { ip = offset; }
581                }
582                OP_MAKELIST => {
583                    let count = read_u16(&code, ip) as usize;
584                    ip += 2;
585                    let mut items = Vec::with_capacity(count);
586                    for _ in 0..count { items.push(self.stack.pop().unwrap()); }
587                    items.reverse();
588                    self.stack.push(Value::List(items));
589                }
590                OP_GETINDEX => {
591                    let idx_v = self.stack.pop().unwrap();
592                    let obj = self.stack.pop().unwrap();
593                    match (&obj, &idx_v) {
594                        (Value::List(list), Value::Number(n)) => {
595                            let i = *n as usize;
596                            self.stack.push(if i < list.len() { list[i].clone() } else { Value::None });
597                        }
598                        _ => self.stack.push(Value::None),
599                    }
600                }
601                OP_HALT => return Ok(()),
602                _ => return Err(format!("unknown opcode {} at ip {}", op, ip - 1)),
603            }
604
605            self.call_stack.last_mut().unwrap().ip = ip;
606        }
607    }
608
609    fn call_builtin(&mut self, name: &str, arg_count: usize) -> Result<(), String> {
610        match name {
611            "print" => {
612                if let Some(val) = self.stack.pop() {
613                    println!("{}", val.display());
614                }
615                self.stack.push(Value::None);
616            }
617            "format" => {
618                let mut parts: Vec<String> = Vec::with_capacity(arg_count);
619                for _ in 0..arg_count {
620                    if let Some(val) = self.stack.pop() {
621                        parts.push(val.display());
622                    }
623                }
624                parts.reverse();
625                if parts.is_empty() {
626                    self.stack.push(Value::Str(String::new()));
627                    return Ok(());
628                }
629                let fmt_str = parts.remove(0);
630                let mut result = fmt_str;
631                for part in parts {
632                    result = result.replacen("{}", &part, 1);
633                }
634                self.stack.push(Value::Str(result));
635            }
636            "len" => {
637                let val = self.stack.pop().unwrap_or(Value::None);
638                let n = match &val {
639                    Value::Str(s) => s.len() as f64,
640                    Value::List(l) => l.len() as f64,
641                    _ => 0.0,
642                };
643                self.stack.push(Value::Number(n));
644            }
645            "to_str" => {
646                let val = self.stack.pop().unwrap_or(Value::None);
647                self.stack.push(Value::Str(val.display()));
648            }
649            "sin" => {
650                let val = self.stack.pop().unwrap_or(Value::None);
651                if let Value::Number(n) = val {
652                    self.stack.push(Value::Number(n.sin()));
653                } else {
654                    self.stack.push(Value::Number(0.0));
655                }
656            }
657            "cos" => {
658                let val = self.stack.pop().unwrap_or(Value::None);
659                if let Value::Number(n) = val {
660                    self.stack.push(Value::Number(n.cos()));
661                } else {
662                    self.stack.push(Value::Number(0.0));
663                }
664            }
665            _ => return Err(format!("unimplemented builtin: {}", name)),
666        }
667        Ok(())
668    }
669}
670
671// ─── BytecodeBackend ─────────────────────────────────────────────────────────
672
673pub struct BytecodeBackend;
674
675impl CodegenBackend for BytecodeBackend {
676    fn emit(&mut self, program: &crate::MirProgram, out: &std::path::Path) -> anyhow::Result<()> {
677        let vm_prog = compile_mir_program(&program.mir);
678        let mut bytes = Vec::new();
679        bytes.extend_from_slice(b"LINGBC");
680        let fn_count = vm_prog.functions.len() as u32;
681        bytes.extend_from_slice(&fn_count.to_le_bytes());
682        for func in &vm_prog.functions {
683            let name_bytes = func.name.as_bytes();
684            bytes.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
685            bytes.extend_from_slice(name_bytes);
686            bytes.extend_from_slice(&func.arg_count.to_le_bytes());
687            bytes.extend_from_slice(&func.chunk.local_count.to_le_bytes());
688            bytes.extend_from_slice(&(func.chunk.code.len() as u32).to_le_bytes());
689            bytes.extend_from_slice(&func.chunk.code);
690            bytes.extend_from_slice(&(func.chunk.floats.len() as u32).to_le_bytes());
691            for &f in &func.chunk.floats {
692                bytes.extend_from_slice(&f.to_bits().to_le_bytes());
693            }
694            bytes.extend_from_slice(&(func.chunk.strings.len() as u32).to_le_bytes());
695            for s in &func.chunk.strings {
696                let sb = s.as_bytes();
697                bytes.extend_from_slice(&(sb.len() as u32).to_le_bytes());
698                bytes.extend_from_slice(sb);
699            }
700        }
701        std::fs::write(out, &bytes)?;
702        Ok(())
703    }
704}