Skip to main content

sema_vm/
vm.rs

1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::rc::Rc;
4
5use sema_core::{
6    resolve as resolve_spur, Env, EvalContext, SemaError, Spur, Value, NAN_INT_SIGN_BIT,
7    NAN_INT_SMALL_PATTERN, NAN_PAYLOAD_MASK, NAN_TAG_MASK,
8};
9
10use crate::chunk::Function;
11
12/// A mutable cell for captured variables (upvalues).
13#[derive(Debug)]
14pub struct UpvalueCell {
15    pub value: RefCell<Value>,
16}
17
18impl UpvalueCell {
19    pub fn new(value: Value) -> Self {
20        UpvalueCell {
21            value: RefCell::new(value),
22        }
23    }
24}
25
26/// A runtime closure: function template + captured upvalues.
27#[derive(Debug, Clone)]
28pub struct Closure {
29    pub func: Rc<Function>,
30    pub upvalues: Vec<Rc<UpvalueCell>>,
31}
32
33/// Payload stored in NativeFn for VM closures.
34/// Carries both the closure and the function table from its compilation context.
35struct VmClosurePayload {
36    closure: Rc<Closure>,
37    functions: Rc<Vec<Rc<Function>>>,
38}
39
40/// A call frame in the VM's call stack.
41struct CallFrame {
42    closure: Rc<Closure>,
43    pc: usize,
44    base: usize,
45    /// Open upvalue cells for locals in this frame.
46    /// Maps local slot → shared UpvalueCell. Created lazily when a local is captured.
47    /// `None` means no locals have been captured yet (avoids heap allocation).
48    open_upvalues: Option<Vec<Option<Rc<UpvalueCell>>>>,
49}
50
51/// Number of entries in the direct-mapped global cache (must be power of 2).
52const GLOBAL_CACHE_SIZE: usize = 16;
53
54/// The bytecode virtual machine.
55pub struct VM {
56    stack: Vec<Value>,
57    frames: Vec<CallFrame>,
58    globals: Rc<Env>,
59    functions: Rc<Vec<Rc<Function>>>,
60    /// Direct-mapped cache for global lookups: (spur_bits, env_version, value)
61    global_cache: [(u32, u64, Value); GLOBAL_CACHE_SIZE],
62}
63
64impl VM {
65    pub fn new(globals: Rc<Env>, functions: Vec<Rc<Function>>) -> Self {
66        VM {
67            stack: Vec::with_capacity(256),
68            frames: Vec::with_capacity(64),
69            globals,
70            functions: Rc::new(functions),
71            global_cache: std::array::from_fn(|_| (u32::MAX, u64::MAX, Value::nil())),
72        }
73    }
74
75    fn new_with_rc_functions(globals: Rc<Env>, functions: Rc<Vec<Rc<Function>>>) -> Self {
76        VM {
77            stack: Vec::with_capacity(256),
78            frames: Vec::with_capacity(64),
79            globals,
80            functions,
81            global_cache: std::array::from_fn(|_| (u32::MAX, u64::MAX, Value::nil())),
82        }
83    }
84
85    pub fn execute(&mut self, closure: Rc<Closure>, ctx: &EvalContext) -> Result<Value, SemaError> {
86        let base = self.stack.len();
87        // Reserve space for locals
88        let n_locals = closure.func.chunk.n_locals as usize;
89        for _ in 0..n_locals {
90            self.stack.push(Value::nil());
91        }
92        self.frames.push(CallFrame {
93            closure,
94            pc: 0,
95            base,
96            open_upvalues: None,
97        });
98        self.run(ctx)
99    }
100
101    fn run(&mut self, ctx: &EvalContext) -> Result<Value, SemaError> {
102        // Raw-pointer macros for reading operands without bounds checks in inner loop
103        macro_rules! read_u16 {
104            ($code:expr, $pc:expr) => {{
105                let v = unsafe { u16::from_le_bytes([*$code.add($pc), *$code.add($pc + 1)]) };
106                $pc += 2;
107                v
108            }};
109        }
110        macro_rules! read_i32 {
111            ($code:expr, $pc:expr) => {{
112                let v = unsafe {
113                    i32::from_le_bytes([
114                        *$code.add($pc),
115                        *$code.add($pc + 1),
116                        *$code.add($pc + 2),
117                        *$code.add($pc + 3),
118                    ])
119                };
120                $pc += 4;
121                v
122            }};
123        }
124        macro_rules! read_u32 {
125            ($code:expr, $pc:expr) => {{
126                let v = unsafe {
127                    u32::from_le_bytes([
128                        *$code.add($pc),
129                        *$code.add($pc + 1),
130                        *$code.add($pc + 2),
131                        *$code.add($pc + 3),
132                    ])
133                };
134                $pc += 4;
135                v
136            }};
137        }
138
139        // Two-level dispatch: outer loop caches frame locals, inner loop dispatches opcodes.
140        // We only break to the outer loop when frames change (Call/TailCall/Return/exceptions).
141        'dispatch: loop {
142            let fi = self.frames.len() - 1;
143            let frame = &self.frames[fi];
144            let code = frame.closure.func.chunk.code.as_ptr();
145            let consts: *const [Value] = frame.closure.func.chunk.consts.as_slice();
146            let base = frame.base;
147            let mut pc = frame.pc;
148            #[cfg(debug_assertions)]
149            let code_len = frame.closure.func.chunk.code.len();
150            let _ = frame; // release borrow so we can mutate self
151
152            loop {
153                #[cfg(debug_assertions)]
154                debug_assert!(pc < code_len, "pc {pc} out of bounds (len {code_len})");
155                let op = unsafe { *code.add(pc) };
156                pc += 1;
157
158                match op {
159                    // --- Constants & stack ---
160                    0 /* Const */ => {
161                        let idx = read_u16!(code, pc) as usize;
162                        let val = unsafe { &*consts }[idx].clone();
163                        self.stack.push(val);
164                    }
165                    1 /* Nil */ => {
166                        self.stack.push(Value::nil());
167                    }
168                    2 /* True */ => {
169                        self.stack.push(Value::bool(true));
170                    }
171                    3 /* False */ => {
172                        self.stack.push(Value::bool(false));
173                    }
174                    4 /* Pop */ => {
175                        self.stack.pop();
176                    }
177                    5 /* Dup */ => {
178                        let val = self.stack[self.stack.len() - 1].clone();
179                        self.stack.push(val);
180                    }
181
182                    // --- Locals ---
183                    6 /* LoadLocal */ => {
184                        let slot = read_u16!(code, pc) as usize;
185                        let val = if let Some(ref open) = self.frames[fi].open_upvalues {
186                            if let Some(Some(cell)) = open.get(slot) {
187                                cell.value.borrow().clone()
188                            } else {
189                                self.stack[base + slot].clone()
190                            }
191                        } else {
192                            self.stack[base + slot].clone()
193                        };
194                        self.stack.push(val);
195                    }
196                    7 /* StoreLocal */ => {
197                        let slot = read_u16!(code, pc) as usize;
198                        let val = self.stack.pop().unwrap();
199                        self.stack[base + slot] = val.clone();
200                        if let Some(ref open) = self.frames[fi].open_upvalues {
201                            if let Some(Some(cell)) = open.get(slot) {
202                                *cell.value.borrow_mut() = val;
203                            }
204                        }
205                    }
206
207                    // --- Upvalues ---
208                    8 /* LoadUpvalue */ => {
209                        let idx = read_u16!(code, pc) as usize;
210                        let val = self.frames[fi].closure.upvalues[idx].value.borrow().clone();
211                        self.stack.push(val);
212                    }
213                    9 /* StoreUpvalue */ => {
214                        let idx = read_u16!(code, pc) as usize;
215                        let val = self.stack.pop().unwrap();
216                        *self.frames[fi].closure.upvalues[idx].value.borrow_mut() = val;
217                    }
218
219                    // --- Globals ---
220                    10 /* LoadGlobal */ => {
221                        let bits = read_u32!(code, pc);
222                        let version = self.globals.version.get();
223                        let slot = (bits as usize) & (GLOBAL_CACHE_SIZE - 1);
224                        let entry = &self.global_cache[slot];
225                        if entry.0 == bits && entry.1 == version {
226                            self.stack.push(entry.2.clone());
227                        } else {
228                            let spur: Spur = unsafe { std::mem::transmute::<u32, Spur>(bits) };
229                            match self.globals.get(spur) {
230                                Some(val) => {
231                                    self.global_cache[slot] = (bits, version, val.clone());
232                                    self.stack.push(val);
233                                }
234                                None => {
235                                    self.frames[fi].pc = pc;
236                                    let err = SemaError::Unbound(resolve_spur(spur));
237                                    match self.handle_exception(err, pc - 5)? {
238                                        ExceptionAction::Handled => continue 'dispatch,
239                                        ExceptionAction::Propagate(e) => return Err(e),
240                                    }
241                                }
242                            }
243                        }
244                    }
245                    11 /* StoreGlobal */ => {
246                        let bits = read_u32!(code, pc);
247                        let spur: Spur = unsafe { std::mem::transmute::<u32, Spur>(bits) };
248                        let val = self.stack.pop().unwrap();
249                        if !self.globals.set_existing(spur, val.clone()) {
250                            self.globals.set(spur, val);
251                        }
252                    }
253                    12 /* DefineGlobal */ => {
254                        let bits = read_u32!(code, pc);
255                        let spur: Spur = unsafe { std::mem::transmute::<u32, Spur>(bits) };
256                        let val = self.stack.pop().unwrap();
257                        self.globals.set(spur, val);
258                    }
259
260                    // --- Control flow ---
261                    13 /* Jump */ => {
262                        let offset = read_i32!(code, pc);
263                        pc = (pc as i64 + offset as i64) as usize;
264                    }
265                    14 /* JumpIfFalse */ => {
266                        let offset = read_i32!(code, pc);
267                        let val = self.stack.pop().unwrap();
268                        if !val.is_truthy() {
269                            pc = (pc as i64 + offset as i64) as usize;
270                        }
271                    }
272                    15 /* JumpIfTrue */ => {
273                        let offset = read_i32!(code, pc);
274                        let val = self.stack.pop().unwrap();
275                        if val.is_truthy() {
276                            pc = (pc as i64 + offset as i64) as usize;
277                        }
278                    }
279
280                    // --- Function calls ---
281                    16 /* Call */ => {
282                        let argc = read_u16!(code, pc) as usize;
283                        self.frames[fi].pc = pc;
284                        let saved_pc = pc - 3;
285                        if let Err(err) = self.call_value(argc, ctx) {
286                            match self.handle_exception(err, saved_pc)? {
287                                ExceptionAction::Handled => {}
288                                ExceptionAction::Propagate(e) => return Err(e),
289                            }
290                        }
291                        continue 'dispatch;
292                    }
293                    17 /* TailCall */ => {
294                        let argc = read_u16!(code, pc) as usize;
295                        self.frames[fi].pc = pc;
296                        let saved_pc = pc - 3;
297                        if let Err(err) = self.tail_call_value(argc, ctx) {
298                            match self.handle_exception(err, saved_pc)? {
299                                ExceptionAction::Handled => {}
300                                ExceptionAction::Propagate(e) => return Err(e),
301                            }
302                        }
303                        continue 'dispatch;
304                    }
305                    18 /* Return */ => {
306                        let result = self.stack.pop().unwrap_or(Value::nil());
307                        let frame = self.frames.pop().unwrap();
308                        self.stack.truncate(frame.base);
309                        if self.frames.is_empty() {
310                            return Ok(result);
311                        }
312                        self.stack.push(result);
313                        continue 'dispatch;
314                    }
315
316                    // --- Closures ---
317                    19 /* MakeClosure */ => {
318                        self.frames[fi].pc = pc - 1; // make_closure reads from frame.pc (the opcode position)
319                        self.make_closure()?;
320                        continue 'dispatch;
321                    }
322
323                    20 /* CallNative */ => {
324                        let _native_id = read_u16!(code, pc);
325                        let _argc = read_u16!(code, pc);
326                        self.frames[fi].pc = pc;
327                        return Err(SemaError::eval("VM: CallNative not yet implemented"));
328                    }
329
330                    // --- Data constructors ---
331                    21 /* MakeList */ => {
332                        let n = read_u16!(code, pc) as usize;
333                        let start = self.stack.len() - n;
334                        let items: Vec<Value> = self.stack.drain(start..).collect();
335                        self.stack.push(Value::list(items));
336                    }
337                    22 /* MakeVector */ => {
338                        let n = read_u16!(code, pc) as usize;
339                        let start = self.stack.len() - n;
340                        let items: Vec<Value> = self.stack.drain(start..).collect();
341                        self.stack.push(Value::vector(items));
342                    }
343                    23 /* MakeMap */ => {
344                        let n = read_u16!(code, pc) as usize;
345                        let start = self.stack.len() - n * 2;
346                        let items: Vec<Value> = self.stack.drain(start..).collect();
347                        let mut map = BTreeMap::new();
348                        for pair in items.chunks(2) {
349                            map.insert(pair[0].clone(), pair[1].clone());
350                        }
351                        self.stack.push(Value::map(map));
352                    }
353                    24 /* MakeHashMap */ => {
354                        let n = read_u16!(code, pc) as usize;
355                        let start = self.stack.len() - n * 2;
356                        let items: Vec<Value> = self.stack.drain(start..).collect();
357                        let mut map = hashbrown::HashMap::new();
358                        for pair in items.chunks(2) {
359                            map.insert(pair[0].clone(), pair[1].clone());
360                        }
361                        self.stack.push(Value::hashmap_from_rc(Rc::new(map)));
362                    }
363
364                    // --- Exceptions ---
365                    25 /* Throw */ => {
366                        self.frames[fi].pc = pc;
367                        let val = self.stack.pop().unwrap();
368                        let err = SemaError::UserException(val);
369                        match self.handle_exception(err, pc - 1)? {
370                            ExceptionAction::Handled => continue 'dispatch,
371                            ExceptionAction::Propagate(e) => return Err(e),
372                        }
373                    }
374
375                    // --- Arithmetic ---
376                    26 /* Add */ => {
377                        let b = self.stack.pop().unwrap();
378                        let a = self.stack.pop().unwrap();
379                        match vm_add(&a, &b) {
380                            Ok(v) => self.stack.push(v),
381                            Err(err) => {
382                                self.frames[fi].pc = pc;
383                                match self.handle_exception(err, pc - 1)? {
384                                    ExceptionAction::Handled => continue 'dispatch,
385                                    ExceptionAction::Propagate(e) => return Err(e),
386                                }
387                            }
388                        }
389                    }
390                    27 /* Sub */ => {
391                        let b = self.stack.pop().unwrap();
392                        let a = self.stack.pop().unwrap();
393                        match vm_sub(&a, &b) {
394                            Ok(v) => self.stack.push(v),
395                            Err(err) => {
396                                self.frames[fi].pc = pc;
397                                match self.handle_exception(err, pc - 1)? {
398                                    ExceptionAction::Handled => continue 'dispatch,
399                                    ExceptionAction::Propagate(e) => return Err(e),
400                                }
401                            }
402                        }
403                    }
404                    28 /* Mul */ => {
405                        let b = self.stack.pop().unwrap();
406                        let a = self.stack.pop().unwrap();
407                        match vm_mul(&a, &b) {
408                            Ok(v) => self.stack.push(v),
409                            Err(err) => {
410                                self.frames[fi].pc = pc;
411                                match self.handle_exception(err, pc - 1)? {
412                                    ExceptionAction::Handled => continue 'dispatch,
413                                    ExceptionAction::Propagate(e) => return Err(e),
414                                }
415                            }
416                        }
417                    }
418                    29 /* Div */ => {
419                        let b = self.stack.pop().unwrap();
420                        let a = self.stack.pop().unwrap();
421                        match vm_div(&a, &b) {
422                            Ok(v) => self.stack.push(v),
423                            Err(err) => {
424                                self.frames[fi].pc = pc;
425                                match self.handle_exception(err, pc - 1)? {
426                                    ExceptionAction::Handled => continue 'dispatch,
427                                    ExceptionAction::Propagate(e) => return Err(e),
428                                }
429                            }
430                        }
431                    }
432                    30 /* Negate */ => {
433                        let a = self.stack.pop().unwrap();
434                        if let Some(n) = a.as_int() {
435                            self.stack.push(Value::int(-n));
436                        } else if let Some(f) = a.as_float() {
437                            self.stack.push(Value::float(-f));
438                        } else {
439                            self.frames[fi].pc = pc;
440                            let err = SemaError::type_error("number", a.type_name());
441                            match self.handle_exception(err, pc - 1)? {
442                                ExceptionAction::Handled => continue 'dispatch,
443                                ExceptionAction::Propagate(e) => return Err(e),
444                            }
445                        }
446                    }
447                    31 /* Not */ => {
448                        let a = self.stack.pop().unwrap();
449                        self.stack.push(Value::bool(!a.is_truthy()));
450                    }
451                    32 /* Eq */ => {
452                        let b = self.stack.pop().unwrap();
453                        let a = self.stack.pop().unwrap();
454                        self.stack.push(Value::bool(a == b));
455                    }
456                    33 /* Lt */ => {
457                        let b = self.stack.pop().unwrap();
458                        let a = self.stack.pop().unwrap();
459                        match vm_lt(&a, &b) {
460                            Ok(v) => self.stack.push(Value::bool(v)),
461                            Err(err) => {
462                                self.frames[fi].pc = pc;
463                                match self.handle_exception(err, pc - 1)? {
464                                    ExceptionAction::Handled => continue 'dispatch,
465                                    ExceptionAction::Propagate(e) => return Err(e),
466                                }
467                            }
468                        }
469                    }
470                    34 /* Gt */ => {
471                        let b = self.stack.pop().unwrap();
472                        let a = self.stack.pop().unwrap();
473                        match vm_lt(&b, &a) {
474                            Ok(v) => self.stack.push(Value::bool(v)),
475                            Err(err) => {
476                                self.frames[fi].pc = pc;
477                                match self.handle_exception(err, pc - 1)? {
478                                    ExceptionAction::Handled => continue 'dispatch,
479                                    ExceptionAction::Propagate(e) => return Err(e),
480                                }
481                            }
482                        }
483                    }
484                    35 /* Le */ => {
485                        let b = self.stack.pop().unwrap();
486                        let a = self.stack.pop().unwrap();
487                        match vm_lt(&b, &a) {
488                            Ok(v) => self.stack.push(Value::bool(!v)),
489                            Err(err) => {
490                                self.frames[fi].pc = pc;
491                                match self.handle_exception(err, pc - 1)? {
492                                    ExceptionAction::Handled => continue 'dispatch,
493                                    ExceptionAction::Propagate(e) => return Err(e),
494                                }
495                            }
496                        }
497                    }
498                    36 /* Ge */ => {
499                        let b = self.stack.pop().unwrap();
500                        let a = self.stack.pop().unwrap();
501                        match vm_lt(&a, &b) {
502                            Ok(v) => self.stack.push(Value::bool(!v)),
503                            Err(err) => {
504                                self.frames[fi].pc = pc;
505                                match self.handle_exception(err, pc - 1)? {
506                                    ExceptionAction::Handled => continue 'dispatch,
507                                    ExceptionAction::Propagate(e) => return Err(e),
508                                }
509                            }
510                        }
511                    }
512
513                    // --- Specialized int fast paths ---
514                    // These operate directly on raw u64 bits to avoid Clone/Drop overhead.
515                    // Small ints are immediates (no heap pointer), so we can safely
516                    // overwrite stack slots and adjust length without running destructors.
517                    37 /* AddInt */ => {
518                        let len = self.stack.len();
519                        let a_bits = unsafe { (*self.stack.as_ptr().add(len - 2)).raw_bits() };
520                        let b_bits = unsafe { (*self.stack.as_ptr().add(len - 1)).raw_bits() };
521                        if (a_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
522                            && (b_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
523                        {
524                            let sum = (a_bits.wrapping_add(b_bits)) & NAN_PAYLOAD_MASK;
525                            let result = NAN_INT_SMALL_PATTERN | sum;
526                            unsafe {
527                                std::ptr::write(
528                                    self.stack.as_mut_ptr().add(len - 2),
529                                    Value::from_raw_bits(result),
530                                );
531                                self.stack.set_len(len - 1);
532                            }
533                        } else {
534                            let b = self.stack.pop().unwrap();
535                            let a = self.stack.pop().unwrap();
536                            match vm_add(&a, &b) {
537                                Ok(v) => self.stack.push(v),
538                                Err(err) => {
539                                    self.frames[fi].pc = pc;
540                                    match self.handle_exception(err, pc - 1)? {
541                                        ExceptionAction::Handled => continue 'dispatch,
542                                        ExceptionAction::Propagate(e) => return Err(e),
543                                    }
544                                }
545                            }
546                        }
547                    }
548                    38 /* SubInt */ => {
549                        let len = self.stack.len();
550                        let a_bits = unsafe { (*self.stack.as_ptr().add(len - 2)).raw_bits() };
551                        let b_bits = unsafe { (*self.stack.as_ptr().add(len - 1)).raw_bits() };
552                        if (a_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
553                            && (b_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
554                        {
555                            let diff = (a_bits.wrapping_sub(b_bits)) & NAN_PAYLOAD_MASK;
556                            let result = NAN_INT_SMALL_PATTERN | diff;
557                            unsafe {
558                                std::ptr::write(
559                                    self.stack.as_mut_ptr().add(len - 2),
560                                    Value::from_raw_bits(result),
561                                );
562                                self.stack.set_len(len - 1);
563                            }
564                        } else {
565                            let b = self.stack.pop().unwrap();
566                            let a = self.stack.pop().unwrap();
567                            match vm_sub(&a, &b) {
568                                Ok(v) => self.stack.push(v),
569                                Err(err) => {
570                                    self.frames[fi].pc = pc;
571                                    match self.handle_exception(err, pc - 1)? {
572                                        ExceptionAction::Handled => continue 'dispatch,
573                                        ExceptionAction::Propagate(e) => return Err(e),
574                                    }
575                                }
576                            }
577                        }
578                    }
579                    39 /* MulInt */ => {
580                        let len = self.stack.len();
581                        let a_bits = unsafe { (*self.stack.as_ptr().add(len - 2)).raw_bits() };
582                        let b_bits = unsafe { (*self.stack.as_ptr().add(len - 1)).raw_bits() };
583                        if (a_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
584                            && (b_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
585                        {
586                            // Must sign-extend to i64 for correct multiplication
587                            let a_payload = a_bits & NAN_PAYLOAD_MASK;
588                            let b_payload = b_bits & NAN_PAYLOAD_MASK;
589                            let ax = if a_payload & NAN_INT_SIGN_BIT != 0 {
590                                (a_payload | !NAN_PAYLOAD_MASK) as i64
591                            } else {
592                                a_payload as i64
593                            };
594                            let bx = if b_payload & NAN_INT_SIGN_BIT != 0 {
595                                (b_payload | !NAN_PAYLOAD_MASK) as i64
596                            } else {
597                                b_payload as i64
598                            };
599                            // Use Value::int for multiplication — result may overflow 45 bits
600                            unsafe {
601                                std::ptr::write(
602                                    self.stack.as_mut_ptr().add(len - 2),
603                                    Value::int(ax.wrapping_mul(bx)),
604                                );
605                                self.stack.set_len(len - 1);
606                            }
607                        } else {
608                            let b = self.stack.pop().unwrap();
609                            let a = self.stack.pop().unwrap();
610                            match vm_mul(&a, &b) {
611                                Ok(v) => self.stack.push(v),
612                                Err(err) => {
613                                    self.frames[fi].pc = pc;
614                                    match self.handle_exception(err, pc - 1)? {
615                                        ExceptionAction::Handled => continue 'dispatch,
616                                        ExceptionAction::Propagate(e) => return Err(e),
617                                    }
618                                }
619                            }
620                        }
621                    }
622                    40 /* LtInt */ => {
623                        let len = self.stack.len();
624                        let a_bits = unsafe { (*self.stack.as_ptr().add(len - 2)).raw_bits() };
625                        let b_bits = unsafe { (*self.stack.as_ptr().add(len - 1)).raw_bits() };
626                        if (a_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
627                            && (b_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
628                        {
629                            // Sign-extend payloads and compare
630                            let a_payload = a_bits & NAN_PAYLOAD_MASK;
631                            let b_payload = b_bits & NAN_PAYLOAD_MASK;
632                            let ax = if a_payload & NAN_INT_SIGN_BIT != 0 {
633                                (a_payload | !NAN_PAYLOAD_MASK) as i64
634                            } else {
635                                a_payload as i64
636                            };
637                            let bx = if b_payload & NAN_INT_SIGN_BIT != 0 {
638                                (b_payload | !NAN_PAYLOAD_MASK) as i64
639                            } else {
640                                b_payload as i64
641                            };
642                            unsafe {
643                                std::ptr::write(
644                                    self.stack.as_mut_ptr().add(len - 2),
645                                    Value::bool(ax < bx),
646                                );
647                                self.stack.set_len(len - 1);
648                            }
649                        } else {
650                            let b = self.stack.pop().unwrap();
651                            let a = self.stack.pop().unwrap();
652                            match vm_lt(&a, &b) {
653                                Ok(v) => self.stack.push(Value::bool(v)),
654                                Err(err) => {
655                                    self.frames[fi].pc = pc;
656                                    match self.handle_exception(err, pc - 1)? {
657                                        ExceptionAction::Handled => continue 'dispatch,
658                                        ExceptionAction::Propagate(e) => return Err(e),
659                                    }
660                                }
661                            }
662                        }
663                    }
664                    41 /* EqInt */ => {
665                        let len = self.stack.len();
666                        let a_bits = unsafe { (*self.stack.as_ptr().add(len - 2)).raw_bits() };
667                        let b_bits = unsafe { (*self.stack.as_ptr().add(len - 1)).raw_bits() };
668                        if (a_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
669                            && (b_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
670                        {
671                            // Small ints: equal iff same bits
672                            unsafe {
673                                std::ptr::write(
674                                    self.stack.as_mut_ptr().add(len - 2),
675                                    Value::bool(a_bits == b_bits),
676                                );
677                                self.stack.set_len(len - 1);
678                            }
679                        } else {
680                            let b = self.stack.pop().unwrap();
681                            let a = self.stack.pop().unwrap();
682                            self.stack.push(Value::bool(a == b));
683                        }
684                    }
685
686                    42 /* LoadLocal0 */ => {
687                        let val = if let Some(ref open) = self.frames[fi].open_upvalues {
688                            if let Some(Some(cell)) = open.first() {
689                                cell.value.borrow().clone()
690                            } else {
691                                self.stack[base].clone()
692                            }
693                        } else {
694                            self.stack[base].clone()
695                        };
696                        self.stack.push(val);
697                    }
698                    43 /* LoadLocal1 */ => {
699                        let val = if let Some(ref open) = self.frames[fi].open_upvalues {
700                            if let Some(Some(cell)) = open.get(1) {
701                                cell.value.borrow().clone()
702                            } else {
703                                self.stack[base + 1].clone()
704                            }
705                        } else {
706                            self.stack[base + 1].clone()
707                        };
708                        self.stack.push(val);
709                    }
710                    44 /* LoadLocal2 */ => {
711                        let val = if let Some(ref open) = self.frames[fi].open_upvalues {
712                            if let Some(Some(cell)) = open.get(2) {
713                                cell.value.borrow().clone()
714                            } else {
715                                self.stack[base + 2].clone()
716                            }
717                        } else {
718                            self.stack[base + 2].clone()
719                        };
720                        self.stack.push(val);
721                    }
722                    45 /* LoadLocal3 */ => {
723                        let val = if let Some(ref open) = self.frames[fi].open_upvalues {
724                            if let Some(Some(cell)) = open.get(3) {
725                                cell.value.borrow().clone()
726                            } else {
727                                self.stack[base + 3].clone()
728                            }
729                        } else {
730                            self.stack[base + 3].clone()
731                        };
732                        self.stack.push(val);
733                    }
734
735                    _ => {
736                        return Err(SemaError::eval(format!("VM: invalid opcode {}", op)));
737                    }
738                }
739            }
740        }
741    }
742
743    // --- Function call implementation ---
744
745    fn call_value(&mut self, argc: usize, ctx: &EvalContext) -> Result<(), SemaError> {
746        let func_idx = self.stack.len() - 1 - argc;
747
748        // Fast path: peek at tag without Rc refcount bump.
749        // TAG_NATIVE_FN = 15 in the NaN-boxing layout.
750        if self.stack[func_idx].raw_tag() == Some(15) {
751            // Check for VM closure payload without holding a borrow across mutation.
752            // Extract closure+functions (cloned Rc) in a block, then call with them.
753            let vm_closure_data = {
754                let native = self.stack[func_idx].as_native_fn_ref().unwrap();
755                native.payload.as_ref().and_then(|p| {
756                    p.downcast_ref::<VmClosurePayload>()
757                        .map(|vmc| (vmc.closure.clone(), vmc.functions.clone()))
758                })
759            };
760            if let Some((closure, functions)) = vm_closure_data {
761                self.functions = functions;
762                return self.call_vm_closure_from_rc(&closure, argc);
763            }
764            // Regular native fn — need Rc for the call
765            let func_rc = self.stack[func_idx].as_native_fn_rc().unwrap();
766            let args_start = func_idx + 1;
767            let args: Vec<Value> = self.stack[args_start..].to_vec();
768            self.stack.truncate(func_idx);
769            let result = (func_rc.func)(ctx, &args)?;
770            self.stack.push(result);
771            Ok(())
772        } else if let Some(kw) = self.stack[func_idx].as_keyword_spur() {
773            // Keyword as function: (kw map) -> map[kw]
774            if argc != 1 {
775                return Err(SemaError::arity(resolve_spur(kw), "1", argc));
776            }
777            let arg = self.stack.pop().unwrap();
778            self.stack.pop(); // pop keyword
779            let kw_val = Value::keyword_from_spur(kw);
780            let result = if let Some(m) = arg.as_map_rc() {
781                m.get(&kw_val).cloned().unwrap_or(Value::nil())
782            } else if let Some(m) = arg.as_hashmap_rc() {
783                m.get(&kw_val).cloned().unwrap_or(Value::nil())
784            } else {
785                return Err(SemaError::type_error("map or hashmap", arg.type_name()));
786            };
787            self.stack.push(result);
788            Ok(())
789        } else {
790            // Lambda or other callable — clone once and use call_callback
791            let func_val = self.stack[func_idx].clone();
792            let args_start = func_idx + 1;
793            let args: Vec<Value> = self.stack[args_start..].to_vec();
794            self.stack.truncate(func_idx);
795            let result = sema_core::call_callback(ctx, &func_val, &args)?;
796            self.stack.push(result);
797            Ok(())
798        }
799    }
800
801    fn tail_call_value(&mut self, argc: usize, ctx: &EvalContext) -> Result<(), SemaError> {
802        let func_idx = self.stack.len() - 1 - argc;
803
804        // Fast path: peek at tag without Rc refcount bump
805        if self.stack[func_idx].raw_tag() == Some(15) {
806            let vm_closure_data = {
807                let native = self.stack[func_idx].as_native_fn_ref().unwrap();
808                native.payload.as_ref().and_then(|p| {
809                    p.downcast_ref::<VmClosurePayload>()
810                        .map(|vmc| (vmc.closure.clone(), vmc.functions.clone()))
811                })
812            };
813            if let Some((closure, functions)) = vm_closure_data {
814                self.functions = functions;
815                return self.tail_call_vm_closure_from_rc(&closure, argc);
816            }
817        }
818
819        // Non-VM callables: regular call (no TCO possible)
820        self.call_value(argc, ctx)
821    }
822
823    /// Push a new CallFrame for a VM closure (no Rust recursion).
824    /// Caller must set `self.functions` before calling this.
825    fn call_vm_closure_from_rc(
826        &mut self,
827        closure: &Rc<Closure>,
828        argc: usize,
829    ) -> Result<(), SemaError> {
830        let closure = closure.clone();
831        let func = &closure.func;
832        let arity = func.arity as usize;
833        let has_rest = func.has_rest;
834        let n_locals = func.chunk.n_locals as usize;
835
836        // Arity check
837        if has_rest {
838            if argc < arity {
839                return Err(SemaError::arity(
840                    func.name
841                        .map(resolve_spur)
842                        .unwrap_or_else(|| "<lambda>".to_string()),
843                    format!("{}+", arity),
844                    argc,
845                ));
846            }
847        } else if argc != arity {
848            return Err(SemaError::arity(
849                func.name
850                    .map(resolve_spur)
851                    .unwrap_or_else(|| "<lambda>".to_string()),
852                arity.to_string(),
853                argc,
854            ));
855        }
856
857        // Copy args directly from stack into new locals — no Vec allocation
858        let func_idx = self.stack.len() - 1 - argc;
859        let base = func_idx; // reuse the callee's slot as new frame base
860
861        // Copy params first (forward copy: dest[base+i] < src[func_idx+1+i], always safe)
862        if has_rest {
863            let rest: Vec<Value> = self.stack[func_idx + 1 + arity..func_idx + 1 + argc].to_vec();
864            for i in 0..arity {
865                self.stack[base + i] = self.stack[func_idx + 1 + i].clone();
866            }
867            self.stack[base + arity] = Value::list(rest);
868        } else {
869            for i in 0..arity {
870                self.stack[base + i] = self.stack[func_idx + 1 + i].clone();
871            }
872        }
873
874        // Now resize to exact local count (pads with nil or truncates excess args)
875        self.stack.resize(base + n_locals, Value::nil());
876
877        // Push frame
878        self.frames.push(CallFrame {
879            closure,
880            pc: 0,
881            base,
882            open_upvalues: None,
883        });
884
885        Ok(())
886    }
887
888    /// Tail-call a VM closure: reuse the current frame's stack space.
889    /// Caller must set `self.functions` before calling this.
890    fn tail_call_vm_closure_from_rc(
891        &mut self,
892        closure: &Rc<Closure>,
893        argc: usize,
894    ) -> Result<(), SemaError> {
895        let closure = closure.clone();
896        let func = &closure.func;
897        let arity = func.arity as usize;
898        let has_rest = func.has_rest;
899        let n_locals = func.chunk.n_locals as usize;
900
901        // Arity check
902        if has_rest {
903            if argc < arity {
904                return Err(SemaError::arity(
905                    func.name
906                        .map(resolve_spur)
907                        .unwrap_or_else(|| "<lambda>".to_string()),
908                    format!("{}+", arity),
909                    argc,
910                ));
911            }
912        } else if argc != arity {
913            return Err(SemaError::arity(
914                func.name
915                    .map(resolve_spur)
916                    .unwrap_or_else(|| "<lambda>".to_string()),
917                arity.to_string(),
918                argc,
919            ));
920        }
921
922        // Copy args directly into current frame's base — no Vec allocation
923        let func_idx = self.stack.len() - 1 - argc;
924        let base = self.frames.last().unwrap().base;
925
926        // Copy args into base slots (args are above base, no overlap issues)
927        if has_rest {
928            let rest: Vec<Value> = self.stack[func_idx + 1 + arity..func_idx + 1 + argc].to_vec();
929            for i in 0..arity {
930                self.stack[base + i] = self.stack[func_idx + 1 + i].clone();
931            }
932            self.stack[base + arity] = Value::list(rest);
933        } else {
934            for i in 0..arity {
935                self.stack[base + i] = self.stack[func_idx + 1 + i].clone();
936            }
937        }
938
939        // Resize to exact local count (pads with nil or truncates excess)
940        self.stack.resize(base + n_locals, Value::nil());
941
942        // Replace current frame (reuse slot)
943        let frame = self.frames.last_mut().unwrap();
944        frame.closure = closure;
945        frame.pc = 0;
946        // base stays the same
947        frame.open_upvalues = None;
948
949        Ok(())
950    }
951
952    // --- MakeClosure ---
953
954    fn make_closure(&mut self) -> Result<(), SemaError> {
955        // Read instruction operands from the current frame's bytecode.
956        // We extract everything we need first, then release the borrow.
957        let frame = self.frames.last().unwrap();
958        let code = &frame.closure.func.chunk.code;
959        let pc = frame.pc + 1;
960        let func_id = u16::from_le_bytes([code[pc], code[pc + 1]]) as usize;
961        let n_upvalues = u16::from_le_bytes([code[pc + 2], code[pc + 3]]) as usize;
962
963        // Collect upvalue descriptors
964        let mut uv_descs = Vec::with_capacity(n_upvalues);
965        let mut uv_pc = pc + 4;
966        for _ in 0..n_upvalues {
967            let is_local = u16::from_le_bytes([code[uv_pc], code[uv_pc + 1]]);
968            let idx = u16::from_le_bytes([code[uv_pc + 2], code[uv_pc + 3]]) as usize;
969            uv_pc += 4;
970            uv_descs.push((is_local != 0, idx));
971        }
972
973        let base = frame.base;
974        let parent_upvalues = frame.closure.upvalues.clone();
975        // Release the immutable borrow before mutating
976        let _ = frame;
977
978        let func = self.functions[func_id].clone();
979        let mut upvalues = Vec::with_capacity(n_upvalues);
980
981        for (is_local, idx) in &uv_descs {
982            if *is_local {
983                // Capture from current frame's local slot using a shared UpvalueCell.
984                // Lazily allocate open_upvalues on first capture.
985                let frame = self.frames.last_mut().unwrap();
986                let n_locals = frame.closure.func.chunk.n_locals as usize;
987                let open = frame
988                    .open_upvalues
989                    .get_or_insert_with(|| vec![None; n_locals]);
990                let cell = if let Some(existing) = &open[*idx] {
991                    existing.clone()
992                } else {
993                    let val = self.stack[base + *idx].clone();
994                    let cell = Rc::new(UpvalueCell::new(val));
995                    open[*idx] = Some(cell.clone());
996                    cell
997                };
998                upvalues.push(cell);
999            } else {
1000                // Capture from current frame's upvalue
1001                upvalues.push(parent_upvalues[*idx].clone());
1002            }
1003        }
1004
1005        // Update pc past the entire instruction
1006        self.frames.last_mut().unwrap().pc = uv_pc;
1007
1008        let closure = Rc::new(Closure { func, upvalues });
1009        let payload: Rc<dyn std::any::Any> = Rc::new(VmClosurePayload {
1010            closure: closure.clone(),
1011            functions: self.functions.clone(),
1012        });
1013        let closure_for_fallback = closure.clone();
1014        let functions = self.functions.clone();
1015        let globals = self.globals.clone();
1016
1017        // The NativeFn wrapper is used as a fallback when called from outside the VM
1018        // (e.g., from stdlib HOFs like map/filter). Inside the VM, call_value detects
1019        // the payload and pushes a CallFrame instead — no Rust recursion.
1020        let native = Value::native_fn_from_rc(Rc::new(sema_core::NativeFn::with_payload(
1021            closure_for_fallback
1022                .func
1023                .name
1024                .map(resolve_spur)
1025                .unwrap_or_else(|| "<vm-closure>".to_string()),
1026            payload,
1027            move |ctx, args| {
1028                let mut vm = VM::new_with_rc_functions(globals.clone(), functions.clone());
1029                let func = &closure_for_fallback.func;
1030                let arity = func.arity as usize;
1031                let has_rest = func.has_rest;
1032                let n_locals = func.chunk.n_locals as usize;
1033
1034                if has_rest {
1035                    if args.len() < arity {
1036                        return Err(SemaError::arity(
1037                            func.name
1038                                .map(resolve_spur)
1039                                .unwrap_or_else(|| "<lambda>".to_string()),
1040                            format!("{}+", arity),
1041                            args.len(),
1042                        ));
1043                    }
1044                } else if args.len() != arity {
1045                    return Err(SemaError::arity(
1046                        func.name
1047                            .map(resolve_spur)
1048                            .unwrap_or_else(|| "<lambda>".to_string()),
1049                        arity.to_string(),
1050                        args.len(),
1051                    ));
1052                }
1053
1054                for _ in 0..n_locals {
1055                    vm.stack.push(Value::nil());
1056                }
1057
1058                if has_rest {
1059                    for i in 0..arity {
1060                        vm.stack[i] = args.get(i).cloned().unwrap_or(Value::nil());
1061                    }
1062                    let rest: Vec<Value> = args[arity..].to_vec();
1063                    vm.stack[arity] = Value::list(rest);
1064                } else {
1065                    for i in 0..arity {
1066                        vm.stack[i] = args.get(i).cloned().unwrap_or(Value::nil());
1067                    }
1068                }
1069
1070                vm.frames.push(CallFrame {
1071                    closure: closure_for_fallback.clone(),
1072                    pc: 0,
1073                    base: 0,
1074                    open_upvalues: None,
1075                });
1076                vm.run(ctx)
1077            },
1078        )));
1079
1080        self.stack.push(native);
1081        Ok(())
1082    }
1083
1084    // --- Exception handling ---
1085
1086    fn handle_exception(
1087        &mut self,
1088        err: SemaError,
1089        failing_pc: usize,
1090    ) -> Result<ExceptionAction, SemaError> {
1091        let mut pc_for_lookup = failing_pc as u32;
1092        // Walk frames from top looking for a handler
1093        while let Some(frame) = self.frames.last() {
1094            let chunk = &frame.closure.func.chunk;
1095
1096            // Check exception table for this frame
1097            let mut found = None;
1098            for entry in &chunk.exception_table {
1099                if pc_for_lookup >= entry.try_start && pc_for_lookup < entry.try_end {
1100                    found = Some(entry.clone());
1101                    break;
1102                }
1103            }
1104
1105            if let Some(entry) = found {
1106                // Restore stack to handler state
1107                let base = frame.base;
1108                self.stack.truncate(base + entry.stack_depth as usize);
1109
1110                // Push error value as a map matching the tree-walker's error_to_value format
1111                let error_val = error_to_value(&err);
1112                self.stack.push(error_val);
1113
1114                // Jump to handler
1115                let frame = self.frames.last_mut().unwrap();
1116                frame.pc = entry.handler_pc as usize;
1117                return Ok(ExceptionAction::Handled);
1118            }
1119
1120            // No handler in this frame, pop it and try parent
1121            let frame = self.frames.pop().unwrap();
1122            self.stack.truncate(frame.base);
1123            // Parent frames use their own pc for lookup
1124            if let Some(parent) = self.frames.last() {
1125                pc_for_lookup = parent.pc as u32;
1126            }
1127        }
1128
1129        // No handler found anywhere
1130        Ok(ExceptionAction::Propagate(err))
1131    }
1132}
1133
1134enum ExceptionAction {
1135    Handled,
1136    Propagate(SemaError),
1137}
1138
1139/// Convert a SemaError into a Sema map value, matching the tree-walker's format.
1140fn error_to_value(err: &SemaError) -> Value {
1141    let inner = err.inner();
1142    let mut map = BTreeMap::new();
1143    match inner {
1144        SemaError::Eval(msg) => {
1145            map.insert(Value::keyword("type"), Value::keyword("eval"));
1146            map.insert(Value::keyword("message"), Value::string(msg));
1147        }
1148        SemaError::Type { expected, got } => {
1149            map.insert(Value::keyword("type"), Value::keyword("type-error"));
1150            map.insert(
1151                Value::keyword("message"),
1152                Value::string(&format!("expected {expected}, got {got}")),
1153            );
1154            map.insert(Value::keyword("expected"), Value::string(expected));
1155            map.insert(Value::keyword("got"), Value::string(got));
1156        }
1157        SemaError::Arity {
1158            name,
1159            expected,
1160            got,
1161        } => {
1162            map.insert(Value::keyword("type"), Value::keyword("arity"));
1163            map.insert(
1164                Value::keyword("message"),
1165                Value::string(&format!("{name} expects {expected} args, got {got}")),
1166            );
1167        }
1168        SemaError::Unbound(name) => {
1169            map.insert(Value::keyword("type"), Value::keyword("unbound"));
1170            map.insert(
1171                Value::keyword("message"),
1172                Value::string(&format!("Unbound variable: {name}")),
1173            );
1174            map.insert(Value::keyword("name"), Value::string(name));
1175        }
1176        SemaError::UserException(val) => {
1177            map.insert(Value::keyword("type"), Value::keyword("user"));
1178            map.insert(Value::keyword("message"), Value::string(&val.to_string()));
1179            map.insert(Value::keyword("value"), val.clone());
1180        }
1181        SemaError::Io(msg) => {
1182            map.insert(Value::keyword("type"), Value::keyword("io"));
1183            map.insert(Value::keyword("message"), Value::string(msg));
1184        }
1185        SemaError::Llm(msg) => {
1186            map.insert(Value::keyword("type"), Value::keyword("llm"));
1187            map.insert(Value::keyword("message"), Value::string(msg));
1188        }
1189        SemaError::Reader { message, span } => {
1190            map.insert(Value::keyword("type"), Value::keyword("reader"));
1191            map.insert(
1192                Value::keyword("message"),
1193                Value::string(&format!("{message} at {span}")),
1194            );
1195        }
1196        SemaError::PermissionDenied {
1197            function,
1198            capability,
1199        } => {
1200            map.insert(Value::keyword("type"), Value::keyword("permission-denied"));
1201            map.insert(
1202                Value::keyword("message"),
1203                Value::string(&format!(
1204                    "Permission denied: {function} requires '{capability}' capability"
1205                )),
1206            );
1207            map.insert(Value::keyword("function"), Value::string(function));
1208            map.insert(Value::keyword("capability"), Value::string(capability));
1209        }
1210        SemaError::WithTrace { .. } | SemaError::WithContext { .. } => {
1211            unreachable!("inner() already unwraps these")
1212        }
1213    }
1214    Value::map(map)
1215}
1216
1217// --- Arithmetic helpers ---
1218
1219#[inline(always)]
1220fn vm_add(a: &Value, b: &Value) -> Result<Value, SemaError> {
1221    use sema_core::ValueView;
1222    match (a.view(), b.view()) {
1223        (ValueView::Int(x), ValueView::Int(y)) => Ok(Value::int(x.wrapping_add(y))),
1224        (ValueView::Float(x), ValueView::Float(y)) => Ok(Value::float(x + y)),
1225        (ValueView::Int(x), ValueView::Float(y)) => Ok(Value::float(x as f64 + y)),
1226        (ValueView::Float(x), ValueView::Int(y)) => Ok(Value::float(x + y as f64)),
1227        (ValueView::String(x), ValueView::String(y)) => {
1228            let mut s = (*x).clone();
1229            s.push_str(&y);
1230            Ok(Value::string(&s))
1231        }
1232        _ => Err(SemaError::type_error(
1233            "number or string",
1234            format!("{} and {}", a.type_name(), b.type_name()),
1235        )),
1236    }
1237}
1238
1239#[inline(always)]
1240fn vm_sub(a: &Value, b: &Value) -> Result<Value, SemaError> {
1241    use sema_core::ValueView;
1242    match (a.view(), b.view()) {
1243        (ValueView::Int(x), ValueView::Int(y)) => Ok(Value::int(x.wrapping_sub(y))),
1244        (ValueView::Float(x), ValueView::Float(y)) => Ok(Value::float(x - y)),
1245        (ValueView::Int(x), ValueView::Float(y)) => Ok(Value::float(x as f64 - y)),
1246        (ValueView::Float(x), ValueView::Int(y)) => Ok(Value::float(x - y as f64)),
1247        _ => Err(SemaError::type_error(
1248            "number",
1249            format!("{} and {}", a.type_name(), b.type_name()),
1250        )),
1251    }
1252}
1253
1254#[inline(always)]
1255fn vm_mul(a: &Value, b: &Value) -> Result<Value, SemaError> {
1256    use sema_core::ValueView;
1257    match (a.view(), b.view()) {
1258        (ValueView::Int(x), ValueView::Int(y)) => Ok(Value::int(x.wrapping_mul(y))),
1259        (ValueView::Float(x), ValueView::Float(y)) => Ok(Value::float(x * y)),
1260        (ValueView::Int(x), ValueView::Float(y)) => Ok(Value::float(x as f64 * y)),
1261        (ValueView::Float(x), ValueView::Int(y)) => Ok(Value::float(x * y as f64)),
1262        _ => Err(SemaError::type_error(
1263            "number",
1264            format!("{} and {}", a.type_name(), b.type_name()),
1265        )),
1266    }
1267}
1268
1269#[inline(always)]
1270fn vm_div(a: &Value, b: &Value) -> Result<Value, SemaError> {
1271    use sema_core::ValueView;
1272    match (a.view(), b.view()) {
1273        (ValueView::Int(_), ValueView::Int(0)) => Err(SemaError::eval("division by zero")),
1274        (ValueView::Int(x), ValueView::Int(y)) => Ok(Value::int(x / y)),
1275        (ValueView::Float(x), ValueView::Float(y)) => Ok(Value::float(x / y)),
1276        (ValueView::Int(x), ValueView::Float(y)) => Ok(Value::float(x as f64 / y)),
1277        (ValueView::Float(x), ValueView::Int(y)) => Ok(Value::float(x / y as f64)),
1278        _ => Err(SemaError::type_error(
1279            "number",
1280            format!("{} and {}", a.type_name(), b.type_name()),
1281        )),
1282    }
1283}
1284
1285#[inline(always)]
1286fn vm_lt(a: &Value, b: &Value) -> Result<bool, SemaError> {
1287    use sema_core::ValueView;
1288    match (a.view(), b.view()) {
1289        (ValueView::Int(x), ValueView::Int(y)) => Ok(x < y),
1290        (ValueView::Float(x), ValueView::Float(y)) => Ok(x < y),
1291        (ValueView::Int(x), ValueView::Float(y)) => Ok((x as f64) < y),
1292        (ValueView::Float(x), ValueView::Int(y)) => Ok(x < (y as f64)),
1293        (ValueView::String(x), ValueView::String(y)) => Ok(x < y),
1294        _ => Err(SemaError::type_error(
1295            "comparable values",
1296            format!("{} and {}", a.type_name(), b.type_name()),
1297        )),
1298    }
1299}
1300
1301/// Compile a sequence of Value ASTs through the full pipeline and produce
1302/// the entry closure + function table ready for VM execution.
1303pub fn compile_program(vals: &[Value]) -> Result<(Rc<Closure>, Vec<Rc<Function>>), SemaError> {
1304    let mut resolved = Vec::new();
1305    let mut total_locals: u16 = 0;
1306    for val in vals {
1307        let core = crate::lower::lower(val)?;
1308        let (res, n) = crate::resolve::resolve_with_locals(&core)?;
1309        total_locals = total_locals.max(n);
1310        resolved.push(res);
1311    }
1312    let result = crate::compiler::compile_many_with_locals(&resolved, total_locals)?;
1313
1314    let functions: Vec<Rc<Function>> = result.functions.into_iter().map(Rc::new).collect();
1315    let closure = Rc::new(Closure {
1316        func: Rc::new(Function {
1317            name: None,
1318            chunk: result.chunk,
1319            upvalue_descs: Vec::new(),
1320            arity: 0,
1321            has_rest: false,
1322            local_names: Vec::new(),
1323        }),
1324        upvalues: Vec::new(),
1325    });
1326
1327    Ok((closure, functions))
1328}
1329
1330/// Convenience: compile and run a string expression in the VM.
1331pub fn eval_str(input: &str, globals: &Rc<Env>, ctx: &EvalContext) -> Result<Value, SemaError> {
1332    let vals =
1333        sema_reader::read_many(input).map_err(|e| SemaError::eval(format!("parse error: {e}")))?;
1334    let (closure, functions) = compile_program(&vals)?;
1335    let mut vm = VM::new(globals.clone(), functions);
1336    vm.execute(closure, ctx)
1337}
1338
1339#[cfg(test)]
1340mod tests {
1341    use super::*;
1342    use sema_core::{intern, NativeFn};
1343
1344    fn make_test_env() -> Rc<Env> {
1345        let env = Rc::new(Env::new());
1346        env.set(
1347            intern("+"),
1348            Value::native_fn(NativeFn::simple("+", |args| vm_add(&args[0], &args[1]))),
1349        );
1350        env.set(
1351            intern("-"),
1352            Value::native_fn(NativeFn::simple("-", |args| vm_sub(&args[0], &args[1]))),
1353        );
1354        env.set(
1355            intern("*"),
1356            Value::native_fn(NativeFn::simple("*", |args| vm_mul(&args[0], &args[1]))),
1357        );
1358        env.set(
1359            intern("/"),
1360            Value::native_fn(NativeFn::simple("/", |args| vm_div(&args[0], &args[1]))),
1361        );
1362        env.set(
1363            intern("="),
1364            Value::native_fn(NativeFn::simple("=", |args| {
1365                Ok(Value::bool(args[0] == args[1]))
1366            })),
1367        );
1368        env.set(
1369            intern("<"),
1370            Value::native_fn(NativeFn::simple("<", |args| {
1371                Ok(Value::bool(vm_lt(&args[0], &args[1])?))
1372            })),
1373        );
1374        env.set(
1375            intern(">"),
1376            Value::native_fn(NativeFn::simple(">", |args| {
1377                Ok(Value::bool(vm_lt(&args[1], &args[0])?))
1378            })),
1379        );
1380        env.set(
1381            intern("not"),
1382            Value::native_fn(NativeFn::simple("not", |args| {
1383                Ok(Value::bool(!args[0].is_truthy()))
1384            })),
1385        );
1386        env.set(
1387            intern("list"),
1388            Value::native_fn(NativeFn::simple("list", |args| {
1389                Ok(Value::list(args.to_vec()))
1390            })),
1391        );
1392        env
1393    }
1394
1395    fn eval(input: &str) -> Result<Value, SemaError> {
1396        let globals = make_test_env();
1397        let ctx = EvalContext::new();
1398        eval_str(input, &globals, &ctx)
1399    }
1400
1401    #[test]
1402    fn test_vm_int_literal() {
1403        assert_eq!(eval("42").unwrap(), Value::int(42));
1404    }
1405
1406    #[test]
1407    fn test_vm_nil() {
1408        assert_eq!(eval("nil").unwrap(), Value::nil());
1409    }
1410
1411    #[test]
1412    fn test_vm_bool() {
1413        assert_eq!(eval("#t").unwrap(), Value::bool(true));
1414        assert_eq!(eval("#f").unwrap(), Value::bool(false));
1415    }
1416
1417    #[test]
1418    fn test_vm_string() {
1419        assert_eq!(eval("\"hello\"").unwrap(), Value::string("hello"));
1420    }
1421
1422    #[test]
1423    fn test_vm_if_true() {
1424        assert_eq!(eval("(if #t 42 99)").unwrap(), Value::int(42));
1425    }
1426
1427    #[test]
1428    fn test_vm_if_false() {
1429        assert_eq!(eval("(if #f 42 99)").unwrap(), Value::int(99));
1430    }
1431
1432    #[test]
1433    fn test_vm_begin() {
1434        assert_eq!(eval("(begin 1 2 3)").unwrap(), Value::int(3));
1435    }
1436
1437    #[test]
1438    fn test_vm_define_and_load() {
1439        let globals = make_test_env();
1440        let ctx = EvalContext::new();
1441        eval_str("(define x 42)", &globals, &ctx).unwrap();
1442        let result = eval_str("x", &globals, &ctx).unwrap();
1443        assert_eq!(result, Value::int(42));
1444    }
1445
1446    #[test]
1447    fn test_vm_let() {
1448        assert_eq!(eval("(let ((x 10)) x)").unwrap(), Value::int(10));
1449    }
1450
1451    #[test]
1452    fn test_vm_let_multiple() {
1453        assert_eq!(eval("(let ((x 10) (y 20)) y)").unwrap(), Value::int(20));
1454    }
1455
1456    #[test]
1457    fn test_vm_nested_if() {
1458        assert_eq!(eval("(if (if #t #f #t) 1 2)").unwrap(), Value::int(2));
1459    }
1460
1461    #[test]
1462    fn test_vm_lambda_call() {
1463        assert_eq!(eval("((lambda (x) x) 42)").unwrap(), Value::int(42));
1464    }
1465
1466    #[test]
1467    fn test_vm_lambda_two_args() {
1468        assert_eq!(eval("((lambda (x y) y) 1 2)").unwrap(), Value::int(2));
1469    }
1470
1471    #[test]
1472    fn test_vm_closure_capture() {
1473        assert_eq!(
1474            eval("(let ((x 10)) ((lambda () x)))").unwrap(),
1475            Value::int(10)
1476        );
1477    }
1478
1479    #[test]
1480    fn test_vm_list_literal() {
1481        let result = eval("(list 1 2 3)").unwrap();
1482        let items = result.as_list().expect("Expected list");
1483        assert_eq!(items.len(), 3);
1484        assert_eq!(items[0], Value::int(1));
1485    }
1486
1487    #[test]
1488    fn test_vm_make_vector() {
1489        let result = eval("[1 2 3]").unwrap();
1490        let items = result.as_vector().expect("Expected vector");
1491        assert_eq!(items.len(), 3);
1492    }
1493
1494    #[test]
1495    fn test_vm_and_short_circuit() {
1496        assert_eq!(eval("(and #f 42)").unwrap(), Value::bool(false));
1497        assert_eq!(eval("(and #t 42)").unwrap(), Value::int(42));
1498    }
1499
1500    #[test]
1501    fn test_vm_or_short_circuit() {
1502        assert_eq!(eval("(or 42 99)").unwrap(), Value::int(42));
1503        assert_eq!(eval("(or #f 99)").unwrap(), Value::int(99));
1504    }
1505
1506    #[test]
1507    fn test_vm_throw_catch() {
1508        // Caught value is now a map with :type, :message, :value keys
1509        let result = eval("(try (throw \"boom\") (catch e (:value e)))").unwrap();
1510        assert_eq!(result, Value::string("boom"));
1511    }
1512
1513    #[test]
1514    fn test_vm_throw_catch_type() {
1515        let result = eval("(try (throw \"boom\") (catch e (:type e)))").unwrap();
1516        assert_eq!(result, Value::keyword("user"));
1517    }
1518
1519    #[test]
1520    fn test_vm_try_no_throw() {
1521        assert_eq!(eval("(try 42 (catch e 99))").unwrap(), Value::int(42));
1522    }
1523
1524    #[test]
1525    fn test_vm_try_catch_native_error() {
1526        // Division by zero from NativeFn should be caught
1527        let result = eval("(try (/ 1 0) (catch e \"caught\"))").unwrap();
1528        assert_eq!(result, Value::string("caught"));
1529    }
1530
1531    #[test]
1532    fn test_vm_try_catch_native_error_message() {
1533        let result = eval("(try (/ 1 0) (catch e (:message e)))").unwrap();
1534        let s = result.as_str().expect("Expected string");
1535        assert!(s.contains("division by zero"), "got: {s}");
1536    }
1537
1538    #[test]
1539    fn test_vm_try_catch_type_error() {
1540        let result = eval("(try (+ 1 \"a\") (catch e (:type e)))").unwrap();
1541        assert_eq!(result, Value::keyword("type-error"));
1542    }
1543
1544    #[test]
1545    fn test_vm_quote() {
1546        let result = eval("'(a b c)").unwrap();
1547        let items = result.as_list().expect("Expected list");
1548        assert_eq!(items.len(), 3);
1549    }
1550
1551    #[test]
1552    fn test_vm_set() {
1553        let globals = make_test_env();
1554        let ctx = EvalContext::new();
1555        eval_str("(define x 1)", &globals, &ctx).unwrap();
1556        eval_str("(set! x 42)", &globals, &ctx).unwrap();
1557        let result = eval_str("x", &globals, &ctx).unwrap();
1558        assert_eq!(result, Value::int(42));
1559    }
1560
1561    #[test]
1562    fn test_vm_recursive_define() {
1563        let globals = make_test_env();
1564        let ctx = EvalContext::new();
1565        eval_str(
1566            "(define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))",
1567            &globals,
1568            &ctx,
1569        )
1570        .unwrap();
1571        let result = eval_str("(fact 5)", &globals, &ctx).unwrap();
1572        assert_eq!(result, Value::int(120));
1573    }
1574
1575    #[test]
1576    fn test_vm_do_loop() {
1577        let result = eval("(do ((i 0 (+ i 1))) ((= i 5) i))").unwrap();
1578        assert_eq!(result, Value::int(5));
1579    }
1580
1581    #[test]
1582    fn test_vm_named_let() {
1583        let result =
1584            eval("(let loop ((n 5) (acc 1)) (if (= n 0) acc (loop (- n 1) (* acc n))))").unwrap();
1585        assert_eq!(result, Value::int(120));
1586    }
1587
1588    #[test]
1589    fn test_vm_letrec() {
1590        let globals = make_test_env();
1591        let ctx = EvalContext::new();
1592        let result = eval_str(
1593            "(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))) (odd? (lambda (n) (if (= n 0) #f (even? (- n 1)))))) (even? 4))",
1594            &globals,
1595            &ctx,
1596        ).unwrap();
1597        assert_eq!(result, Value::bool(true));
1598    }
1599
1600    #[test]
1601    fn test_vm_rest_params() {
1602        let result = eval("((lambda (x . rest) rest) 1 2 3)").unwrap();
1603        let items = result.as_list().expect("Expected list");
1604        assert_eq!(items.len(), 2);
1605        assert_eq!(items[0], Value::int(2));
1606        assert_eq!(items[1], Value::int(3));
1607    }
1608
1609    // --- Task 8: Mutable upvalue tests ---
1610
1611    #[test]
1612    fn test_vm_counter_closure() {
1613        // make-counter pattern: closure that mutates a captured variable
1614        let result =
1615            eval("(let ((n 0)) (let ((inc (lambda () (set! n (+ n 1)) n))) (inc) (inc) (inc)))")
1616                .unwrap();
1617        assert_eq!(result, Value::int(3));
1618    }
1619
1620    #[test]
1621    fn test_vm_shared_mutable_upvalue() {
1622        // Two closures sharing the same mutable upvalue
1623        let result = eval(
1624            "(let ((n 0)) (let ((inc (lambda () (set! n (+ n 1)))) (get (lambda () n))) (inc) (inc) (get)))",
1625        )
1626        .unwrap();
1627        assert_eq!(result, Value::int(2));
1628    }
1629
1630    #[test]
1631    fn test_vm_set_local_in_let() {
1632        // set! on a local variable (not captured)
1633        let result = eval("(let ((x 1)) (set! x 42) x)").unwrap();
1634        assert_eq!(result, Value::int(42));
1635    }
1636
1637    #[test]
1638    fn test_vm_closure_captures_after_mutation() {
1639        // Closure captures value after mutation
1640        let result = eval("(let ((x 1)) (set! x 10) ((lambda () x)))").unwrap();
1641        assert_eq!(result, Value::int(10));
1642    }
1643
1644    #[test]
1645    fn test_vm_closure_returns_closure() {
1646        // A closure that returns another closure
1647        let result = eval("(let ((f (lambda () (lambda (x) x)))) ((f) 42))").unwrap();
1648        assert_eq!(result, Value::int(42));
1649    }
1650
1651    #[test]
1652    fn test_vm_make_adder() {
1653        // Classic make-adder pattern: closure captures upvalue
1654        let globals = make_test_env();
1655        let ctx = EvalContext::new();
1656        eval_str(
1657            "(define (make-adder n) (lambda (x) (+ n x)))",
1658            &globals,
1659            &ctx,
1660        )
1661        .unwrap();
1662        eval_str("(define add5 (make-adder 5))", &globals, &ctx).unwrap();
1663        let result = eval_str("(add5 3)", &globals, &ctx).unwrap();
1664        assert_eq!(result, Value::int(8));
1665    }
1666
1667    #[test]
1668    fn test_vm_compose() {
1669        // compose: closure returns closure that captures two upvalues
1670        let globals = make_test_env();
1671        let ctx = EvalContext::new();
1672        eval_str(
1673            "(define (compose f g) (lambda (x) (f (g x))))",
1674            &globals,
1675            &ctx,
1676        )
1677        .unwrap();
1678        eval_str("(define inc (lambda (x) (+ x 1)))", &globals, &ctx).unwrap();
1679        eval_str("(define dbl (lambda (x) (* x 2)))", &globals, &ctx).unwrap();
1680        let result = eval_str("((compose dbl inc) 5)", &globals, &ctx).unwrap();
1681        assert_eq!(result, Value::int(12));
1682    }
1683
1684    #[test]
1685    fn test_vm_nested_make_closure() {
1686        // Three levels deep
1687        let result = eval("((((lambda () (lambda () (lambda () 42))))))").unwrap();
1688        assert_eq!(result, Value::int(42));
1689    }
1690
1691    #[test]
1692    fn test_vm_named_fn_rest_params() {
1693        let globals = make_test_env();
1694        let ctx = EvalContext::new();
1695        eval_str("(define (f . args) args)", &globals, &ctx).unwrap();
1696        let result = eval_str("(f 1 2 3)", &globals, &ctx).unwrap();
1697        let items = result.as_list().expect("Expected list");
1698        assert_eq!(items.len(), 3);
1699        assert_eq!(items[0], Value::int(1));
1700    }
1701
1702    #[test]
1703    fn test_vm_named_let_still_works_with_fix() {
1704        let globals = make_test_env();
1705        let ctx = EvalContext::new();
1706        let result = eval_str(
1707            "(let loop ((n 5) (acc 1)) (if (= n 0) acc (loop (- n 1) (* acc n))))",
1708            &globals,
1709            &ctx,
1710        )
1711        .unwrap();
1712        assert_eq!(result, Value::int(120));
1713    }
1714
1715    #[test]
1716    fn test_vm_curry() {
1717        // Curry pattern
1718        let globals = make_test_env();
1719        let ctx = EvalContext::new();
1720        eval_str(
1721            "(define (curry f) (lambda (x) (lambda (y) (f x y))))",
1722            &globals,
1723            &ctx,
1724        )
1725        .unwrap();
1726        let result = eval_str("(((curry +) 3) 4)", &globals, &ctx).unwrap();
1727        assert_eq!(result, Value::int(7));
1728    }
1729}