Skip to main content

gc/
vm.rs

1use std::collections::{HashMap, HashSet};
2
3use byteorder::{BigEndian, ByteOrder};
4use compiler::compiler::{Bytecode, DebugInfo};
5use compiler::op_code::Opcode;
6use object::builtins::{BuiltIns, BuiltinId};
7use object::Object;
8use parser::lexer::token::Span;
9use serde::Serialize;
10
11use crate::frame::Frame;
12use crate::report::{
13    empty_value_kind_counts, select_global_roots, summarize_gc_object, GcCollectionReport,
14    GlobalRoot,
15};
16use crate::value::{
17    alloc_value, call_builtin, export_object, get_value, get_value_mut, import_object,
18    try_export_object, value_to_string, GcBoundMethod, GcClass, GcClosure, GcInstance, HashKey,
19    Value,
20};
21use crate::{GcHeap, GcId, GcRef};
22
23const STACK_SIZE: usize = 2048;
24pub const GLOBAL_SIZE: usize = 65536;
25const MAX_FRAMES: usize = 1024;
26pub const DEFAULT_INSTRUCTION_BUDGET: usize = 100_000;
27
28#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub struct GcRuntimeError {
31    pub message: String,
32    pub span: Option<Span>,
33}
34
35enum CalleeKind {
36    Closure(GcClosure),
37    Builtin(BuiltinId),
38    BoundMethod(GcBoundMethod),
39    Class(String),
40    Other(String),
41}
42
43pub struct GcVM {
44    heap: GcHeap,
45    constants: Vec<GcRef>,
46    stack: Vec<GcRef>,
47    sp: usize,
48    globals: Vec<GcRef>,
49    global_names: Vec<(String, usize)>,
50    frames: Vec<Frame>,
51    frame_index: usize,
52    null: GcRef,
53    last_popped: GcRef,
54    main_debug_info: DebugInfo,
55    function_debug_info: HashMap<GcRef, DebugInfo>,
56}
57
58impl GcVM {
59    pub fn new(bytecode: Bytecode) -> Self {
60        let Bytecode {
61            instructions,
62            constants: object_constants,
63            debug_info: main_debug_info,
64            function_debug_info: object_function_debug_info,
65        } = bytecode;
66        let mut heap = GcHeap::new();
67        let null = alloc_value(&mut heap, Value::Null);
68        let constants = object_constants
69            .iter()
70            .map(|constant| import_object(&mut heap, constant))
71            .collect::<Vec<_>>();
72        let function_debug_info = object_function_debug_info
73            .into_iter()
74            .filter_map(|(index, debug_info)| {
75                constants
76                    .get(index)
77                    .copied()
78                    .map(|reference| (reference, debug_info))
79            })
80            .collect();
81
82        let main_fn = alloc_value(
83            &mut heap,
84            Value::CompiledFunction(object::CompiledFunction {
85                name: String::new(),
86                instructions: instructions.data,
87                num_locals: 0,
88                num_parameters: 0,
89            }),
90        );
91        let main_instructions = compiled_instructions(&heap, main_fn);
92        // Frames keep borrowed GcRefs. The initial main_fn allocation is the VM
93        // root for these handles; placeholder frames do not take extra refs.
94        let main_frame = Frame::new(
95            GcClosure {
96                func: main_fn,
97                free: vec![],
98            },
99            main_instructions,
100            0,
101        );
102
103        let empty_frame = Frame::new(
104            GcClosure {
105                func: main_fn,
106                free: vec![],
107            },
108            vec![],
109            0,
110        );
111
112        let mut frames = vec![empty_frame; MAX_FRAMES];
113        frames[0] = main_frame;
114
115        let stack = (0..STACK_SIZE).map(|_| heap.dup(null)).collect();
116        let globals = (0..GLOBAL_SIZE).map(|_| heap.dup(null)).collect();
117        let last_popped = heap.dup(null);
118
119        GcVM {
120            heap,
121            constants,
122            stack,
123            sp: 0,
124            globals,
125            global_names: Vec::new(),
126            frames,
127            frame_index: 1,
128            null,
129            last_popped,
130            main_debug_info,
131            function_debug_info,
132        }
133    }
134
135    /// Replace the current main program while preserving the heap and globals.
136    ///
137    /// Used by the REPL so later lines can read bindings created earlier. Old
138    /// constant-pool owning refs are released; objects still reachable from
139    /// globals stay alive.
140    pub fn load_bytecode(&mut self, bytecode: Bytecode) {
141        let Bytecode {
142            instructions,
143            constants: object_constants,
144            debug_info: main_debug_info,
145            function_debug_info: object_function_debug_info,
146        } = bytecode;
147
148        self.clear_stack_range(0, self.sp);
149        self.sp = 0;
150
151        // Reset the last result so statements that never pop (e.g. a bare
152        // `let`) report null instead of the previous program's result.
153        self.heap.free(self.last_popped);
154        self.last_popped = self.heap.dup(self.null);
155
156        for reference in self.constants.drain(..) {
157            self.heap.free(reference);
158        }
159        self.function_debug_info.clear();
160
161        let old_main = self.frames[0].cl.func;
162        self.heap.free(old_main);
163
164        self.constants = object_constants
165            .iter()
166            .map(|constant| import_object(&mut self.heap, constant))
167            .collect::<Vec<_>>();
168        self.function_debug_info = object_function_debug_info
169            .into_iter()
170            .filter_map(|(index, debug_info)| {
171                self.constants
172                    .get(index)
173                    .copied()
174                    .map(|reference| (reference, debug_info))
175            })
176            .collect();
177        self.main_debug_info = main_debug_info;
178
179        let main_fn = alloc_value(
180            &mut self.heap,
181            Value::CompiledFunction(object::CompiledFunction {
182                name: String::new(),
183                instructions: instructions.data,
184                num_locals: 0,
185                num_parameters: 0,
186            }),
187        );
188        let main_instructions = compiled_instructions(&self.heap, main_fn);
189        let main_frame = Frame::new(
190            GcClosure {
191                func: main_fn,
192                free: vec![],
193            },
194            main_instructions,
195            0,
196        );
197        let empty_frame = Frame::new(
198            GcClosure {
199                func: main_fn,
200                free: vec![],
201            },
202            vec![],
203            0,
204        );
205        self.frames = vec![empty_frame; MAX_FRAMES];
206        self.frames[0] = main_frame;
207        self.frame_index = 1;
208    }
209
210    pub fn heap(&self) -> &GcHeap {
211        &self.heap
212    }
213
214    pub fn heap_mut(&mut self) -> &mut GcHeap {
215        &mut self.heap
216    }
217
218    /// Record which global slot each source-level global name refers to, so
219    /// reports can state the named root set (see `GlobalRoot`).
220    pub fn set_global_names(&mut self, names: Vec<(String, usize)>) {
221        self.global_names = names;
222    }
223
224    pub fn collect_garbage(&mut self) -> GcCollectionReport {
225        // Read the named global slots before collecting. Named slots are VM
226        // roots, so every object listed here survives the cycle collector.
227        let global_roots = self
228            .global_names
229            .iter()
230            .filter(|(_, index)| *index < self.globals.len())
231            .map(|(name, index)| GlobalRoot {
232                name: name.clone(),
233                object_id: self.globals[*index].0,
234            })
235            .collect();
236        let before_kinds = self.heap.value_kinds_by_id();
237        let before = self.heap.snapshot();
238        let diagnostics = self.heap.run_gc_with_stats_bundle();
239        let after = self.heap.snapshot();
240        let mut collected_by_value_kind = empty_value_kind_counts();
241        for (id, kind) in before_kinds {
242            if !self.heap.runtime().object_exists(id) {
243                *collected_by_value_kind.entry(kind).or_default() += 1;
244            }
245        }
246        let mut objects = diagnostics.objects;
247        let cataloged: HashSet<GcId> = objects.iter().map(|object| object.id).collect();
248        let (global_roots, omitted_global_roots) = select_global_roots(global_roots, &cataloged);
249        // The phase budgets pick the catalog without looking at the root set,
250        // so a kept root can name an object the catalog dropped. Summarize
251        // those now — named objects survived the collection, so they still
252        // exist. This keeps every reported root resolvable.
253        let mut uncataloged: Vec<GcId> = global_roots
254            .iter()
255            .map(|root| root.object_id)
256            .filter(|id| !cataloged.contains(id))
257            .collect();
258        uncataloged.sort_unstable();
259        uncataloged.dedup();
260        for id in uncataloged {
261            objects.push(summarize_gc_object(self.heap.runtime(), id));
262        }
263        objects.sort_unstable_by_key(|object| object.id);
264        GcCollectionReport {
265            before,
266            after,
267            objects,
268            global_roots,
269            omitted_global_roots,
270            phases: diagnostics.phases,
271            collected_by_value_kind,
272        }
273    }
274
275    fn runtime_error(&self, message: impl Into<String>) -> GcRuntimeError {
276        let frame = &self.frames[self.frame_index - 1];
277        let debug_info = if self.frame_index == 1 {
278            Some(&self.main_debug_info)
279        } else {
280            self.function_debug_info.get(&frame.cl.func)
281        };
282        let span = debug_info.and_then(|debug_info| {
283            (frame.ip >= 0)
284                .then(|| frame.ip as usize)
285                .and_then(|pc| debug_info.span_for_pc(pc).cloned())
286        });
287        GcRuntimeError {
288            message: message.into(),
289            span,
290        }
291    }
292
293    pub fn run(&mut self) {
294        self.run_with_budget(usize::MAX)
295            .expect("GC VM execution failed");
296    }
297
298    pub fn run_with_budget(&mut self, instruction_budget: usize) -> Result<(), GcRuntimeError> {
299        let mut executed = 0;
300        while self.current_frame().ip < self.current_frame().instructions.len() as i32 - 1 {
301            self.current_frame().ip += 1;
302            let ip = self.current_frame().ip as usize;
303            if executed >= instruction_budget {
304                return Err(self.runtime_error(format!(
305                    "instruction limit exceeded (budget: {})",
306                    instruction_budget
307                )));
308            }
309            executed += 1;
310            let ins = self.current_frame().instructions.clone();
311            let op = *ins.get(ip).unwrap();
312            let opcode = Opcode::from_repr(op)
313                .ok_or_else(|| self.runtime_error(format!("unknown opcode 0x{:02x}", op)))?;
314
315            match opcode {
316                Opcode::OpConst => {
317                    let const_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
318                    self.current_frame().ip += 2;
319                    let constant = self.constant(const_index)?;
320                    self.dup_and_push(constant)?;
321                }
322                Opcode::OpAdd | Opcode::OpSub | Opcode::OpMul | Opcode::OpDiv => {
323                    self.execute_binary_operation(opcode)?;
324                }
325                Opcode::OpPop => {
326                    self.pop_discard()?;
327                }
328                Opcode::OpTrue => {
329                    self.alloc_and_push(Value::Boolean(true))?;
330                }
331                Opcode::OpFalse => {
332                    self.alloc_and_push(Value::Boolean(false))?;
333                }
334                Opcode::OpEqual | Opcode::OpNotEqual | Opcode::OpGreaterThan => {
335                    self.execute_comparison(opcode)?;
336                }
337                Opcode::OpMinus => {
338                    self.execute_minus_operation()?;
339                }
340                Opcode::OpBang => {
341                    self.execute_bang_operation()?;
342                }
343                Opcode::OpJump => {
344                    let pos = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
345                    self.current_frame().ip = pos as i32 - 1;
346                }
347                Opcode::OpJumpNotTruthy => {
348                    let pos = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
349                    self.current_frame().ip += 2;
350                    let condition = self.pop_owned()?;
351                    if !is_truthy(&self.heap, condition) {
352                        self.current_frame().ip = pos as i32 - 1;
353                    }
354                    self.heap.free(condition);
355                }
356                Opcode::OpNull => {
357                    self.dup_and_push(self.null)?;
358                }
359                Opcode::OpGetGlobal => {
360                    let global_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
361                    self.current_frame().ip += 2;
362                    self.dup_and_push(self.globals[global_index])?;
363                }
364                Opcode::OpSetGlobal => {
365                    let global_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
366                    self.current_frame().ip += 2;
367                    let value = self.pop_owned()?;
368                    self.heap.free(self.globals[global_index]);
369                    self.globals[global_index] = value;
370                }
371                Opcode::OpArray => {
372                    let count = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
373                    self.current_frame().ip += 2;
374                    let start = self.stack_base_for(count)?;
375                    let elements = self.build_array(start, self.sp);
376                    let array = alloc_value(&mut self.heap, Value::Array(elements));
377                    self.clear_stack_range(start, self.sp);
378                    self.sp = start;
379                    self.push_raw(array)?;
380                }
381                Opcode::OpHash => {
382                    let count = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
383                    self.current_frame().ip += 2;
384                    let start = self.stack_base_for(count)?;
385                    let elements = self.build_hash(start, self.sp)?;
386                    let hash = alloc_value(&mut self.heap, Value::Hash(elements));
387                    self.clear_stack_range(start, self.sp);
388                    self.sp = start;
389                    self.push_raw(hash)?;
390                }
391                Opcode::OpIndex => {
392                    let (index, left) = self.pop_owned_pair()?;
393                    let result = self.execute_index_operation(left, index);
394                    self.heap.free(index);
395                    self.heap.free(left);
396                    result?;
397                }
398                Opcode::OpReturnValue => {
399                    let return_value = self.pop_owned()?;
400                    if self.frame_index == 1 {
401                        // A top-level return ends the program with this value
402                        // as its result, matching the interpreter backend.
403                        self.clear_stack_range(0, self.sp);
404                        self.sp = 0;
405                        self.heap.free(self.last_popped);
406                        self.last_popped = return_value;
407                        break;
408                    }
409                    let frame = self.pop_frame();
410                    let new_sp = frame.base_pointer - 1;
411                    self.clear_stack_range(new_sp, self.sp);
412                    self.sp = new_sp;
413                    self.push_raw(return_value)?;
414                }
415                Opcode::OpReturn => {
416                    if self.frame_index == 1 {
417                        self.clear_stack_range(0, self.sp);
418                        self.sp = 0;
419                        self.heap.free(self.last_popped);
420                        self.last_popped = self.heap.dup(self.null);
421                        break;
422                    }
423                    let frame = self.pop_frame();
424                    let new_sp = frame.base_pointer - 1;
425                    self.clear_stack_range(new_sp, self.sp);
426                    self.sp = new_sp;
427                    self.dup_and_push(self.null)?;
428                }
429                Opcode::OpCall => {
430                    let num_args = ins[ip + 1] as usize;
431                    self.current_frame().ip += 1;
432                    self.execute_call(num_args)?;
433                }
434                Opcode::OpSetLocal => {
435                    let local_index = ins[ip + 1] as usize;
436                    self.current_frame().ip += 1;
437                    let base = self.current_frame().base_pointer;
438                    let slot = self.local_slot(base, local_index)?;
439                    let value = self.pop_owned()?;
440                    self.heap.free(self.stack[slot]);
441                    self.stack[slot] = value;
442                }
443                Opcode::OpGetLocal => {
444                    let local_index = ins[ip + 1] as usize;
445                    self.current_frame().ip += 1;
446                    let base = self.current_frame().base_pointer;
447                    let slot = self.local_slot(base, local_index)?;
448                    self.dup_and_push(self.stack[slot])?;
449                }
450                Opcode::OpGetBuiltin => {
451                    let built_index = ins[ip + 1] as usize;
452                    self.current_frame().ip += 1;
453                    let definition = BuiltIns.get(built_index).ok_or_else(|| {
454                        self.runtime_error(format!("builtin index {} out of range", built_index))
455                    })?;
456                    self.alloc_and_push(Value::Builtin(definition.id))?;
457                }
458                Opcode::OpClosure => {
459                    let const_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
460                    let num_free = ins[ip + 3] as usize;
461                    self.current_frame().ip += 3;
462                    self.push_closure(const_index, num_free)?;
463                }
464                Opcode::OpGetFree => {
465                    let free_index = ins[ip + 1] as usize;
466                    self.current_frame().ip += 1;
467                    let free_var = self.current_frame().cl.free.get(free_index).copied();
468                    let free_var = free_var.ok_or_else(|| {
469                        self.runtime_error(format!(
470                            "free variable index {} out of range",
471                            free_index
472                        ))
473                    })?;
474                    self.dup_and_push(free_var)?;
475                }
476                Opcode::OpCurrentClosure => {
477                    let current = self.current_frame().cl.clone();
478                    self.alloc_and_push(Value::Closure(current))?;
479                }
480                Opcode::OpClass => {
481                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
482                    self.current_frame().ip += 2;
483                    let name = self.constant_string(name_index)?;
484                    self.alloc_and_push(Value::Class(GcClass {
485                        name,
486                        constructor: None,
487                        methods: HashMap::new(),
488                    }))?;
489                }
490                Opcode::OpMethod => {
491                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
492                    let kind = ins[ip + 3];
493                    self.current_frame().ip += 3;
494                    let name = self.constant_string(name_index)?;
495                    let method = self.pop_owned()?;
496                    if self.sp == 0 {
497                        self.heap.free(method);
498                        return Err(self.runtime_error("stack underflow"));
499                    }
500                    let class = self.stack[self.sp - 1];
501                    let result = self.install_method(class, name, method, kind == 1);
502                    self.heap.free(method);
503                    result?;
504                }
505                Opcode::OpGetProperty => {
506                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
507                    self.current_frame().ip += 2;
508                    let name = self.constant_string(name_index)?;
509                    let receiver = self.pop_owned()?;
510                    let value = self.get_property(receiver, &name);
511                    self.heap.free(receiver);
512                    self.push_raw(value?)?;
513                }
514                Opcode::OpSetProperty => {
515                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
516                    self.current_frame().ip += 2;
517                    let name = self.constant_string(name_index)?;
518                    let (value, receiver) = self.pop_owned_pair()?;
519                    let result = self.set_property(receiver, name, value);
520                    self.heap.free(value);
521                    self.heap.free(receiver);
522                    result?;
523                }
524                Opcode::OpNew => {
525                    let num_args = ins[ip + 1] as usize;
526                    self.current_frame().ip += 1;
527                    self.execute_new(num_args)?;
528                }
529            }
530        }
531        Ok(())
532    }
533
534    pub fn last_popped_stack_elm(&self) -> Option<GcRef> {
535        Some(self.last_popped)
536    }
537
538    pub fn export_last_result(&self) -> Option<Object> {
539        self.last_popped_stack_elm()
540            .map(|reference| export_object(&self.heap, reference))
541    }
542
543    pub fn try_export_last_result(&self) -> Result<Object, String> {
544        try_export_object(&self.heap, self.last_popped)
545    }
546
547    pub fn last_result_string(&self) -> String {
548        value_to_string(&self.heap, self.last_popped)
549    }
550
551    fn alloc_and_push(&mut self, value: Value) -> Result<(), GcRuntimeError> {
552        let reference = alloc_value(&mut self.heap, value);
553        self.push_raw(reference)
554    }
555
556    fn dup_and_push(&mut self, reference: GcRef) -> Result<(), GcRuntimeError> {
557        let duplicated = self.heap.dup(reference);
558        self.push_raw(duplicated)
559    }
560
561    fn push_raw(&mut self, value: GcRef) -> Result<(), GcRuntimeError> {
562        if self.sp >= STACK_SIZE {
563            let error = self.runtime_error("stack limit exceeded");
564            self.heap.free(value);
565            return Err(error);
566        }
567        let old = self.stack[self.sp];
568        self.stack[self.sp] = value;
569        self.heap.free(old);
570        self.sp += 1;
571        Ok(())
572    }
573
574    /// Move the top stack slot's owned reference to the caller.
575    ///
576    /// The caller must either free the returned ref or store it in another
577    /// owning location. The vacated stack slot is reset to a null ref.
578    fn pop_owned(&mut self) -> Result<GcRef, GcRuntimeError> {
579        if self.sp == 0 {
580            return Err(self.runtime_error("stack underflow"));
581        }
582        self.sp -= 1;
583        let value = self.stack[self.sp];
584        self.stack[self.sp] = self.heap.dup(self.null);
585        Ok(value)
586    }
587
588    fn pop_discard(&mut self) -> Result<(), GcRuntimeError> {
589        let value = self.pop_owned()?;
590        self.heap.free(self.last_popped);
591        self.last_popped = value;
592        Ok(())
593    }
594
595    /// Pop the top two owned references (top first). On underflow neither
596    /// reference leaks.
597    fn pop_owned_pair(&mut self) -> Result<(GcRef, GcRef), GcRuntimeError> {
598        let top = self.pop_owned()?;
599        match self.pop_owned() {
600            Ok(below) => Ok((top, below)),
601            Err(error) => {
602                self.heap.free(top);
603                Err(error)
604            }
605        }
606    }
607
608    /// Bounds-checked `sp - count` for opcodes that consume `count` stack
609    /// slots.
610    fn stack_base_for(&self, count: usize) -> Result<usize, GcRuntimeError> {
611        self.sp
612            .checked_sub(count)
613            .ok_or_else(|| self.runtime_error("stack underflow"))
614    }
615
616    fn local_slot(&self, base: usize, local_index: usize) -> Result<usize, GcRuntimeError> {
617        let slot = base + local_index;
618        if slot >= STACK_SIZE {
619            return Err(self.runtime_error(format!("local index {} out of range", local_index)));
620        }
621        Ok(slot)
622    }
623
624    fn clear_stack_range(&mut self, start: usize, end: usize) {
625        for index in start..end {
626            let old = self.stack[index];
627            self.stack[index] = self.heap.dup(self.null);
628            self.heap.free(old);
629        }
630    }
631
632    fn execute_binary_operation(&mut self, opcode: Opcode) -> Result<(), GcRuntimeError> {
633        let (right, left) = self.pop_owned_pair()?;
634        let left_value = get_value(&self.heap, left).clone();
635        let right_value = get_value(&self.heap, right).clone();
636        let result = match (&left_value, &right_value) {
637            (Value::Integer(l), Value::Integer(r)) => match opcode {
638                Opcode::OpAdd => Ok(Value::Integer(l + r)),
639                Opcode::OpSub => Ok(Value::Integer(l - r)),
640                Opcode::OpMul => Ok(Value::Integer(l * r)),
641                Opcode::OpDiv if *r != 0 => l
642                    .checked_div(*r)
643                    .map(Value::Integer)
644                    .ok_or_else(|| "integer overflow in division".to_string()),
645                Opcode::OpDiv => Err("division by zero".to_string()),
646                _ => unreachable!(),
647            },
648            (Value::String(l), Value::String(r)) if opcode == Opcode::OpAdd => {
649                Ok(Value::String(l.to_string() + r))
650            }
651            _ => Err(format!(
652                "unsupported binary operation for {} and {}",
653                value_to_string(&self.heap, left),
654                value_to_string(&self.heap, right)
655            )),
656        };
657        self.heap.free(left);
658        self.heap.free(right);
659        match result {
660            Ok(value) => self.alloc_and_push(value),
661            Err(message) => Err(self.runtime_error(message)),
662        }
663    }
664
665    fn execute_comparison(&mut self, opcode: Opcode) -> Result<(), GcRuntimeError> {
666        let (right, left) = self.pop_owned_pair()?;
667        let result = match (get_value(&self.heap, left), get_value(&self.heap, right)) {
668            (Value::Integer(l), Value::Integer(r)) => match opcode {
669                Opcode::OpEqual => Some(l == r),
670                Opcode::OpNotEqual => Some(l != r),
671                Opcode::OpGreaterThan => Some(l > r),
672                _ => unreachable!(),
673            },
674            (Value::Boolean(l), Value::Boolean(r)) => match opcode {
675                Opcode::OpEqual => Some(l == r),
676                Opcode::OpNotEqual => Some(l != r),
677                _ => None,
678            },
679            (Value::String(l), Value::String(r)) => match opcode {
680                Opcode::OpEqual => Some(l == r),
681                Opcode::OpNotEqual => Some(l != r),
682                _ => None,
683            },
684            (Value::Null, Value::Null) => match opcode {
685                Opcode::OpEqual => Some(true),
686                Opcode::OpNotEqual => Some(false),
687                _ => None,
688            },
689            (Value::Class(_), Value::Class(_))
690            | (Value::Instance(_), Value::Instance(_))
691            | (Value::BoundMethod(_), Value::BoundMethod(_)) => match opcode {
692                Opcode::OpEqual => Some(left == right),
693                Opcode::OpNotEqual => Some(left != right),
694                _ => None,
695            },
696            _ => None,
697        };
698        let message = if result.is_none() {
699            Some(format!(
700                "unsupported comparison for {} and {}",
701                value_to_string(&self.heap, left),
702                value_to_string(&self.heap, right)
703            ))
704        } else {
705            None
706        };
707        self.heap.free(left);
708        self.heap.free(right);
709        if let Some(result) = result {
710            self.alloc_and_push(Value::Boolean(result))
711        } else {
712            Err(self.runtime_error(message.unwrap()))
713        }
714    }
715
716    fn execute_minus_operation(&mut self) -> Result<(), GcRuntimeError> {
717        let operand = self.pop_owned()?;
718        let negated = match get_value(&self.heap, operand) {
719            Value::Integer(value) => Some(-value),
720            _ => None,
721        };
722        let message = negated.is_none().then(|| {
723            format!("unsupported type for negation: {}", value_to_string(&self.heap, operand))
724        });
725        self.heap.free(operand);
726        if let Some(negated) = negated {
727            self.alloc_and_push(Value::Integer(negated))
728        } else {
729            Err(self.runtime_error(message.unwrap()))
730        }
731    }
732
733    fn execute_bang_operation(&mut self) -> Result<(), GcRuntimeError> {
734        let operand = self.pop_owned()?;
735        let result = match get_value(&self.heap, operand) {
736            Value::Boolean(l) => !l,
737            _ => false,
738        };
739        self.heap.free(operand);
740        self.alloc_and_push(Value::Boolean(result))
741    }
742
743    fn build_array(&mut self, start: usize, end: usize) -> Vec<GcRef> {
744        let mut elements = Vec::with_capacity(end - start);
745        for i in start..end {
746            elements.push(self.stack[i]);
747        }
748        elements
749    }
750
751    fn build_hash(
752        &mut self,
753        start: usize,
754        end: usize,
755    ) -> Result<HashMap<HashKey, GcRef>, GcRuntimeError> {
756        let mut elements = HashMap::new();
757        for i in (start..end).step_by(2) {
758            let key_ref = self.stack[i];
759            let key = HashKey::from_value(get_value(&self.heap, key_ref)).ok_or_else(|| {
760                self.runtime_error(format!(
761                    "hash key must be hashable, got {}",
762                    value_to_string(&self.heap, key_ref)
763                ))
764            })?;
765            elements.insert(key, self.stack[i + 1]);
766        }
767        Ok(elements)
768    }
769
770    fn execute_index_operation(&mut self, left: GcRef, index: GcRef) -> Result<(), GcRuntimeError> {
771        let left_value = get_value(&self.heap, left).clone();
772        let index_value = get_value(&self.heap, index).clone();
773        match (&left_value, &index_value) {
774            (Value::Array(array), Value::Integer(i)) => self.execute_array_index(array, *i),
775            (Value::Hash(hash), _) => self.execute_hash_index(hash, &index_value),
776            _ => Err(self.runtime_error(format!(
777                "unsupported index operation for {} and {}",
778                value_to_string(&self.heap, left),
779                value_to_string(&self.heap, index)
780            ))),
781        }
782    }
783
784    fn execute_array_index(&mut self, array: &[GcRef], index: i64) -> Result<(), GcRuntimeError> {
785        if index < array.len() as i64 && index >= 0 {
786            self.dup_and_push(array[index as usize])
787        } else {
788            self.dup_and_push(self.null)
789        }
790    }
791
792    fn execute_hash_index(
793        &mut self,
794        hash: &HashMap<HashKey, GcRef>,
795        index: &Value,
796    ) -> Result<(), GcRuntimeError> {
797        let key = HashKey::from_value(index)
798            .ok_or_else(|| self.runtime_error("unsupported hash index key"))?;
799        match hash.get(&key) {
800            Some(value) => self.dup_and_push(*value),
801            None => self.dup_and_push(self.null),
802        }
803    }
804
805    fn current_frame(&mut self) -> &mut Frame {
806        &mut self.frames[self.frame_index - 1]
807    }
808
809    fn push_frame(&mut self, frame: Frame) -> Result<(), GcRuntimeError> {
810        if self.frame_index >= MAX_FRAMES {
811            return Err(self.runtime_error("frame limit exceeded"));
812        }
813        self.frames[self.frame_index] = frame;
814        self.frame_index += 1;
815        Ok(())
816    }
817
818    fn pop_frame(&mut self) -> Frame {
819        self.frame_index -= 1;
820        self.frames[self.frame_index].clone()
821    }
822
823    fn execute_call(&mut self, num_args: usize) -> Result<(), GcRuntimeError> {
824        let callee_slot = self.stack_base_for(num_args + 1)?;
825        let callee = self.stack[callee_slot];
826        match callee_kind(&self.heap, callee) {
827            CalleeKind::Closure(closure) => self.call_closure(closure, num_args),
828            CalleeKind::Builtin(builtin) => self.call_builtin(builtin, num_args),
829            CalleeKind::BoundMethod(bound) => self.call_bound_method(bound, num_args),
830            CalleeKind::Class(name) => {
831                Err(self.runtime_error(format!("class {} must be constructed with new", name)))
832            }
833            CalleeKind::Other(value) => Err(self.runtime_error(format!("cannot call {}", value))),
834        }
835    }
836
837    fn call_closure(&mut self, closure: GcClosure, num_args: usize) -> Result<(), GcRuntimeError> {
838        let compiled = match get_value(&self.heap, closure.func) {
839            Value::CompiledFunction(f) => f.clone(),
840            _ => return Err(self.runtime_error("closure without compiled function")),
841        };
842        if compiled.num_parameters != num_args {
843            return Err(self.runtime_error(format!(
844                "wrong number of arguments: want={}, got={}",
845                compiled.num_parameters, num_args
846            )));
847        }
848
849        let frame = Frame::new(closure, compiled.instructions, self.sp - num_args);
850        // checked_add: num_locals comes from bytecode, so it can be an
851        // arbitrary usize, not just a compiler-emitted small count.
852        let next_sp = frame
853            .base_pointer
854            .checked_add(compiled.num_locals)
855            .filter(|next_sp| *next_sp <= STACK_SIZE)
856            .ok_or_else(|| self.runtime_error("stack limit exceeded"))?;
857        self.sp = next_sp;
858        self.push_frame(frame)
859    }
860
861    fn call_builtin(&mut self, builtin: BuiltinId, num_args: usize) -> Result<(), GcRuntimeError> {
862        let base = self.sp - num_args - 1;
863        let args = self.stack[self.sp - num_args..self.sp].to_vec();
864        let result = call_builtin(&mut self.heap, builtin, &args, self.null);
865        self.clear_stack_range(base, self.sp);
866        self.sp = base;
867        self.push_raw(result)
868    }
869
870    fn push_closure(&mut self, const_index: usize, num_free: usize) -> Result<(), GcRuntimeError> {
871        let func = self.constant(const_index)?;
872        if !matches!(get_value(&self.heap, func), Value::CompiledFunction(_)) {
873            return Err(self.runtime_error(format!(
874                "cannot build closure over {}",
875                value_to_string(&self.heap, func)
876            )));
877        }
878        let start = self.stack_base_for(num_free)?;
879        let mut free = Vec::with_capacity(num_free);
880        for i in 0..num_free {
881            free.push(self.stack[start + i]);
882        }
883        let closure = alloc_value(
884            &mut self.heap,
885            Value::Closure(GcClosure {
886                func,
887                free,
888            }),
889        );
890        self.clear_stack_range(start, self.sp);
891        self.sp = start;
892        self.push_raw(closure)
893    }
894
895    fn constant(&self, index: usize) -> Result<GcRef, GcRuntimeError> {
896        self.constants
897            .get(index)
898            .copied()
899            .ok_or_else(|| self.runtime_error(format!("constant index {} out of range", index)))
900    }
901
902    fn constant_string(&self, index: usize) -> Result<String, GcRuntimeError> {
903        let constant = self.constant(index)?;
904        match get_value(&self.heap, constant) {
905            Value::String(value) => Ok(value.clone()),
906            value => Err(self.runtime_error(format!("expected string constant, got {}", value))),
907        }
908    }
909
910    fn install_method(
911        &mut self,
912        class: GcRef,
913        name: String,
914        method: GcRef,
915        constructor: bool,
916    ) -> Result<(), GcRuntimeError> {
917        if !matches!(get_value(&self.heap, class), Value::Class(_)) {
918            return Err(self.runtime_error(format!(
919                "cannot install method on {}",
920                value_to_string(&self.heap, class)
921            )));
922        }
923        let owned_method = self.heap.dup(method);
924        let old_method = match get_value_mut(&mut self.heap, class) {
925            Value::Class(class) => {
926                if constructor {
927                    class.constructor.replace(owned_method)
928                } else {
929                    class.methods.insert(name, owned_method)
930                }
931            }
932            _ => unreachable!(),
933        };
934        if let Some(old_method) = old_method {
935            self.heap.free(old_method);
936        }
937        Ok(())
938    }
939
940    fn get_property(&mut self, receiver: GcRef, name: &str) -> Result<GcRef, GcRuntimeError> {
941        let (class, field) = match get_value(&self.heap, receiver) {
942            Value::Instance(instance) => (instance.class, instance.fields.get(name).copied()),
943            _ => {
944                return Err(self.runtime_error(format!(
945                    "cannot read property '{}' of {}",
946                    name,
947                    value_to_string(&self.heap, receiver)
948                )))
949            }
950        };
951        if let Some(field) = field {
952            return Ok(self.heap.dup(field));
953        }
954
955        let (class_name, method) = match get_value(&self.heap, class) {
956            Value::Class(class) => (class.name.clone(), class.methods.get(name).copied()),
957            _ => return Err(self.runtime_error("instance has invalid class")),
958        };
959        match method {
960            Some(method) => Ok(alloc_value(
961                &mut self.heap,
962                Value::BoundMethod(GcBoundMethod {
963                    receiver,
964                    method,
965                    name: name.to_string(),
966                }),
967            )),
968            None => {
969                Err(self
970                    .runtime_error(format!("property '{}' does not exist on {}", name, class_name)))
971            }
972        }
973    }
974
975    fn set_property(
976        &mut self,
977        receiver: GcRef,
978        name: String,
979        value: GcRef,
980    ) -> Result<(), GcRuntimeError> {
981        if !matches!(get_value(&self.heap, receiver), Value::Instance(_)) {
982            return Err(self.runtime_error(format!(
983                "cannot set property '{}' of {}",
984                name,
985                value_to_string(&self.heap, receiver)
986            )));
987        }
988        let owned_value = self.heap.dup(value);
989        let old_value = match get_value_mut(&mut self.heap, receiver) {
990            Value::Instance(instance) => instance.fields.insert(name, owned_value),
991            _ => unreachable!(),
992        };
993        if let Some(old_value) = old_value {
994            self.heap.free(old_value);
995        }
996        Ok(())
997    }
998
999    fn execute_new(&mut self, num_args: usize) -> Result<(), GcRuntimeError> {
1000        let base = self.stack_base_for(num_args + 1)?;
1001        let class_reference = self.stack[base];
1002        let (class_name, constructor) = match get_value(&self.heap, class_reference) {
1003            Value::Class(class) => (class.name.clone(), class.constructor),
1004            _ => {
1005                return Err(self.runtime_error(format!(
1006                    "cannot construct {}",
1007                    value_to_string(&self.heap, class_reference)
1008                )))
1009            }
1010        };
1011
1012        let Some(constructor) = constructor else {
1013            if num_args != 0 {
1014                return Err(self.runtime_error(format!(
1015                    "wrong number of arguments for {}.constructor: want=0, got={}",
1016                    class_name, num_args
1017                )));
1018            }
1019            let instance = alloc_value(
1020                &mut self.heap,
1021                Value::Instance(GcInstance {
1022                    class: class_reference,
1023                    fields: HashMap::new(),
1024                }),
1025            );
1026            self.clear_stack_range(base, self.sp);
1027            self.sp = base;
1028            return self.push_raw(instance);
1029        };
1030
1031        let closure = match get_value(&self.heap, constructor) {
1032            Value::Closure(closure) => closure.clone(),
1033            _ => return Err(self.runtime_error("constructor is not a closure")),
1034        };
1035        let compiled = match get_value(&self.heap, closure.func) {
1036            Value::CompiledFunction(function) => function.clone(),
1037            _ => return Err(self.runtime_error("constructor closure has invalid function")),
1038        };
1039        let expected = compiled.num_parameters.saturating_sub(1);
1040        if expected != num_args {
1041            return Err(self.runtime_error(format!(
1042                "wrong number of arguments for {}.constructor: want={}, got={}",
1043                class_name, expected, num_args
1044            )));
1045        }
1046
1047        let instance = alloc_value(
1048            &mut self.heap,
1049            Value::Instance(GcInstance {
1050                class: class_reference,
1051                fields: HashMap::new(),
1052            }),
1053        );
1054        self.rewrite_receiver_call(constructor, instance, num_args)?;
1055        self.call_closure(closure, num_args + 1)
1056    }
1057
1058    fn call_bound_method(
1059        &mut self,
1060        bound: GcBoundMethod,
1061        num_args: usize,
1062    ) -> Result<(), GcRuntimeError> {
1063        let closure = match get_value(&self.heap, bound.method) {
1064            Value::Closure(closure) => closure.clone(),
1065            _ => return Err(self.runtime_error("bound method is not a closure")),
1066        };
1067        let compiled = match get_value(&self.heap, closure.func) {
1068            Value::CompiledFunction(function) => function.clone(),
1069            _ => return Err(self.runtime_error("method closure has invalid function")),
1070        };
1071        let expected = compiled.num_parameters.saturating_sub(1);
1072        if expected != num_args {
1073            let class_name = match get_value(&self.heap, bound.receiver) {
1074                Value::Instance(instance) => match get_value(&self.heap, instance.class) {
1075                    Value::Class(class) => class.name.clone(),
1076                    _ => "<invalid class>".to_string(),
1077                },
1078                _ => "<invalid receiver>".to_string(),
1079            };
1080            return Err(self.runtime_error(format!(
1081                "wrong number of arguments for {}.{}: want={}, got={}",
1082                class_name, bound.name, expected, num_args
1083            )));
1084        }
1085        let receiver = self.heap.dup(bound.receiver);
1086        self.rewrite_receiver_call(bound.method, receiver, num_args)?;
1087        self.call_closure(closure, num_args + 1)
1088    }
1089
1090    /// Takes ownership of `receiver` and frees it if the stack cannot hold
1091    /// the rewritten call layout.
1092    fn rewrite_receiver_call(
1093        &mut self,
1094        callable: GcRef,
1095        receiver: GcRef,
1096        num_args: usize,
1097    ) -> Result<(), GcRuntimeError> {
1098        let base = self.sp - num_args - 1;
1099        if base + num_args + 2 > STACK_SIZE {
1100            let error = self.runtime_error("stack limit exceeded");
1101            self.heap.free(receiver);
1102            return Err(error);
1103        }
1104        let callable = self.heap.dup(callable);
1105        let borrowed_arguments = self.stack[self.sp - num_args..self.sp].to_vec();
1106        let arguments = borrowed_arguments
1107            .into_iter()
1108            .map(|argument| self.heap.dup(argument))
1109            .collect::<Vec<_>>();
1110        self.clear_stack_range(base, self.sp);
1111        self.sp = base;
1112        self.push_raw(callable)?;
1113        self.push_raw(receiver)?;
1114        for argument in arguments {
1115            self.push_raw(argument)?;
1116        }
1117        Ok(())
1118    }
1119}
1120
1121fn is_truthy(heap: &GcHeap, condition: GcRef) -> bool {
1122    match get_value(heap, condition) {
1123        Value::Boolean(b) => *b,
1124        Value::Null => false,
1125        _ => true,
1126    }
1127}
1128
1129fn callee_kind(heap: &GcHeap, reference: GcRef) -> CalleeKind {
1130    match get_value(heap, reference) {
1131        Value::Closure(closure) => CalleeKind::Closure(closure.clone()),
1132        Value::Builtin(builtin) => CalleeKind::Builtin(*builtin),
1133        Value::BoundMethod(bound) => CalleeKind::BoundMethod(bound.clone()),
1134        Value::Class(class) => CalleeKind::Class(class.name.clone()),
1135        _ => CalleeKind::Other(value_to_string(heap, reference)),
1136    }
1137}
1138
1139fn compiled_instructions(heap: &GcHeap, func: GcRef) -> Vec<u8> {
1140    match get_value(heap, func) {
1141        Value::CompiledFunction(f) => f.instructions.clone(),
1142        _ => panic!("expected compiled function"),
1143    }
1144}