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_SMALL_PATTERN,
7    NAN_PAYLOAD_BITS, NAN_PAYLOAD_MASK, NAN_TAG_MASK, TAG_NATIVE_FN,
8};
9
10use crate::chunk::Function;
11use crate::opcodes::op;
12
13/// A mutable cell for captured variables (upvalues).
14#[derive(Debug)]
15pub struct UpvalueCell {
16    pub value: RefCell<Value>,
17}
18
19impl UpvalueCell {
20    pub fn new(value: Value) -> Self {
21        UpvalueCell {
22            value: RefCell::new(value),
23        }
24    }
25}
26
27/// A runtime closure: function template + captured upvalues.
28#[derive(Debug, Clone)]
29pub struct Closure {
30    pub func: Rc<Function>,
31    pub upvalues: Vec<Rc<UpvalueCell>>,
32}
33
34/// Payload stored in NativeFn for VM closures.
35/// Carries both the closure and the function table from its compilation context.
36struct VmClosurePayload {
37    closure: Rc<Closure>,
38    functions: Rc<Vec<Rc<Function>>>,
39}
40
41/// A call frame in the VM's call stack.
42struct CallFrame {
43    closure: Rc<Closure>,
44    pc: usize,
45    base: usize,
46    /// Open upvalue cells for locals in this frame.
47    /// Maps local slot → shared UpvalueCell. Created lazily when a local is captured.
48    /// `None` means no locals have been captured yet (avoids heap allocation).
49    open_upvalues: Option<Vec<Option<Rc<UpvalueCell>>>>,
50}
51
52/// Number of entries in the direct-mapped global cache (must be power of 2).
53const GLOBAL_CACHE_SIZE: usize = 16;
54
55/// The bytecode virtual machine.
56pub struct VM {
57    stack: Vec<Value>,
58    frames: Vec<CallFrame>,
59    globals: Rc<Env>,
60    functions: Rc<Vec<Rc<Function>>>,
61    /// Direct-mapped cache for global lookups: (spur_bits, env_version, value)
62    global_cache: [(u32, u64, Value); GLOBAL_CACHE_SIZE],
63}
64
65impl VM {
66    pub fn new(globals: Rc<Env>, functions: Vec<Rc<Function>>) -> Self {
67        VM {
68            stack: Vec::with_capacity(256),
69            frames: Vec::with_capacity(64),
70            globals,
71            functions: Rc::new(functions),
72            global_cache: std::array::from_fn(|_| (u32::MAX, u64::MAX, Value::nil())),
73        }
74    }
75
76    fn new_with_rc_functions(globals: Rc<Env>, functions: Rc<Vec<Rc<Function>>>) -> Self {
77        VM {
78            stack: Vec::with_capacity(256),
79            frames: Vec::with_capacity(64),
80            globals,
81            functions,
82            global_cache: std::array::from_fn(|_| (u32::MAX, u64::MAX, Value::nil())),
83        }
84    }
85
86    pub fn execute(&mut self, closure: Rc<Closure>, ctx: &EvalContext) -> Result<Value, SemaError> {
87        let base = self.stack.len();
88        // Reserve space for locals
89        let n_locals = closure.func.chunk.n_locals as usize;
90        self.stack.resize(base + n_locals, Value::nil());
91        self.frames.push(CallFrame {
92            closure,
93            pc: 0,
94            base,
95            open_upvalues: None,
96        });
97        self.run(ctx)
98    }
99
100    fn run(&mut self, ctx: &EvalContext) -> Result<Value, SemaError> {
101        // Raw-pointer macros for reading operands without bounds checks in inner loop
102        macro_rules! read_u16 {
103            ($code:expr, $pc:expr) => {{
104                let v = unsafe { u16::from_le_bytes([*$code.add($pc), *$code.add($pc + 1)]) };
105                $pc += 2;
106                v
107            }};
108        }
109        macro_rules! read_i32 {
110            ($code:expr, $pc:expr) => {{
111                let v = unsafe {
112                    i32::from_le_bytes([
113                        *$code.add($pc),
114                        *$code.add($pc + 1),
115                        *$code.add($pc + 2),
116                        *$code.add($pc + 3),
117                    ])
118                };
119                $pc += 4;
120                v
121            }};
122        }
123        macro_rules! read_u32 {
124            ($code:expr, $pc:expr) => {{
125                let v = unsafe {
126                    u32::from_le_bytes([
127                        *$code.add($pc),
128                        *$code.add($pc + 1),
129                        *$code.add($pc + 2),
130                        *$code.add($pc + 3),
131                    ])
132                };
133                $pc += 4;
134                v
135            }};
136        }
137
138        // Unsafe unchecked pop — valid when compiler guarantees stack correctness.
139        #[inline(always)]
140        unsafe fn pop_unchecked(stack: &mut Vec<Value>) -> Value {
141            let len = stack.len();
142            debug_assert!(len > 0, "pop_unchecked on empty stack");
143            let v = std::ptr::read(stack.as_ptr().add(len - 1));
144            stack.set_len(len - 1);
145            v
146        }
147
148        // Branchless sign-extension shift for NaN-boxed small ints
149        const SIGN_SHIFT: u32 = 64 - NAN_PAYLOAD_BITS;
150
151        // Cold-path macro: saves pc, handles exception, and dispatches or returns.
152        // Keeps the error path out of the hot instruction sequence.
153        macro_rules! handle_err {
154            ($self:expr, $fi:expr, $pc:expr, $err:expr, $saved_pc:expr, $label:tt) => {{
155                $self.frames[$fi].pc = $pc;
156                match $self.handle_exception($err, $saved_pc)? {
157                    ExceptionAction::Handled => continue $label,
158                    ExceptionAction::Propagate(e) => return Err(e),
159                }
160            }};
161        }
162
163        // Two-level dispatch: outer loop caches frame locals, inner loop dispatches opcodes.
164        // We only break to the outer loop when frames change (Call/TailCall/Return/exceptions).
165        'dispatch: loop {
166            let fi = self.frames.len() - 1;
167            let frame = &self.frames[fi];
168            let code = frame.closure.func.chunk.code.as_ptr();
169            let consts: *const [Value] = frame.closure.func.chunk.consts.as_slice();
170            let base = frame.base;
171            let mut pc = frame.pc;
172            let has_open_upvalues = frame.open_upvalues.is_some();
173            #[cfg(debug_assertions)]
174            let code_len = frame.closure.func.chunk.code.len();
175            let _ = frame; // release borrow so we can mutate self
176
177            loop {
178                #[cfg(debug_assertions)]
179                debug_assert!(pc < code_len, "pc {pc} out of bounds (len {code_len})");
180                let op = unsafe { *code.add(pc) };
181                pc += 1;
182
183                match op {
184                    // --- Constants & stack ---
185                    op::CONST => {
186                        let idx = read_u16!(code, pc) as usize;
187                        let val = unsafe { (&(*consts)).get_unchecked(idx) }.clone();
188                        self.stack.push(val);
189                    }
190                    op::NIL => {
191                        self.stack.push(Value::nil());
192                    }
193                    op::TRUE => {
194                        self.stack.push(Value::bool(true));
195                    }
196                    op::FALSE => {
197                        self.stack.push(Value::bool(false));
198                    }
199                    op::POP => {
200                        unsafe { pop_unchecked(&mut self.stack) };
201                    }
202                    op::DUP => {
203                        let val =
204                            unsafe { &*self.stack.as_ptr().add(self.stack.len() - 1) }.clone();
205                        self.stack.push(val);
206                    }
207
208                    // --- Locals ---
209                    op::LOAD_LOCAL => {
210                        let slot = read_u16!(code, pc) as usize;
211                        let val = if has_open_upvalues {
212                            if let Some(ref open) = self.frames[fi].open_upvalues {
213                                if let Some(Some(cell)) = open.get(slot) {
214                                    cell.value.borrow().clone()
215                                } else {
216                                    self.stack[base + slot].clone()
217                                }
218                            } else {
219                                unreachable!()
220                            }
221                        } else {
222                            self.stack[base + slot].clone()
223                        };
224                        self.stack.push(val);
225                    }
226                    op::STORE_LOCAL => {
227                        let slot = read_u16!(code, pc) as usize;
228                        let val = unsafe { pop_unchecked(&mut self.stack) };
229                        self.stack[base + slot] = val.clone();
230                        if has_open_upvalues {
231                            if let Some(ref open) = self.frames[fi].open_upvalues {
232                                if let Some(Some(cell)) = open.get(slot) {
233                                    *cell.value.borrow_mut() = val;
234                                }
235                            }
236                        }
237                    }
238
239                    // --- Upvalues ---
240                    op::LOAD_UPVALUE => {
241                        let idx = read_u16!(code, pc) as usize;
242                        let val = self.frames[fi].closure.upvalues[idx].value.borrow().clone();
243                        self.stack.push(val);
244                    }
245                    op::STORE_UPVALUE => {
246                        let idx = read_u16!(code, pc) as usize;
247                        let val = unsafe { pop_unchecked(&mut self.stack) };
248                        *self.frames[fi].closure.upvalues[idx].value.borrow_mut() = val;
249                    }
250
251                    // --- Globals ---
252                    op::LOAD_GLOBAL => {
253                        let bits = read_u32!(code, pc);
254                        let version = self.globals.version.get();
255                        let slot = (bits as usize) & (GLOBAL_CACHE_SIZE - 1);
256                        let entry = &self.global_cache[slot];
257                        if entry.0 == bits && entry.1 == version {
258                            self.stack.push(entry.2.clone());
259                        } else {
260                            let spur: Spur = unsafe { std::mem::transmute::<u32, Spur>(bits) };
261                            match self.globals.get(spur) {
262                                Some(val) => {
263                                    self.global_cache[slot] = (bits, version, val.clone());
264                                    self.stack.push(val);
265                                }
266                                None => {
267                                    let err = SemaError::Unbound(resolve_spur(spur));
268                                    handle_err!(self, fi, pc, err, pc - op::SIZE_OP_U32, 'dispatch);
269                                }
270                            }
271                        }
272                    }
273                    op::STORE_GLOBAL => {
274                        let bits = read_u32!(code, pc);
275                        let spur: Spur = unsafe { std::mem::transmute::<u32, Spur>(bits) };
276                        let val = unsafe { pop_unchecked(&mut self.stack) };
277                        if !self.globals.set_existing(spur, val.clone()) {
278                            self.globals.set(spur, val);
279                        }
280                    }
281                    op::DEFINE_GLOBAL => {
282                        let bits = read_u32!(code, pc);
283                        let spur: Spur = unsafe { std::mem::transmute::<u32, Spur>(bits) };
284                        let val = unsafe { pop_unchecked(&mut self.stack) };
285                        self.globals.set(spur, val);
286                    }
287
288                    // --- Control flow ---
289                    op::JUMP => {
290                        let offset = read_i32!(code, pc);
291                        pc = (pc as i64 + offset as i64) as usize;
292                    }
293                    op::JUMP_IF_FALSE => {
294                        let offset = read_i32!(code, pc);
295                        let val = unsafe { pop_unchecked(&mut self.stack) };
296                        if !val.is_truthy() {
297                            pc = (pc as i64 + offset as i64) as usize;
298                        }
299                    }
300                    op::JUMP_IF_TRUE => {
301                        let offset = read_i32!(code, pc);
302                        let val = unsafe { pop_unchecked(&mut self.stack) };
303                        if val.is_truthy() {
304                            pc = (pc as i64 + offset as i64) as usize;
305                        }
306                    }
307
308                    // --- Function calls ---
309                    op::CALL => {
310                        let argc = read_u16!(code, pc) as usize;
311                        self.frames[fi].pc = pc;
312                        let saved_pc = pc - op::SIZE_OP_U16;
313                        if let Err(err) = self.call_value(argc, ctx) {
314                            match self.handle_exception(err, saved_pc)? {
315                                ExceptionAction::Handled => {}
316                                ExceptionAction::Propagate(e) => return Err(e),
317                            }
318                        }
319                        continue 'dispatch;
320                    }
321                    op::TAIL_CALL => {
322                        let argc = read_u16!(code, pc) as usize;
323                        self.frames[fi].pc = pc;
324                        let saved_pc = pc - op::SIZE_OP_U16;
325                        if let Err(err) = self.tail_call_value(argc, ctx) {
326                            match self.handle_exception(err, saved_pc)? {
327                                ExceptionAction::Handled => {}
328                                ExceptionAction::Propagate(e) => return Err(e),
329                            }
330                        }
331                        continue 'dispatch;
332                    }
333                    op::RETURN => {
334                        let result = if !self.stack.is_empty() {
335                            unsafe { pop_unchecked(&mut self.stack) }
336                        } else {
337                            Value::nil()
338                        };
339                        let frame = self.frames.pop().unwrap();
340                        self.stack.truncate(frame.base);
341                        if self.frames.is_empty() {
342                            return Ok(result);
343                        }
344                        self.stack.push(result);
345                        continue 'dispatch;
346                    }
347
348                    // --- Closures ---
349                    op::MAKE_CLOSURE => {
350                        self.frames[fi].pc = pc - op::SIZE_OP; // make_closure reads from frame.pc (the opcode position)
351                        self.make_closure()?;
352                        continue 'dispatch;
353                    }
354
355                    op::CALL_NATIVE => {
356                        let _native_id = read_u16!(code, pc);
357                        let _argc = read_u16!(code, pc);
358                        self.frames[fi].pc = pc;
359                        return Err(SemaError::eval("VM: CallNative not yet implemented"));
360                    }
361
362                    // --- Data constructors ---
363                    op::MAKE_LIST => {
364                        let n = read_u16!(code, pc) as usize;
365                        let start = self.stack.len() - n;
366                        let items: Vec<Value> = self.stack.drain(start..).collect();
367                        self.stack.push(Value::list(items));
368                    }
369                    op::MAKE_VECTOR => {
370                        let n = read_u16!(code, pc) as usize;
371                        let start = self.stack.len() - n;
372                        let items: Vec<Value> = self.stack.drain(start..).collect();
373                        self.stack.push(Value::vector(items));
374                    }
375                    op::MAKE_MAP => {
376                        let n = read_u16!(code, pc) as usize;
377                        let start = self.stack.len() - n * 2;
378                        let items: Vec<Value> = self.stack.drain(start..).collect();
379                        let mut map = BTreeMap::new();
380                        for pair in items.chunks(2) {
381                            map.insert(pair[0].clone(), pair[1].clone());
382                        }
383                        self.stack.push(Value::map(map));
384                    }
385                    op::MAKE_HASH_MAP => {
386                        let n = read_u16!(code, pc) as usize;
387                        let start = self.stack.len() - n * 2;
388                        let items: Vec<Value> = self.stack.drain(start..).collect();
389                        let mut map = hashbrown::HashMap::new();
390                        for pair in items.chunks(2) {
391                            map.insert(pair[0].clone(), pair[1].clone());
392                        }
393                        self.stack.push(Value::hashmap_from_rc(Rc::new(map)));
394                    }
395
396                    // --- Exceptions ---
397                    op::THROW => {
398                        let val = unsafe { pop_unchecked(&mut self.stack) };
399                        let err = SemaError::UserException(val);
400                        handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch);
401                    }
402
403                    // --- Arithmetic ---
404                    op::ADD => {
405                        let b = unsafe { pop_unchecked(&mut self.stack) };
406                        let a = unsafe { pop_unchecked(&mut self.stack) };
407                        match vm_add(&a, &b) {
408                            Ok(v) => self.stack.push(v),
409                            Err(err) => handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch),
410                        }
411                    }
412                    op::SUB => {
413                        let b = unsafe { pop_unchecked(&mut self.stack) };
414                        let a = unsafe { pop_unchecked(&mut self.stack) };
415                        match vm_sub(&a, &b) {
416                            Ok(v) => self.stack.push(v),
417                            Err(err) => handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch),
418                        }
419                    }
420                    op::MUL => {
421                        let b = unsafe { pop_unchecked(&mut self.stack) };
422                        let a = unsafe { pop_unchecked(&mut self.stack) };
423                        match vm_mul(&a, &b) {
424                            Ok(v) => self.stack.push(v),
425                            Err(err) => handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch),
426                        }
427                    }
428                    op::DIV => {
429                        let b = unsafe { pop_unchecked(&mut self.stack) };
430                        let a = unsafe { pop_unchecked(&mut self.stack) };
431                        match vm_div(&a, &b) {
432                            Ok(v) => self.stack.push(v),
433                            Err(err) => handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch),
434                        }
435                    }
436                    op::NEGATE => {
437                        let a = unsafe { pop_unchecked(&mut self.stack) };
438                        if let Some(n) = a.as_int() {
439                            self.stack.push(Value::int(-n));
440                        } else if let Some(f) = a.as_float() {
441                            self.stack.push(Value::float(-f));
442                        } else {
443                            let err = SemaError::type_error("number", a.type_name());
444                            handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch);
445                        }
446                    }
447                    op::NOT => {
448                        let a = unsafe { pop_unchecked(&mut self.stack) };
449                        self.stack.push(Value::bool(!a.is_truthy()));
450                    }
451                    op::EQ => {
452                        let b = unsafe { pop_unchecked(&mut self.stack) };
453                        let a = unsafe { pop_unchecked(&mut self.stack) };
454                        self.stack.push(Value::bool(vm_eq(&a, &b)));
455                    }
456                    op::LT => {
457                        let b = unsafe { pop_unchecked(&mut self.stack) };
458                        let a = unsafe { pop_unchecked(&mut self.stack) };
459                        match vm_lt(&a, &b) {
460                            Ok(v) => self.stack.push(Value::bool(v)),
461                            Err(err) => handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch),
462                        }
463                    }
464                    op::GT => {
465                        let b = unsafe { pop_unchecked(&mut self.stack) };
466                        let a = unsafe { pop_unchecked(&mut self.stack) };
467                        match vm_lt(&b, &a) {
468                            Ok(v) => self.stack.push(Value::bool(v)),
469                            Err(err) => handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch),
470                        }
471                    }
472                    op::LE => {
473                        let b = unsafe { pop_unchecked(&mut self.stack) };
474                        let a = unsafe { pop_unchecked(&mut self.stack) };
475                        match vm_lt(&b, &a) {
476                            Ok(v) => self.stack.push(Value::bool(!v)),
477                            Err(err) => handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch),
478                        }
479                    }
480                    op::GE => {
481                        let b = unsafe { pop_unchecked(&mut self.stack) };
482                        let a = unsafe { pop_unchecked(&mut self.stack) };
483                        match vm_lt(&a, &b) {
484                            Ok(v) => self.stack.push(Value::bool(!v)),
485                            Err(err) => handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch),
486                        }
487                    }
488
489                    // --- Specialized int fast paths ---
490                    // These operate directly on raw u64 bits to avoid Clone/Drop overhead.
491                    // Small ints are immediates (no heap pointer), so we can safely
492                    // overwrite stack slots and adjust length without running destructors.
493                    op::ADD_INT => {
494                        let len = self.stack.len();
495                        let a_bits = unsafe { (*self.stack.as_ptr().add(len - 2)).raw_bits() };
496                        let b_bits = unsafe { (*self.stack.as_ptr().add(len - 1)).raw_bits() };
497                        if (a_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
498                            && (b_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
499                        {
500                            let sum = (a_bits.wrapping_add(b_bits)) & NAN_PAYLOAD_MASK;
501                            let result = NAN_INT_SMALL_PATTERN | sum;
502                            unsafe {
503                                std::ptr::write(
504                                    self.stack.as_mut_ptr().add(len - 2),
505                                    Value::from_raw_bits(result),
506                                );
507                                self.stack.set_len(len - 1);
508                            }
509                        } else {
510                            let b = unsafe { pop_unchecked(&mut self.stack) };
511                            let a = unsafe { pop_unchecked(&mut self.stack) };
512                            match vm_add(&a, &b) {
513                                Ok(v) => self.stack.push(v),
514                                Err(err) => {
515                                    handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch)
516                                }
517                            }
518                        }
519                    }
520                    op::SUB_INT => {
521                        let len = self.stack.len();
522                        let a_bits = unsafe { (*self.stack.as_ptr().add(len - 2)).raw_bits() };
523                        let b_bits = unsafe { (*self.stack.as_ptr().add(len - 1)).raw_bits() };
524                        if (a_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
525                            && (b_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
526                        {
527                            let diff = (a_bits.wrapping_sub(b_bits)) & NAN_PAYLOAD_MASK;
528                            let result = NAN_INT_SMALL_PATTERN | diff;
529                            unsafe {
530                                std::ptr::write(
531                                    self.stack.as_mut_ptr().add(len - 2),
532                                    Value::from_raw_bits(result),
533                                );
534                                self.stack.set_len(len - 1);
535                            }
536                        } else {
537                            let b = unsafe { pop_unchecked(&mut self.stack) };
538                            let a = unsafe { pop_unchecked(&mut self.stack) };
539                            match vm_sub(&a, &b) {
540                                Ok(v) => self.stack.push(v),
541                                Err(err) => {
542                                    handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch)
543                                }
544                            }
545                        }
546                    }
547                    op::MUL_INT => {
548                        let len = self.stack.len();
549                        let a_bits = unsafe { (*self.stack.as_ptr().add(len - 2)).raw_bits() };
550                        let b_bits = unsafe { (*self.stack.as_ptr().add(len - 1)).raw_bits() };
551                        if (a_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
552                            && (b_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
553                        {
554                            // Branchless sign-extension to i64
555                            let ax =
556                                (((a_bits & NAN_PAYLOAD_MASK) << SIGN_SHIFT) as i64) >> SIGN_SHIFT;
557                            let bx =
558                                (((b_bits & NAN_PAYLOAD_MASK) << SIGN_SHIFT) as i64) >> SIGN_SHIFT;
559                            // Use Value::int for multiplication — result may overflow 45 bits
560                            unsafe {
561                                std::ptr::write(
562                                    self.stack.as_mut_ptr().add(len - 2),
563                                    Value::int(ax.wrapping_mul(bx)),
564                                );
565                                self.stack.set_len(len - 1);
566                            }
567                        } else {
568                            let b = unsafe { pop_unchecked(&mut self.stack) };
569                            let a = unsafe { pop_unchecked(&mut self.stack) };
570                            match vm_mul(&a, &b) {
571                                Ok(v) => self.stack.push(v),
572                                Err(err) => {
573                                    handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch)
574                                }
575                            }
576                        }
577                    }
578                    op::LT_INT => {
579                        let len = self.stack.len();
580                        let a_bits = unsafe { (*self.stack.as_ptr().add(len - 2)).raw_bits() };
581                        let b_bits = unsafe { (*self.stack.as_ptr().add(len - 1)).raw_bits() };
582                        if (a_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
583                            && (b_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
584                        {
585                            // Branchless sign-extension and compare
586                            let ax =
587                                (((a_bits & NAN_PAYLOAD_MASK) << SIGN_SHIFT) as i64) >> SIGN_SHIFT;
588                            let bx =
589                                (((b_bits & NAN_PAYLOAD_MASK) << SIGN_SHIFT) as i64) >> SIGN_SHIFT;
590                            unsafe {
591                                std::ptr::write(
592                                    self.stack.as_mut_ptr().add(len - 2),
593                                    Value::bool(ax < bx),
594                                );
595                                self.stack.set_len(len - 1);
596                            }
597                        } else {
598                            let b = unsafe { pop_unchecked(&mut self.stack) };
599                            let a = unsafe { pop_unchecked(&mut self.stack) };
600                            match vm_lt(&a, &b) {
601                                Ok(v) => self.stack.push(Value::bool(v)),
602                                Err(err) => {
603                                    handle_err!(self, fi, pc, err, pc - op::SIZE_OP, 'dispatch)
604                                }
605                            }
606                        }
607                    }
608                    op::EQ_INT => {
609                        let len = self.stack.len();
610                        let a_bits = unsafe { (*self.stack.as_ptr().add(len - 2)).raw_bits() };
611                        let b_bits = unsafe { (*self.stack.as_ptr().add(len - 1)).raw_bits() };
612                        if (a_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
613                            && (b_bits & NAN_TAG_MASK) == NAN_INT_SMALL_PATTERN
614                        {
615                            // Small ints: equal iff same bits
616                            unsafe {
617                                std::ptr::write(
618                                    self.stack.as_mut_ptr().add(len - 2),
619                                    Value::bool(a_bits == b_bits),
620                                );
621                                self.stack.set_len(len - 1);
622                            }
623                        } else {
624                            let b = unsafe { pop_unchecked(&mut self.stack) };
625                            let a = unsafe { pop_unchecked(&mut self.stack) };
626                            self.stack.push(Value::bool(vm_eq(&a, &b)));
627                        }
628                    }
629
630                    op::LOAD_LOCAL0 => {
631                        let val = if has_open_upvalues {
632                            if let Some(ref open) = self.frames[fi].open_upvalues {
633                                if let Some(Some(cell)) = open.first() {
634                                    cell.value.borrow().clone()
635                                } else {
636                                    self.stack[base].clone()
637                                }
638                            } else {
639                                unreachable!()
640                            }
641                        } else {
642                            self.stack[base].clone()
643                        };
644                        self.stack.push(val);
645                    }
646                    op::LOAD_LOCAL1 => {
647                        let val = if has_open_upvalues {
648                            if let Some(ref open) = self.frames[fi].open_upvalues {
649                                if let Some(Some(cell)) = open.get(1) {
650                                    cell.value.borrow().clone()
651                                } else {
652                                    self.stack[base + 1].clone()
653                                }
654                            } else {
655                                unreachable!()
656                            }
657                        } else {
658                            self.stack[base + 1].clone()
659                        };
660                        self.stack.push(val);
661                    }
662                    op::LOAD_LOCAL2 => {
663                        let val = if has_open_upvalues {
664                            if let Some(ref open) = self.frames[fi].open_upvalues {
665                                if let Some(Some(cell)) = open.get(2) {
666                                    cell.value.borrow().clone()
667                                } else {
668                                    self.stack[base + 2].clone()
669                                }
670                            } else {
671                                unreachable!()
672                            }
673                        } else {
674                            self.stack[base + 2].clone()
675                        };
676                        self.stack.push(val);
677                    }
678                    op::LOAD_LOCAL3 => {
679                        let val = if has_open_upvalues {
680                            if let Some(ref open) = self.frames[fi].open_upvalues {
681                                if let Some(Some(cell)) = open.get(3) {
682                                    cell.value.borrow().clone()
683                                } else {
684                                    self.stack[base + 3].clone()
685                                }
686                            } else {
687                                unreachable!()
688                            }
689                        } else {
690                            self.stack[base + 3].clone()
691                        };
692                        self.stack.push(val);
693                    }
694
695                    // Fused LOAD_GLOBAL + CALL: look up global, call without
696                    // pushing the function value onto the stack.
697                    op::CALL_GLOBAL => {
698                        let bits = read_u32!(code, pc);
699                        let argc = read_u16!(code, pc) as usize;
700                        self.frames[fi].pc = pc;
701                        let saved_pc = pc - op::SIZE_CALL_GLOBAL;
702
703                        // Look up the global (with cache)
704                        let version = self.globals.version.get();
705                        let slot = (bits as usize) & (GLOBAL_CACHE_SIZE - 1);
706                        let entry = &self.global_cache[slot];
707                        let func_val = if entry.0 == bits && entry.1 == version {
708                            entry.2.clone()
709                        } else {
710                            let spur: Spur = unsafe { std::mem::transmute::<u32, Spur>(bits) };
711                            match self.globals.get(spur) {
712                                Some(val) => {
713                                    self.global_cache[slot] = (bits, version, val.clone());
714                                    val
715                                }
716                                None => {
717                                    let err = SemaError::Unbound(resolve_spur(spur));
718                                    handle_err!(self, fi, pc, err, saved_pc, 'dispatch);
719                                }
720                            }
721                        };
722
723                        // Fast path: VM closure — use direct call without function slot
724                        if func_val.raw_tag() == Some(TAG_NATIVE_FN) {
725                            let vm_closure_data = {
726                                let native = func_val.as_native_fn_ref().unwrap();
727                                native.payload.as_ref().and_then(|p| {
728                                    p.downcast_ref::<VmClosurePayload>().map(|vmc| {
729                                        let closure = vmc.closure.clone();
730                                        let functions =
731                                            if Rc::ptr_eq(&vmc.functions, &self.functions) {
732                                                None
733                                            } else {
734                                                Some(vmc.functions.clone())
735                                            };
736                                        (closure, functions)
737                                    })
738                                })
739                            };
740                            if let Some((closure, functions)) = vm_closure_data {
741                                if let Some(f) = functions {
742                                    self.functions = f;
743                                }
744                                if let Err(err) = self.call_vm_closure_direct(closure, argc) {
745                                    match self.handle_exception(err, saved_pc)? {
746                                        ExceptionAction::Handled => {}
747                                        ExceptionAction::Propagate(e) => return Err(e),
748                                    }
749                                }
750                                continue 'dispatch;
751                            }
752                        }
753
754                        // Slow path: non-VM callable — use call_value_with
755                        if let Err(err) = self.call_value_with(func_val, argc, ctx) {
756                            match self.handle_exception(err, saved_pc)? {
757                                ExceptionAction::Handled => {}
758                                ExceptionAction::Propagate(e) => return Err(e),
759                            }
760                        }
761                        continue 'dispatch;
762                    }
763
764                    op::STORE_LOCAL0 => {
765                        let val = unsafe { pop_unchecked(&mut self.stack) };
766                        self.stack[base] = val.clone();
767                        if has_open_upvalues {
768                            if let Some(ref open) = self.frames[fi].open_upvalues {
769                                if let Some(Some(cell)) = open.first() {
770                                    *cell.value.borrow_mut() = val;
771                                }
772                            }
773                        }
774                    }
775                    op::STORE_LOCAL1 => {
776                        let val = unsafe { pop_unchecked(&mut self.stack) };
777                        self.stack[base + 1] = val.clone();
778                        if has_open_upvalues {
779                            if let Some(ref open) = self.frames[fi].open_upvalues {
780                                if let Some(Some(cell)) = open.get(1) {
781                                    *cell.value.borrow_mut() = val;
782                                }
783                            }
784                        }
785                    }
786                    op::STORE_LOCAL2 => {
787                        let val = unsafe { pop_unchecked(&mut self.stack) };
788                        self.stack[base + 2] = val.clone();
789                        if has_open_upvalues {
790                            if let Some(ref open) = self.frames[fi].open_upvalues {
791                                if let Some(Some(cell)) = open.get(2) {
792                                    *cell.value.borrow_mut() = val;
793                                }
794                            }
795                        }
796                    }
797                    op::STORE_LOCAL3 => {
798                        let val = unsafe { pop_unchecked(&mut self.stack) };
799                        self.stack[base + 3] = val.clone();
800                        if has_open_upvalues {
801                            if let Some(ref open) = self.frames[fi].open_upvalues {
802                                if let Some(Some(cell)) = open.get(3) {
803                                    *cell.value.borrow_mut() = val;
804                                }
805                            }
806                        }
807                    }
808
809                    _ => {
810                        return Err(SemaError::eval(format!("VM: invalid opcode {}", op)));
811                    }
812                }
813            }
814        }
815    }
816
817    // --- Function call implementation ---
818
819    fn call_value(&mut self, argc: usize, ctx: &EvalContext) -> Result<(), SemaError> {
820        let func_idx = self.stack.len() - 1 - argc;
821
822        // Fast path: peek at tag without Rc refcount bump.
823        if self.stack[func_idx].raw_tag() == Some(TAG_NATIVE_FN) {
824            // Check for VM closure payload without holding a borrow across mutation.
825            // Extract closure (cloned Rc) in a block; only clone functions if different.
826            let vm_closure_data = {
827                let native = self.stack[func_idx].as_native_fn_ref().unwrap();
828                native.payload.as_ref().and_then(|p| {
829                    p.downcast_ref::<VmClosurePayload>().map(|vmc| {
830                        let closure = vmc.closure.clone();
831                        // Only clone functions Rc if it's a different table
832                        let functions = if Rc::ptr_eq(&vmc.functions, &self.functions) {
833                            None
834                        } else {
835                            Some(vmc.functions.clone())
836                        };
837                        (closure, functions)
838                    })
839                })
840            };
841            if let Some((closure, functions)) = vm_closure_data {
842                if let Some(f) = functions {
843                    self.functions = f;
844                }
845                return self.call_vm_closure(closure, argc);
846            }
847            // Regular native fn — need Rc for the call
848            let func_rc = self.stack[func_idx].as_native_fn_rc().unwrap();
849            let args_start = func_idx + 1;
850            let args: Vec<Value> = self.stack.drain(args_start..).collect();
851            self.stack.truncate(func_idx);
852            let result = (func_rc.func)(ctx, &args)?;
853            self.stack.push(result);
854            Ok(())
855        } else if let Some(kw) = self.stack[func_idx].as_keyword_spur() {
856            // Keyword as function: (kw map) -> map[kw]
857            if argc != 1 {
858                return Err(SemaError::arity(resolve_spur(kw), "1", argc));
859            }
860            let arg = self.stack.pop().unwrap();
861            self.stack.pop(); // pop keyword
862            let kw_val = Value::keyword_from_spur(kw);
863            let result = if let Some(m) = arg.as_map_rc() {
864                m.get(&kw_val).cloned().unwrap_or(Value::nil())
865            } else if let Some(m) = arg.as_hashmap_rc() {
866                m.get(&kw_val).cloned().unwrap_or(Value::nil())
867            } else {
868                return Err(SemaError::type_error("map or hashmap", arg.type_name()));
869            };
870            self.stack.push(result);
871            Ok(())
872        } else {
873            // Lambda or other callable — clone once and use call_callback
874            let func_val = self.stack[func_idx].clone();
875            let args_start = func_idx + 1;
876            let args: Vec<Value> = self.stack.drain(args_start..).collect();
877            self.stack.truncate(func_idx);
878            let result = sema_core::call_callback(ctx, &func_val, &args)?;
879            self.stack.push(result);
880            Ok(())
881        }
882    }
883
884    fn tail_call_value(&mut self, argc: usize, ctx: &EvalContext) -> Result<(), SemaError> {
885        let func_idx = self.stack.len() - 1 - argc;
886
887        // Fast path: peek at tag without Rc refcount bump
888        if self.stack[func_idx].raw_tag() == Some(TAG_NATIVE_FN) {
889            let vm_closure_data = {
890                let native = self.stack[func_idx].as_native_fn_ref().unwrap();
891                native.payload.as_ref().and_then(|p| {
892                    p.downcast_ref::<VmClosurePayload>().map(|vmc| {
893                        let closure = vmc.closure.clone();
894                        let functions = if Rc::ptr_eq(&vmc.functions, &self.functions) {
895                            None
896                        } else {
897                            Some(vmc.functions.clone())
898                        };
899                        (closure, functions)
900                    })
901                })
902            };
903            if let Some((closure, functions)) = vm_closure_data {
904                if let Some(f) = functions {
905                    self.functions = f;
906                }
907                return self.tail_call_vm_closure(closure, argc);
908            }
909        }
910
911        // Non-VM callables: regular call (no TCO possible)
912        self.call_value(argc, ctx)
913    }
914
915    /// Call a function value that's NOT on the stack (for CALL_GLOBAL slow path).
916    /// The args are on top of the stack.
917    fn call_value_with(
918        &mut self,
919        func_val: Value,
920        argc: usize,
921        ctx: &EvalContext,
922    ) -> Result<(), SemaError> {
923        if func_val.raw_tag() == Some(TAG_NATIVE_FN) {
924            let func_rc = func_val.as_native_fn_rc().unwrap();
925            let args_start = self.stack.len() - argc;
926            let args: Vec<Value> = self.stack.drain(args_start..).collect();
927            let result = (func_rc.func)(ctx, &args)?;
928            self.stack.push(result);
929            Ok(())
930        } else if let Some(kw) = func_val.as_keyword_spur() {
931            if argc != 1 {
932                return Err(SemaError::arity(resolve_spur(kw), "1", argc));
933            }
934            let arg = self.stack.pop().unwrap();
935            let kw_val = Value::keyword_from_spur(kw);
936            let result = if let Some(m) = arg.as_map_rc() {
937                m.get(&kw_val).cloned().unwrap_or(Value::nil())
938            } else if let Some(m) = arg.as_hashmap_rc() {
939                m.get(&kw_val).cloned().unwrap_or(Value::nil())
940            } else {
941                return Err(SemaError::type_error("map or hashmap", arg.type_name()));
942            };
943            self.stack.push(result);
944            Ok(())
945        } else {
946            let args_start = self.stack.len() - argc;
947            let args: Vec<Value> = self.stack.drain(args_start..).collect();
948            let result = sema_core::call_callback(ctx, &func_val, &args)?;
949            self.stack.push(result);
950            Ok(())
951        }
952    }
953
954    /// Push a new CallFrame for a VM closure called via CALL_GLOBAL.
955    /// No function value is on the stack — only args. `base = stack.len() - argc`.
956    /// Args are already in place; we just extend the stack for remaining locals.
957    fn call_vm_closure_direct(
958        &mut self,
959        closure: Rc<Closure>,
960        argc: usize,
961    ) -> Result<(), SemaError> {
962        let func = &closure.func;
963        let arity = func.arity as usize;
964        let has_rest = func.has_rest;
965        let n_locals = func.chunk.n_locals as usize;
966
967        // Arity check
968        if has_rest {
969            if argc < arity {
970                return Err(SemaError::arity(
971                    func.name
972                        .map(resolve_spur)
973                        .unwrap_or_else(|| "<lambda>".to_string()),
974                    format!("{}+", arity),
975                    argc,
976                ));
977            }
978        } else if argc != arity {
979            return Err(SemaError::arity(
980                func.name
981                    .map(resolve_spur)
982                    .unwrap_or_else(|| "<lambda>".to_string()),
983                arity.to_string(),
984                argc,
985            ));
986        }
987
988        // Args are already at stack[base..base+argc] in the right order.
989        // base = stack.len() - argc
990        let base = self.stack.len() - argc;
991
992        if has_rest {
993            // Collect extra args into a rest list
994            let rest: Vec<Value> = self.stack[base + arity..base + argc].to_vec();
995            self.stack.truncate(base + arity);
996            self.stack.push(Value::list(rest));
997        }
998
999        // Resize to exact local count (pads with nil or truncates)
1000        self.stack.resize(base + n_locals, Value::nil());
1001
1002        self.frames.push(CallFrame {
1003            closure,
1004            pc: 0,
1005            base,
1006            open_upvalues: None,
1007        });
1008
1009        Ok(())
1010    }
1011
1012    /// Push a new CallFrame for a VM closure (no Rust recursion).
1013    /// Caller must set `self.functions` before calling this.
1014    /// Takes ownership of the Rc to avoid an extra clone.
1015    fn call_vm_closure(&mut self, closure: Rc<Closure>, argc: usize) -> Result<(), SemaError> {
1016        let func = &closure.func;
1017        let arity = func.arity as usize;
1018        let has_rest = func.has_rest;
1019        let n_locals = func.chunk.n_locals as usize;
1020
1021        // Arity check
1022        if has_rest {
1023            if argc < arity {
1024                return Err(SemaError::arity(
1025                    func.name
1026                        .map(resolve_spur)
1027                        .unwrap_or_else(|| "<lambda>".to_string()),
1028                    format!("{}+", arity),
1029                    argc,
1030                ));
1031            }
1032        } else if argc != arity {
1033            return Err(SemaError::arity(
1034                func.name
1035                    .map(resolve_spur)
1036                    .unwrap_or_else(|| "<lambda>".to_string()),
1037                arity.to_string(),
1038                argc,
1039            ));
1040        }
1041
1042        // Copy args directly from stack into new locals — no Vec allocation
1043        let func_idx = self.stack.len() - 1 - argc;
1044        let base = func_idx; // reuse the callee's slot as new frame base
1045
1046        // Copy params: clone each arg into its local slot.
1047        // dest (base+i) < src (func_idx+1+i) so forward copy is safe.
1048        Self::copy_args_to_locals(&mut self.stack, base, func_idx + 1, arity, argc, has_rest);
1049
1050        // Now resize to exact local count (pads with nil or truncates excess args)
1051        self.stack.resize(base + n_locals, Value::nil());
1052
1053        // Push frame
1054        self.frames.push(CallFrame {
1055            closure,
1056            pc: 0,
1057            base,
1058            open_upvalues: None,
1059        });
1060
1061        Ok(())
1062    }
1063
1064    /// Tail-call a VM closure: reuse the current frame's stack space.
1065    /// Caller must set `self.functions` before calling this.
1066    /// Takes ownership of the Rc to avoid an extra clone.
1067    fn tail_call_vm_closure(&mut self, closure: Rc<Closure>, argc: usize) -> Result<(), SemaError> {
1068        let func = &closure.func;
1069        let arity = func.arity as usize;
1070        let has_rest = func.has_rest;
1071        let n_locals = func.chunk.n_locals as usize;
1072
1073        // Arity check
1074        if has_rest {
1075            if argc < arity {
1076                return Err(SemaError::arity(
1077                    func.name
1078                        .map(resolve_spur)
1079                        .unwrap_or_else(|| "<lambda>".to_string()),
1080                    format!("{}+", arity),
1081                    argc,
1082                ));
1083            }
1084        } else if argc != arity {
1085            return Err(SemaError::arity(
1086                func.name
1087                    .map(resolve_spur)
1088                    .unwrap_or_else(|| "<lambda>".to_string()),
1089                arity.to_string(),
1090                argc,
1091            ));
1092        }
1093
1094        // Copy args directly into current frame's base — no Vec allocation
1095        let func_idx = self.stack.len() - 1 - argc;
1096        let base = self.frames.last().unwrap().base;
1097
1098        // Copy args into base slots (args are above base, no overlap issues)
1099        Self::copy_args_to_locals(&mut self.stack, base, func_idx + 1, arity, argc, has_rest);
1100
1101        // Resize to exact local count (pads with nil or truncates excess)
1102        self.stack.resize(base + n_locals, Value::nil());
1103
1104        // Replace current frame (reuse slot)
1105        let frame = self.frames.last_mut().unwrap();
1106        frame.closure = closure;
1107        frame.pc = 0;
1108        // base stays the same
1109        frame.open_upvalues = None;
1110
1111        Ok(())
1112    }
1113
1114    /// Copy args from the stack into local slots, handling rest params.
1115    /// `dst` is the base index for destination, `src` is the start of args.
1116    #[inline(always)]
1117    fn copy_args_to_locals(
1118        stack: &mut [Value],
1119        dst: usize,
1120        src: usize,
1121        arity: usize,
1122        argc: usize,
1123        has_rest: bool,
1124    ) {
1125        if has_rest {
1126            let rest: Vec<Value> = stack[src + arity..src + argc].to_vec();
1127            for i in 0..arity {
1128                stack[dst + i] = stack[src + i].clone();
1129            }
1130            stack[dst + arity] = Value::list(rest);
1131        } else {
1132            for i in 0..arity {
1133                stack[dst + i] = stack[src + i].clone();
1134            }
1135        }
1136    }
1137
1138    // --- MakeClosure ---
1139
1140    fn make_closure(&mut self) -> Result<(), SemaError> {
1141        // Read instruction operands from the current frame's bytecode.
1142        // We extract everything we need first, then release the borrow.
1143        let frame = self.frames.last().unwrap();
1144        let code = &frame.closure.func.chunk.code;
1145        let pc = frame.pc + 1;
1146        let func_id = u16::from_le_bytes([code[pc], code[pc + 1]]) as usize;
1147        let n_upvalues = u16::from_le_bytes([code[pc + 2], code[pc + 3]]) as usize;
1148
1149        // Collect upvalue descriptors
1150        let mut uv_descs = Vec::with_capacity(n_upvalues);
1151        let mut uv_pc = pc + 4;
1152        for _ in 0..n_upvalues {
1153            let is_local = u16::from_le_bytes([code[uv_pc], code[uv_pc + 1]]);
1154            let idx = u16::from_le_bytes([code[uv_pc + 2], code[uv_pc + 3]]) as usize;
1155            uv_pc += 4;
1156            uv_descs.push((is_local != 0, idx));
1157        }
1158
1159        let base = frame.base;
1160        let parent_upvalues = frame.closure.upvalues.clone();
1161        // Release the immutable borrow before mutating
1162        let _ = frame;
1163
1164        let func = self.functions[func_id].clone();
1165        let mut upvalues = Vec::with_capacity(n_upvalues);
1166
1167        for (is_local, idx) in &uv_descs {
1168            if *is_local {
1169                // Capture from current frame's local slot using a shared UpvalueCell.
1170                // Lazily allocate open_upvalues on first capture.
1171                let frame = self.frames.last_mut().unwrap();
1172                let n_locals = frame.closure.func.chunk.n_locals as usize;
1173                let open = frame
1174                    .open_upvalues
1175                    .get_or_insert_with(|| vec![None; n_locals]);
1176                let cell = if let Some(existing) = &open[*idx] {
1177                    existing.clone()
1178                } else {
1179                    let val = self.stack[base + *idx].clone();
1180                    let cell = Rc::new(UpvalueCell::new(val));
1181                    open[*idx] = Some(cell.clone());
1182                    cell
1183                };
1184                upvalues.push(cell);
1185            } else {
1186                // Capture from current frame's upvalue
1187                upvalues.push(parent_upvalues[*idx].clone());
1188            }
1189        }
1190
1191        // Update pc past the entire instruction
1192        self.frames.last_mut().unwrap().pc = uv_pc;
1193
1194        let closure = Rc::new(Closure { func, upvalues });
1195        let payload: Rc<dyn std::any::Any> = Rc::new(VmClosurePayload {
1196            closure: closure.clone(),
1197            functions: self.functions.clone(),
1198        });
1199        let closure_for_fallback = closure.clone();
1200        let functions = self.functions.clone();
1201        let globals = self.globals.clone();
1202
1203        // The NativeFn wrapper is used as a fallback when called from outside the VM
1204        // (e.g., from stdlib HOFs like map/filter). Inside the VM, call_value detects
1205        // the payload and pushes a CallFrame instead — no Rust recursion.
1206        let native = Value::native_fn_from_rc(Rc::new(sema_core::NativeFn::with_payload(
1207            closure_for_fallback
1208                .func
1209                .name
1210                .map(resolve_spur)
1211                .unwrap_or_else(|| "<vm-closure>".to_string()),
1212            payload,
1213            move |ctx, args| {
1214                let mut vm = VM::new_with_rc_functions(globals.clone(), functions.clone());
1215                let func = &closure_for_fallback.func;
1216                let arity = func.arity as usize;
1217                let has_rest = func.has_rest;
1218                let n_locals = func.chunk.n_locals as usize;
1219
1220                if has_rest {
1221                    if args.len() < arity {
1222                        return Err(SemaError::arity(
1223                            func.name
1224                                .map(resolve_spur)
1225                                .unwrap_or_else(|| "<lambda>".to_string()),
1226                            format!("{}+", arity),
1227                            args.len(),
1228                        ));
1229                    }
1230                } else if args.len() != arity {
1231                    return Err(SemaError::arity(
1232                        func.name
1233                            .map(resolve_spur)
1234                            .unwrap_or_else(|| "<lambda>".to_string()),
1235                        arity.to_string(),
1236                        args.len(),
1237                    ));
1238                }
1239
1240                vm.stack.resize(n_locals, Value::nil());
1241
1242                if has_rest {
1243                    for i in 0..arity {
1244                        vm.stack[i] = args.get(i).cloned().unwrap_or(Value::nil());
1245                    }
1246                    let rest: Vec<Value> = args[arity..].to_vec();
1247                    vm.stack[arity] = Value::list(rest);
1248                } else {
1249                    for i in 0..arity {
1250                        vm.stack[i] = args.get(i).cloned().unwrap_or(Value::nil());
1251                    }
1252                }
1253
1254                vm.frames.push(CallFrame {
1255                    closure: closure_for_fallback.clone(),
1256                    pc: 0,
1257                    base: 0,
1258                    open_upvalues: None,
1259                });
1260                vm.run(ctx)
1261            },
1262        )));
1263
1264        self.stack.push(native);
1265        Ok(())
1266    }
1267
1268    // --- Exception handling ---
1269
1270    #[cold]
1271    #[inline(never)]
1272    fn handle_exception(
1273        &mut self,
1274        err: SemaError,
1275        failing_pc: usize,
1276    ) -> Result<ExceptionAction, SemaError> {
1277        let mut pc_for_lookup = failing_pc as u32;
1278        // Walk frames from top looking for a handler
1279        while let Some(frame) = self.frames.last() {
1280            let chunk = &frame.closure.func.chunk;
1281
1282            // Check exception table for this frame
1283            let mut found = None;
1284            for entry in &chunk.exception_table {
1285                if pc_for_lookup >= entry.try_start && pc_for_lookup < entry.try_end {
1286                    found = Some(entry.clone());
1287                    break;
1288                }
1289            }
1290
1291            if let Some(entry) = found {
1292                // Restore stack to handler state
1293                let base = frame.base;
1294                self.stack.truncate(base + entry.stack_depth as usize);
1295
1296                // Push error value as a map matching the tree-walker's error_to_value format
1297                let error_val = error_to_value(&err);
1298                self.stack.push(error_val);
1299
1300                // Jump to handler
1301                let frame = self.frames.last_mut().unwrap();
1302                frame.pc = entry.handler_pc as usize;
1303                return Ok(ExceptionAction::Handled);
1304            }
1305
1306            // No handler in this frame, pop it and try parent
1307            let frame = self.frames.pop().unwrap();
1308            self.stack.truncate(frame.base);
1309            // Parent frames use their own pc for lookup.
1310            // parent.pc is the *resume* PC (the byte after the CALL instruction).
1311            // Exception table intervals are half-open [try_start, try_end), so if the
1312            // CALL was the last instruction in the try body, parent.pc == try_end and
1313            // the lookup would miss. Subtract 1 to land inside the CALL instruction.
1314            if let Some(parent) = self.frames.last() {
1315                pc_for_lookup = parent.pc.saturating_sub(1) as u32;
1316            }
1317        }
1318
1319        // No handler found anywhere
1320        Ok(ExceptionAction::Propagate(err))
1321    }
1322}
1323
1324enum ExceptionAction {
1325    Handled,
1326    Propagate(SemaError),
1327}
1328
1329/// Convert a SemaError into a Sema map value, matching the tree-walker's format.
1330fn error_to_value(err: &SemaError) -> Value {
1331    let inner = err.inner();
1332    let mut map = BTreeMap::new();
1333    match inner {
1334        SemaError::Eval(msg) => {
1335            map.insert(Value::keyword("type"), Value::keyword("eval"));
1336            map.insert(Value::keyword("message"), Value::string(msg));
1337        }
1338        SemaError::Type { expected, got } => {
1339            map.insert(Value::keyword("type"), Value::keyword("type-error"));
1340            map.insert(
1341                Value::keyword("message"),
1342                Value::string(&format!("expected {expected}, got {got}")),
1343            );
1344            map.insert(Value::keyword("expected"), Value::string(expected));
1345            map.insert(Value::keyword("got"), Value::string(got));
1346        }
1347        SemaError::Arity {
1348            name,
1349            expected,
1350            got,
1351        } => {
1352            map.insert(Value::keyword("type"), Value::keyword("arity"));
1353            map.insert(
1354                Value::keyword("message"),
1355                Value::string(&format!("{name} expects {expected} args, got {got}")),
1356            );
1357        }
1358        SemaError::Unbound(name) => {
1359            map.insert(Value::keyword("type"), Value::keyword("unbound"));
1360            map.insert(
1361                Value::keyword("message"),
1362                Value::string(&format!("Unbound variable: {name}")),
1363            );
1364            map.insert(Value::keyword("name"), Value::string(name));
1365        }
1366        SemaError::UserException(val) => {
1367            map.insert(Value::keyword("type"), Value::keyword("user"));
1368            map.insert(Value::keyword("message"), Value::string(&val.to_string()));
1369            map.insert(Value::keyword("value"), val.clone());
1370        }
1371        SemaError::Io(msg) => {
1372            map.insert(Value::keyword("type"), Value::keyword("io"));
1373            map.insert(Value::keyword("message"), Value::string(msg));
1374        }
1375        SemaError::Llm(msg) => {
1376            map.insert(Value::keyword("type"), Value::keyword("llm"));
1377            map.insert(Value::keyword("message"), Value::string(msg));
1378        }
1379        SemaError::Reader { message, span } => {
1380            map.insert(Value::keyword("type"), Value::keyword("reader"));
1381            map.insert(
1382                Value::keyword("message"),
1383                Value::string(&format!("{message} at {span}")),
1384            );
1385        }
1386        SemaError::PermissionDenied {
1387            function,
1388            capability,
1389        } => {
1390            map.insert(Value::keyword("type"), Value::keyword("permission-denied"));
1391            map.insert(
1392                Value::keyword("message"),
1393                Value::string(&format!(
1394                    "Permission denied: {function} requires '{capability}' capability"
1395                )),
1396            );
1397            map.insert(Value::keyword("function"), Value::string(function));
1398            map.insert(Value::keyword("capability"), Value::string(capability));
1399        }
1400        SemaError::PathDenied { function, path } => {
1401            map.insert(Value::keyword("type"), Value::keyword("permission-denied"));
1402            map.insert(
1403                Value::keyword("message"),
1404                Value::string(&format!(
1405                    "Permission denied: {function} — path '{path}' is outside allowed directories"
1406                )),
1407            );
1408            map.insert(Value::keyword("function"), Value::string(function));
1409            map.insert(Value::keyword("path"), Value::string(path));
1410        }
1411        SemaError::WithTrace { .. } | SemaError::WithContext { .. } => {
1412            unreachable!("inner() already unwraps these")
1413        }
1414    }
1415    Value::map(map)
1416}
1417
1418// --- Arithmetic helpers ---
1419
1420#[inline(always)]
1421fn vm_add(a: &Value, b: &Value) -> Result<Value, SemaError> {
1422    use sema_core::ValueView;
1423    match (a.view(), b.view()) {
1424        (ValueView::Int(x), ValueView::Int(y)) => Ok(Value::int(x.wrapping_add(y))),
1425        (ValueView::Float(x), ValueView::Float(y)) => Ok(Value::float(x + y)),
1426        (ValueView::Int(x), ValueView::Float(y)) => Ok(Value::float(x as f64 + y)),
1427        (ValueView::Float(x), ValueView::Int(y)) => Ok(Value::float(x + y as f64)),
1428        (ValueView::String(x), ValueView::String(y)) => {
1429            let mut s = (*x).clone();
1430            s.push_str(&y);
1431            Ok(Value::string(&s))
1432        }
1433        _ => Err(SemaError::type_error(
1434            "number or string",
1435            format!("{} and {}", a.type_name(), b.type_name()),
1436        )),
1437    }
1438}
1439
1440#[inline(always)]
1441fn vm_sub(a: &Value, b: &Value) -> Result<Value, SemaError> {
1442    use sema_core::ValueView;
1443    match (a.view(), b.view()) {
1444        (ValueView::Int(x), ValueView::Int(y)) => Ok(Value::int(x.wrapping_sub(y))),
1445        (ValueView::Float(x), ValueView::Float(y)) => Ok(Value::float(x - y)),
1446        (ValueView::Int(x), ValueView::Float(y)) => Ok(Value::float(x as f64 - y)),
1447        (ValueView::Float(x), ValueView::Int(y)) => Ok(Value::float(x - y as f64)),
1448        _ => Err(SemaError::type_error(
1449            "number",
1450            format!("{} and {}", a.type_name(), b.type_name()),
1451        )),
1452    }
1453}
1454
1455#[inline(always)]
1456fn vm_mul(a: &Value, b: &Value) -> Result<Value, SemaError> {
1457    use sema_core::ValueView;
1458    match (a.view(), b.view()) {
1459        (ValueView::Int(x), ValueView::Int(y)) => Ok(Value::int(x.wrapping_mul(y))),
1460        (ValueView::Float(x), ValueView::Float(y)) => Ok(Value::float(x * y)),
1461        (ValueView::Int(x), ValueView::Float(y)) => Ok(Value::float(x as f64 * y)),
1462        (ValueView::Float(x), ValueView::Int(y)) => Ok(Value::float(x * y as f64)),
1463        _ => Err(SemaError::type_error(
1464            "number",
1465            format!("{} and {}", a.type_name(), b.type_name()),
1466        )),
1467    }
1468}
1469
1470#[inline(always)]
1471fn vm_div(a: &Value, b: &Value) -> Result<Value, SemaError> {
1472    use sema_core::ValueView;
1473    match (a.view(), b.view()) {
1474        (ValueView::Int(_), ValueView::Int(0)) => Err(SemaError::eval("division by zero")),
1475        (ValueView::Int(x), ValueView::Int(y)) => {
1476            let result = x as f64 / y as f64;
1477            if result.fract() == 0.0 {
1478                Ok(Value::int(result as i64))
1479            } else {
1480                Ok(Value::float(result))
1481            }
1482        }
1483        (ValueView::Float(x), ValueView::Float(y)) => Ok(Value::float(x / y)),
1484        (ValueView::Int(x), ValueView::Float(y)) => Ok(Value::float(x as f64 / y)),
1485        (ValueView::Float(x), ValueView::Int(y)) => Ok(Value::float(x / y as f64)),
1486        _ => Err(SemaError::type_error(
1487            "number",
1488            format!("{} and {}", a.type_name(), b.type_name()),
1489        )),
1490    }
1491}
1492
1493/// Numeric-coercing equality: matches stdlib `=` semantics.
1494#[inline(always)]
1495fn vm_eq(a: &Value, b: &Value) -> bool {
1496    use sema_core::ValueView;
1497    match (a.view(), b.view()) {
1498        (ValueView::Int(x), ValueView::Int(y)) => x == y,
1499        (ValueView::Float(x), ValueView::Float(y)) => x == y,
1500        (ValueView::Int(x), ValueView::Float(y)) | (ValueView::Float(y), ValueView::Int(x)) => {
1501            (x as f64) == y
1502        }
1503        _ => a == b,
1504    }
1505}
1506
1507fn vm_lt(a: &Value, b: &Value) -> Result<bool, SemaError> {
1508    use sema_core::ValueView;
1509    match (a.view(), b.view()) {
1510        (ValueView::Int(x), ValueView::Int(y)) => Ok(x < y),
1511        (ValueView::Float(x), ValueView::Float(y)) => Ok(x < y),
1512        (ValueView::Int(x), ValueView::Float(y)) => Ok((x as f64) < y),
1513        (ValueView::Float(x), ValueView::Int(y)) => Ok(x < (y as f64)),
1514        (ValueView::String(x), ValueView::String(y)) => Ok(x < y),
1515        _ => Err(SemaError::type_error(
1516            "comparable values",
1517            format!("{} and {}", a.type_name(), b.type_name()),
1518        )),
1519    }
1520}
1521
1522/// Compile a sequence of Value ASTs through the full pipeline and produce
1523/// the entry closure + function table ready for VM execution.
1524pub fn compile_program(vals: &[Value]) -> Result<(Rc<Closure>, Vec<Rc<Function>>), SemaError> {
1525    let mut resolved = Vec::new();
1526    let mut total_locals: u16 = 0;
1527    for val in vals {
1528        let core = crate::lower::lower(val)?;
1529        let (res, n) = crate::resolve::resolve_with_locals(&core)?;
1530        total_locals = total_locals.max(n);
1531        resolved.push(res);
1532    }
1533    let result = crate::compiler::compile_many_with_locals(&resolved, total_locals)?;
1534
1535    let functions: Vec<Rc<Function>> = result.functions.into_iter().map(Rc::new).collect();
1536    let closure = Rc::new(Closure {
1537        func: Rc::new(Function {
1538            name: None,
1539            chunk: result.chunk,
1540            upvalue_descs: Vec::new(),
1541            arity: 0,
1542            has_rest: false,
1543            local_names: Vec::new(),
1544        }),
1545        upvalues: Vec::new(),
1546    });
1547
1548    Ok((closure, functions))
1549}
1550
1551/// Convenience: compile and run a string expression in the VM.
1552pub fn eval_str(input: &str, globals: &Rc<Env>, ctx: &EvalContext) -> Result<Value, SemaError> {
1553    let vals =
1554        sema_reader::read_many(input).map_err(|e| SemaError::eval(format!("parse error: {e}")))?;
1555    let (closure, functions) = compile_program(&vals)?;
1556    let mut vm = VM::new(globals.clone(), functions);
1557    vm.execute(closure, ctx)
1558}
1559
1560#[cfg(test)]
1561mod tests {
1562    use super::*;
1563    use sema_core::{intern, NativeFn};
1564
1565    fn make_test_env() -> Rc<Env> {
1566        let env = Rc::new(Env::new());
1567        env.set(
1568            intern("+"),
1569            Value::native_fn(NativeFn::simple("+", |args| vm_add(&args[0], &args[1]))),
1570        );
1571        env.set(
1572            intern("-"),
1573            Value::native_fn(NativeFn::simple("-", |args| vm_sub(&args[0], &args[1]))),
1574        );
1575        env.set(
1576            intern("*"),
1577            Value::native_fn(NativeFn::simple("*", |args| vm_mul(&args[0], &args[1]))),
1578        );
1579        env.set(
1580            intern("/"),
1581            Value::native_fn(NativeFn::simple("/", |args| vm_div(&args[0], &args[1]))),
1582        );
1583        env.set(
1584            intern("="),
1585            Value::native_fn(NativeFn::simple("=", |args| {
1586                Ok(Value::bool(vm_eq(&args[0], &args[1])))
1587            })),
1588        );
1589        env.set(
1590            intern("<"),
1591            Value::native_fn(NativeFn::simple("<", |args| {
1592                Ok(Value::bool(vm_lt(&args[0], &args[1])?))
1593            })),
1594        );
1595        env.set(
1596            intern(">"),
1597            Value::native_fn(NativeFn::simple(">", |args| {
1598                Ok(Value::bool(vm_lt(&args[1], &args[0])?))
1599            })),
1600        );
1601        env.set(
1602            intern("not"),
1603            Value::native_fn(NativeFn::simple("not", |args| {
1604                Ok(Value::bool(!args[0].is_truthy()))
1605            })),
1606        );
1607        env.set(
1608            intern("list"),
1609            Value::native_fn(NativeFn::simple("list", |args| {
1610                Ok(Value::list(args.to_vec()))
1611            })),
1612        );
1613        env
1614    }
1615
1616    fn eval(input: &str) -> Result<Value, SemaError> {
1617        let globals = make_test_env();
1618        let ctx = EvalContext::new();
1619        eval_str(input, &globals, &ctx)
1620    }
1621
1622    #[test]
1623    fn test_vm_int_literal() {
1624        assert_eq!(eval("42").unwrap(), Value::int(42));
1625    }
1626
1627    #[test]
1628    fn test_vm_nil() {
1629        assert_eq!(eval("nil").unwrap(), Value::nil());
1630    }
1631
1632    #[test]
1633    fn test_vm_bool() {
1634        assert_eq!(eval("#t").unwrap(), Value::bool(true));
1635        assert_eq!(eval("#f").unwrap(), Value::bool(false));
1636    }
1637
1638    #[test]
1639    fn test_vm_string() {
1640        assert_eq!(eval("\"hello\"").unwrap(), Value::string("hello"));
1641    }
1642
1643    #[test]
1644    fn test_vm_if_true() {
1645        assert_eq!(eval("(if #t 42 99)").unwrap(), Value::int(42));
1646    }
1647
1648    #[test]
1649    fn test_vm_if_false() {
1650        assert_eq!(eval("(if #f 42 99)").unwrap(), Value::int(99));
1651    }
1652
1653    #[test]
1654    fn test_vm_begin() {
1655        assert_eq!(eval("(begin 1 2 3)").unwrap(), Value::int(3));
1656    }
1657
1658    #[test]
1659    fn test_vm_define_and_load() {
1660        let globals = make_test_env();
1661        let ctx = EvalContext::new();
1662        eval_str("(define x 42)", &globals, &ctx).unwrap();
1663        let result = eval_str("x", &globals, &ctx).unwrap();
1664        assert_eq!(result, Value::int(42));
1665    }
1666
1667    #[test]
1668    fn test_vm_let() {
1669        assert_eq!(eval("(let ((x 10)) x)").unwrap(), Value::int(10));
1670    }
1671
1672    #[test]
1673    fn test_vm_let_multiple() {
1674        assert_eq!(eval("(let ((x 10) (y 20)) y)").unwrap(), Value::int(20));
1675    }
1676
1677    #[test]
1678    fn test_vm_nested_if() {
1679        assert_eq!(eval("(if (if #t #f #t) 1 2)").unwrap(), Value::int(2));
1680    }
1681
1682    #[test]
1683    fn test_vm_lambda_call() {
1684        assert_eq!(eval("((lambda (x) x) 42)").unwrap(), Value::int(42));
1685    }
1686
1687    #[test]
1688    fn test_vm_lambda_two_args() {
1689        assert_eq!(eval("((lambda (x y) y) 1 2)").unwrap(), Value::int(2));
1690    }
1691
1692    #[test]
1693    fn test_vm_closure_capture() {
1694        assert_eq!(
1695            eval("(let ((x 10)) ((lambda () x)))").unwrap(),
1696            Value::int(10)
1697        );
1698    }
1699
1700    #[test]
1701    fn test_vm_list_literal() {
1702        let result = eval("(list 1 2 3)").unwrap();
1703        let items = result.as_list().expect("Expected list");
1704        assert_eq!(items.len(), 3);
1705        assert_eq!(items[0], Value::int(1));
1706    }
1707
1708    #[test]
1709    fn test_vm_make_vector() {
1710        let result = eval("[1 2 3]").unwrap();
1711        let items = result.as_vector().expect("Expected vector");
1712        assert_eq!(items.len(), 3);
1713    }
1714
1715    #[test]
1716    fn test_vm_and_short_circuit() {
1717        assert_eq!(eval("(and #f 42)").unwrap(), Value::bool(false));
1718        assert_eq!(eval("(and #t 42)").unwrap(), Value::int(42));
1719    }
1720
1721    #[test]
1722    fn test_vm_or_short_circuit() {
1723        assert_eq!(eval("(or 42 99)").unwrap(), Value::int(42));
1724        assert_eq!(eval("(or #f 99)").unwrap(), Value::int(99));
1725    }
1726
1727    #[test]
1728    fn test_vm_throw_catch() {
1729        // Caught value is now a map with :type, :message, :value keys
1730        let result = eval("(try (throw \"boom\") (catch e (:value e)))").unwrap();
1731        assert_eq!(result, Value::string("boom"));
1732    }
1733
1734    #[test]
1735    fn test_vm_throw_catch_type() {
1736        let result = eval("(try (throw \"boom\") (catch e (:type e)))").unwrap();
1737        assert_eq!(result, Value::keyword("user"));
1738    }
1739
1740    #[test]
1741    fn test_vm_try_no_throw() {
1742        assert_eq!(eval("(try 42 (catch e 99))").unwrap(), Value::int(42));
1743    }
1744
1745    #[test]
1746    fn test_vm_try_catch_native_error() {
1747        // Division by zero from NativeFn should be caught
1748        let result = eval("(try (/ 1 0) (catch e \"caught\"))").unwrap();
1749        assert_eq!(result, Value::string("caught"));
1750    }
1751
1752    #[test]
1753    fn test_vm_try_catch_native_error_message() {
1754        let result = eval("(try (/ 1 0) (catch e (:message e)))").unwrap();
1755        let s = result.as_str().expect("Expected string");
1756        assert!(s.contains("division by zero"), "got: {s}");
1757    }
1758
1759    #[test]
1760    fn test_vm_try_catch_type_error() {
1761        let result = eval("(try (+ 1 \"a\") (catch e (:type e)))").unwrap();
1762        assert_eq!(result, Value::keyword("type-error"));
1763    }
1764
1765    #[test]
1766    fn test_vm_try_catch_from_closure_call() {
1767        // Regression: throw inside a called VM closure must be caught by try/catch
1768        let globals = make_test_env();
1769        let ctx = EvalContext::new();
1770        eval_str("(define (thrower) (throw \"boom\"))", &globals, &ctx).unwrap();
1771        let result = eval_str("(try (thrower) (catch e \"caught\"))", &globals, &ctx).unwrap();
1772        assert_eq!(result, Value::string("caught"));
1773    }
1774
1775    #[test]
1776    fn test_vm_try_catch_from_lambda_call() {
1777        // Throw from an immediately-called lambda
1778        let result = eval("(try ((fn () (throw 42))) (catch e (:value e)))").unwrap();
1779        assert_eq!(result, Value::int(42));
1780    }
1781
1782    #[test]
1783    fn test_vm_try_catch_nested_call() {
1784        // Throw two calls deep
1785        let globals = make_test_env();
1786        let ctx = EvalContext::new();
1787        eval_str("(define (inner) (throw \"deep\"))", &globals, &ctx).unwrap();
1788        eval_str("(define (outer) (inner))", &globals, &ctx).unwrap();
1789        let result = eval_str("(try (outer) (catch e \"caught\"))", &globals, &ctx).unwrap();
1790        assert_eq!(result, Value::string("caught"));
1791    }
1792
1793    #[test]
1794    fn test_vm_try_catch_in_call_arg() {
1795        // Regression: try/catch as argument to another function must preserve stack
1796        let globals = make_test_env();
1797        let ctx = EvalContext::new();
1798        eval_str("(define (thrower) (throw \"boom\"))", &globals, &ctx).unwrap();
1799        // try result used as arg to +
1800        let result = eval_str("(+ 1 (try (thrower) (catch e 2)))", &globals, &ctx).unwrap();
1801        assert_eq!(result, Value::int(3));
1802    }
1803
1804    #[test]
1805    fn test_vm_try_catch_in_list_constructor() {
1806        // try/catch as one of several args — stack must be preserved for all
1807        let globals = make_test_env();
1808        let ctx = EvalContext::new();
1809        eval_str("(define (thrower) (throw \"boom\"))", &globals, &ctx).unwrap();
1810        let result = eval_str("(list 1 2 (try (thrower) (catch e 3)) 4)", &globals, &ctx).unwrap();
1811        let items = result.as_list().expect("list");
1812        assert_eq!(items.len(), 4);
1813        assert_eq!(items[2], Value::int(3));
1814    }
1815
1816    #[test]
1817    fn test_vm_try_catch_call_not_last() {
1818        // Call is not the last instruction in the try body
1819        let globals = make_test_env();
1820        let ctx = EvalContext::new();
1821        eval_str("(define (thrower) (throw \"boom\"))", &globals, &ctx).unwrap();
1822        let result = eval_str(
1823            "(try (begin (thrower) 123) (catch e \"caught\"))",
1824            &globals,
1825            &ctx,
1826        )
1827        .unwrap();
1828        assert_eq!(result, Value::string("caught"));
1829    }
1830
1831    #[test]
1832    fn test_vm_quote() {
1833        let result = eval("'(a b c)").unwrap();
1834        let items = result.as_list().expect("Expected list");
1835        assert_eq!(items.len(), 3);
1836    }
1837
1838    #[test]
1839    fn test_vm_set() {
1840        let globals = make_test_env();
1841        let ctx = EvalContext::new();
1842        eval_str("(define x 1)", &globals, &ctx).unwrap();
1843        eval_str("(set! x 42)", &globals, &ctx).unwrap();
1844        let result = eval_str("x", &globals, &ctx).unwrap();
1845        assert_eq!(result, Value::int(42));
1846    }
1847
1848    #[test]
1849    fn test_vm_recursive_define() {
1850        let globals = make_test_env();
1851        let ctx = EvalContext::new();
1852        eval_str(
1853            "(define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))",
1854            &globals,
1855            &ctx,
1856        )
1857        .unwrap();
1858        let result = eval_str("(fact 5)", &globals, &ctx).unwrap();
1859        assert_eq!(result, Value::int(120));
1860    }
1861
1862    #[test]
1863    fn test_vm_do_loop() {
1864        let result = eval("(do ((i 0 (+ i 1))) ((= i 5) i))").unwrap();
1865        assert_eq!(result, Value::int(5));
1866    }
1867
1868    #[test]
1869    fn test_vm_named_let() {
1870        let result =
1871            eval("(let loop ((n 5) (acc 1)) (if (= n 0) acc (loop (- n 1) (* acc n))))").unwrap();
1872        assert_eq!(result, Value::int(120));
1873    }
1874
1875    #[test]
1876    fn test_vm_letrec() {
1877        let globals = make_test_env();
1878        let ctx = EvalContext::new();
1879        let result = eval_str(
1880            "(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))) (odd? (lambda (n) (if (= n 0) #f (even? (- n 1)))))) (even? 4))",
1881            &globals,
1882            &ctx,
1883        ).unwrap();
1884        assert_eq!(result, Value::bool(true));
1885    }
1886
1887    #[test]
1888    fn test_vm_rest_params() {
1889        let result = eval("((lambda (x . rest) rest) 1 2 3)").unwrap();
1890        let items = result.as_list().expect("Expected list");
1891        assert_eq!(items.len(), 2);
1892        assert_eq!(items[0], Value::int(2));
1893        assert_eq!(items[1], Value::int(3));
1894    }
1895
1896    // --- Task 8: Mutable upvalue tests ---
1897
1898    #[test]
1899    fn test_vm_counter_closure() {
1900        // make-counter pattern: closure that mutates a captured variable
1901        let result =
1902            eval("(let ((n 0)) (let ((inc (lambda () (set! n (+ n 1)) n))) (inc) (inc) (inc)))")
1903                .unwrap();
1904        assert_eq!(result, Value::int(3));
1905    }
1906
1907    #[test]
1908    fn test_vm_shared_mutable_upvalue() {
1909        // Two closures sharing the same mutable upvalue
1910        let result = eval(
1911            "(let ((n 0)) (let ((inc (lambda () (set! n (+ n 1)))) (get (lambda () n))) (inc) (inc) (get)))",
1912        )
1913        .unwrap();
1914        assert_eq!(result, Value::int(2));
1915    }
1916
1917    #[test]
1918    fn test_vm_set_local_in_let() {
1919        // set! on a local variable (not captured)
1920        let result = eval("(let ((x 1)) (set! x 42) x)").unwrap();
1921        assert_eq!(result, Value::int(42));
1922    }
1923
1924    #[test]
1925    fn test_vm_closure_captures_after_mutation() {
1926        // Closure captures value after mutation
1927        let result = eval("(let ((x 1)) (set! x 10) ((lambda () x)))").unwrap();
1928        assert_eq!(result, Value::int(10));
1929    }
1930
1931    #[test]
1932    fn test_vm_closure_returns_closure() {
1933        // A closure that returns another closure
1934        let result = eval("(let ((f (lambda () (lambda (x) x)))) ((f) 42))").unwrap();
1935        assert_eq!(result, Value::int(42));
1936    }
1937
1938    #[test]
1939    fn test_vm_make_adder() {
1940        // Classic make-adder pattern: closure captures upvalue
1941        let globals = make_test_env();
1942        let ctx = EvalContext::new();
1943        eval_str(
1944            "(define (make-adder n) (lambda (x) (+ n x)))",
1945            &globals,
1946            &ctx,
1947        )
1948        .unwrap();
1949        eval_str("(define add5 (make-adder 5))", &globals, &ctx).unwrap();
1950        let result = eval_str("(add5 3)", &globals, &ctx).unwrap();
1951        assert_eq!(result, Value::int(8));
1952    }
1953
1954    #[test]
1955    fn test_vm_compose() {
1956        // compose: closure returns closure that captures two upvalues
1957        let globals = make_test_env();
1958        let ctx = EvalContext::new();
1959        eval_str(
1960            "(define (compose f g) (lambda (x) (f (g x))))",
1961            &globals,
1962            &ctx,
1963        )
1964        .unwrap();
1965        eval_str("(define inc (lambda (x) (+ x 1)))", &globals, &ctx).unwrap();
1966        eval_str("(define dbl (lambda (x) (* x 2)))", &globals, &ctx).unwrap();
1967        let result = eval_str("((compose dbl inc) 5)", &globals, &ctx).unwrap();
1968        assert_eq!(result, Value::int(12));
1969    }
1970
1971    #[test]
1972    fn test_vm_nested_make_closure() {
1973        // Three levels deep
1974        let result = eval("((((lambda () (lambda () (lambda () 42))))))").unwrap();
1975        assert_eq!(result, Value::int(42));
1976    }
1977
1978    #[test]
1979    fn test_vm_named_fn_rest_params() {
1980        let globals = make_test_env();
1981        let ctx = EvalContext::new();
1982        eval_str("(define (f . args) args)", &globals, &ctx).unwrap();
1983        let result = eval_str("(f 1 2 3)", &globals, &ctx).unwrap();
1984        let items = result.as_list().expect("Expected list");
1985        assert_eq!(items.len(), 3);
1986        assert_eq!(items[0], Value::int(1));
1987    }
1988
1989    #[test]
1990    fn test_vm_named_let_still_works_with_fix() {
1991        let globals = make_test_env();
1992        let ctx = EvalContext::new();
1993        let result = eval_str(
1994            "(let loop ((n 5) (acc 1)) (if (= n 0) acc (loop (- n 1) (* acc n))))",
1995            &globals,
1996            &ctx,
1997        )
1998        .unwrap();
1999        assert_eq!(result, Value::int(120));
2000    }
2001
2002    #[test]
2003    fn test_vm_curry() {
2004        // Curry pattern
2005        let globals = make_test_env();
2006        let ctx = EvalContext::new();
2007        eval_str(
2008            "(define (curry f) (lambda (x) (lambda (y) (f x y))))",
2009            &globals,
2010            &ctx,
2011        )
2012        .unwrap();
2013        let result = eval_str("(((curry +) 3) 4)", &globals, &ctx).unwrap();
2014        assert_eq!(result, Value::int(7));
2015    }
2016
2017    // --- Regression tests: division and equality semantics ---
2018
2019    #[test]
2020    fn test_vm_div_int_returns_float_when_non_whole() {
2021        let globals = make_test_env();
2022        let ctx = EvalContext::new();
2023        let result = eval_str("(/ 3 2)", &globals, &ctx).unwrap();
2024        assert_eq!(result, Value::float(1.5));
2025    }
2026
2027    #[test]
2028    fn test_vm_div_int_returns_int_when_whole() {
2029        let globals = make_test_env();
2030        let ctx = EvalContext::new();
2031        let result = eval_str("(/ 4 2)", &globals, &ctx).unwrap();
2032        assert_eq!(result, Value::int(2));
2033    }
2034
2035    #[test]
2036    fn test_vm_div_int_negative_non_whole() {
2037        let globals = make_test_env();
2038        let ctx = EvalContext::new();
2039        let result = eval_str("(/ 7 3)", &globals, &ctx).unwrap();
2040        assert!(
2041            result.as_float().is_some(),
2042            "expected float, got {:?}",
2043            result
2044        );
2045    }
2046
2047    #[test]
2048    fn test_vm_div_by_zero() {
2049        let globals = make_test_env();
2050        let ctx = EvalContext::new();
2051        assert!(eval_str("(/ 1 0)", &globals, &ctx).is_err());
2052    }
2053
2054    #[test]
2055    fn test_vm_eq_int_float_coercion() {
2056        let globals = make_test_env();
2057        let ctx = EvalContext::new();
2058        assert_eq!(
2059            eval_str("(= 1 1.0)", &globals, &ctx).unwrap(),
2060            Value::bool(true)
2061        );
2062    }
2063
2064    #[test]
2065    fn test_vm_eq_float_int_coercion() {
2066        let globals = make_test_env();
2067        let ctx = EvalContext::new();
2068        assert_eq!(
2069            eval_str("(= 1.0 1)", &globals, &ctx).unwrap(),
2070            Value::bool(true)
2071        );
2072    }
2073
2074    #[test]
2075    fn test_vm_eq_int_float_not_equal() {
2076        let globals = make_test_env();
2077        let ctx = EvalContext::new();
2078        assert_eq!(
2079            eval_str("(= 1 2.0)", &globals, &ctx).unwrap(),
2080            Value::bool(false)
2081        );
2082    }
2083
2084    #[test]
2085    fn test_vm_eq_same_type_int() {
2086        let globals = make_test_env();
2087        let ctx = EvalContext::new();
2088        assert_eq!(
2089            eval_str("(= 1 1)", &globals, &ctx).unwrap(),
2090            Value::bool(true)
2091        );
2092        assert_eq!(
2093            eval_str("(= 1 2)", &globals, &ctx).unwrap(),
2094            Value::bool(false)
2095        );
2096    }
2097
2098    #[test]
2099    fn test_vm_eq_opcode_direct() {
2100        use crate::emit::Emitter;
2101        use crate::opcodes::Op;
2102        let globals = Rc::new(Env::new());
2103        let ctx = EvalContext::new();
2104        let mut e = Emitter::new();
2105        e.emit_const(Value::int(1));
2106        e.emit_const(Value::float(1.0));
2107        e.emit_op(Op::Eq);
2108        e.emit_op(Op::Return);
2109        let func = Rc::new(crate::chunk::Function {
2110            name: None,
2111            chunk: e.into_chunk(),
2112            upvalue_descs: vec![],
2113            arity: 0,
2114            has_rest: false,
2115            local_names: vec![],
2116        });
2117        let closure = Rc::new(Closure {
2118            func,
2119            upvalues: vec![],
2120        });
2121        let mut vm = VM::new(globals, vec![]);
2122        let result = vm.execute(closure, &ctx).unwrap();
2123        assert_eq!(
2124            result,
2125            Value::bool(true),
2126            "Op::Eq should coerce int 1 == float 1.0"
2127        );
2128    }
2129
2130    #[test]
2131    fn test_vm_div_opcode_direct() {
2132        use crate::emit::Emitter;
2133        use crate::opcodes::Op;
2134        let globals = Rc::new(Env::new());
2135        let ctx = EvalContext::new();
2136        let mut e = Emitter::new();
2137        e.emit_const(Value::int(3));
2138        e.emit_const(Value::int(2));
2139        e.emit_op(Op::Div);
2140        e.emit_op(Op::Return);
2141        let func = Rc::new(crate::chunk::Function {
2142            name: None,
2143            chunk: e.into_chunk(),
2144            upvalue_descs: vec![],
2145            arity: 0,
2146            has_rest: false,
2147            local_names: vec![],
2148        });
2149        let closure = Rc::new(Closure {
2150            func,
2151            upvalues: vec![],
2152        });
2153        let mut vm = VM::new(globals, vec![]);
2154        let result = vm.execute(closure, &ctx).unwrap();
2155        assert_eq!(
2156            result,
2157            Value::float(1.5),
2158            "Op::Div 3/2 should return 1.5, not 1"
2159        );
2160    }
2161}