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_some(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
335                | Opcode::OpNotEqual
336                | Opcode::OpGreaterThan
337                | Opcode::OpLessThan => {
338                    self.execute_comparison(opcode)?;
339                }
340                Opcode::OpMinus => {
341                    self.execute_minus_operation()?;
342                }
343                Opcode::OpBang => {
344                    self.execute_bang_operation()?;
345                }
346                Opcode::OpJump => {
347                    let pos = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
348                    self.current_frame().ip = pos as i32 - 1;
349                }
350                Opcode::OpJumpNotTruthy => {
351                    let pos = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
352                    self.current_frame().ip += 2;
353                    let condition = self.pop_owned()?;
354                    if !is_truthy(&self.heap, condition) {
355                        self.current_frame().ip = pos as i32 - 1;
356                    }
357                    self.heap.free(condition);
358                }
359                Opcode::OpNull => {
360                    self.dup_and_push(self.null)?;
361                }
362                Opcode::OpGetGlobal => {
363                    let global_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
364                    self.current_frame().ip += 2;
365                    self.dup_and_push(self.globals[global_index])?;
366                }
367                Opcode::OpSetGlobal => {
368                    let global_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
369                    self.current_frame().ip += 2;
370                    let value = self.pop_owned()?;
371                    self.heap.free(self.globals[global_index]);
372                    self.globals[global_index] = value;
373                }
374                Opcode::OpArray => {
375                    let count = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
376                    self.current_frame().ip += 2;
377                    let start = self.stack_base_for(count)?;
378                    let elements = self.build_array(start, self.sp);
379                    let array = alloc_value(&mut self.heap, Value::Array(elements));
380                    self.clear_stack_range(start, self.sp);
381                    self.sp = start;
382                    self.push_raw(array)?;
383                }
384                Opcode::OpHash => {
385                    let count = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
386                    self.current_frame().ip += 2;
387                    let start = self.stack_base_for(count)?;
388                    let elements = self.build_hash(start, self.sp)?;
389                    let hash = alloc_value(&mut self.heap, Value::Hash(elements));
390                    self.clear_stack_range(start, self.sp);
391                    self.sp = start;
392                    self.push_raw(hash)?;
393                }
394                Opcode::OpIndex => {
395                    let (index, left) = self.pop_owned_pair()?;
396                    let result = self.execute_index_operation(left, index);
397                    self.heap.free(index);
398                    self.heap.free(left);
399                    result?;
400                }
401                Opcode::OpReturnValue => {
402                    let return_value = self.pop_owned()?;
403                    if self.frame_index == 1 {
404                        // A top-level return ends the program with this value
405                        // as its result, matching the interpreter backend.
406                        self.clear_stack_range(0, self.sp);
407                        self.sp = 0;
408                        self.heap.free(self.last_popped);
409                        self.last_popped = return_value;
410                        break;
411                    }
412                    let frame = self.pop_frame();
413                    let new_sp = frame.base_pointer - 1;
414                    self.clear_stack_range(new_sp, self.sp);
415                    self.sp = new_sp;
416                    self.push_raw(return_value)?;
417                }
418                Opcode::OpReturn => {
419                    if self.frame_index == 1 {
420                        self.clear_stack_range(0, self.sp);
421                        self.sp = 0;
422                        self.heap.free(self.last_popped);
423                        self.last_popped = self.heap.dup(self.null);
424                        break;
425                    }
426                    let frame = self.pop_frame();
427                    let new_sp = frame.base_pointer - 1;
428                    self.clear_stack_range(new_sp, self.sp);
429                    self.sp = new_sp;
430                    self.dup_and_push(self.null)?;
431                }
432                Opcode::OpCall => {
433                    let num_args = ins[ip + 1] as usize;
434                    self.current_frame().ip += 1;
435                    self.execute_call(num_args)?;
436                }
437                Opcode::OpSetLocal => {
438                    let local_index = ins[ip + 1] as usize;
439                    self.current_frame().ip += 1;
440                    let base = self.current_frame().base_pointer;
441                    let slot = self.local_slot(base, local_index)?;
442                    let value = self.pop_owned()?;
443                    self.heap.free(self.stack[slot]);
444                    self.stack[slot] = value;
445                }
446                Opcode::OpGetLocal => {
447                    let local_index = ins[ip + 1] as usize;
448                    self.current_frame().ip += 1;
449                    let base = self.current_frame().base_pointer;
450                    let slot = self.local_slot(base, local_index)?;
451                    self.dup_and_push(self.stack[slot])?;
452                }
453                Opcode::OpGetBuiltin => {
454                    let built_index = ins[ip + 1] as usize;
455                    self.current_frame().ip += 1;
456                    let definition = BuiltIns.get(built_index).ok_or_else(|| {
457                        self.runtime_error(format!("builtin index {} out of range", built_index))
458                    })?;
459                    self.alloc_and_push(Value::Builtin(definition.id))?;
460                }
461                Opcode::OpClosure => {
462                    let const_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
463                    let num_free = ins[ip + 3] as usize;
464                    self.current_frame().ip += 3;
465                    self.push_closure(const_index, num_free)?;
466                }
467                Opcode::OpGetFree => {
468                    let free_index = ins[ip + 1] as usize;
469                    self.current_frame().ip += 1;
470                    let free_var = self.current_frame().cl.free.get(free_index).copied();
471                    let free_var = free_var.ok_or_else(|| {
472                        self.runtime_error(format!(
473                            "free variable index {} out of range",
474                            free_index
475                        ))
476                    })?;
477                    self.dup_and_push(free_var)?;
478                }
479                Opcode::OpCurrentClosure => {
480                    let current = self.current_frame().cl.clone();
481                    self.alloc_and_push(Value::Closure(current))?;
482                }
483                Opcode::OpClass => {
484                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
485                    self.current_frame().ip += 2;
486                    let name = self.constant_string(name_index)?;
487                    self.alloc_and_push(Value::Class(GcClass {
488                        name,
489                        constructor: None,
490                        methods: HashMap::new(),
491                    }))?;
492                }
493                Opcode::OpMethod => {
494                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
495                    let kind = ins[ip + 3];
496                    self.current_frame().ip += 3;
497                    let name = self.constant_string(name_index)?;
498                    let method = self.pop_owned()?;
499                    if self.sp == 0 {
500                        self.heap.free(method);
501                        return Err(self.runtime_error("stack underflow"));
502                    }
503                    let class = self.stack[self.sp - 1];
504                    let result = self.install_method(class, name, method, kind == 1);
505                    self.heap.free(method);
506                    result?;
507                }
508                Opcode::OpGetProperty => {
509                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
510                    self.current_frame().ip += 2;
511                    let name = self.constant_string(name_index)?;
512                    let receiver = self.pop_owned()?;
513                    let value = self.get_property(receiver, &name);
514                    self.heap.free(receiver);
515                    self.push_raw(value?)?;
516                }
517                Opcode::OpSetProperty => {
518                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
519                    self.current_frame().ip += 2;
520                    let name = self.constant_string(name_index)?;
521                    let (value, receiver) = self.pop_owned_pair()?;
522                    let result = self.set_property(receiver, name, value);
523                    self.heap.free(value);
524                    self.heap.free(receiver);
525                    result?;
526                }
527                Opcode::OpNew => {
528                    let num_args = ins[ip + 1] as usize;
529                    self.current_frame().ip += 1;
530                    self.execute_new(num_args)?;
531                }
532            }
533        }
534        Ok(())
535    }
536
537    pub fn last_popped_stack_elm(&self) -> Option<GcRef> {
538        Some(self.last_popped)
539    }
540
541    pub fn export_last_result(&self) -> Option<Object> {
542        self.last_popped_stack_elm()
543            .map(|reference| export_object(&self.heap, reference))
544    }
545
546    pub fn try_export_last_result(&self) -> Result<Object, String> {
547        try_export_object(&self.heap, self.last_popped)
548    }
549
550    pub fn last_result_string(&self) -> String {
551        value_to_string(&self.heap, self.last_popped)
552    }
553
554    fn alloc_and_push(&mut self, value: Value) -> Result<(), GcRuntimeError> {
555        let reference = alloc_value(&mut self.heap, value);
556        self.push_raw(reference)
557    }
558
559    fn dup_and_push(&mut self, reference: GcRef) -> Result<(), GcRuntimeError> {
560        let duplicated = self.heap.dup(reference);
561        self.push_raw(duplicated)
562    }
563
564    fn push_raw(&mut self, value: GcRef) -> Result<(), GcRuntimeError> {
565        if self.sp >= STACK_SIZE {
566            let error = self.runtime_error("stack limit exceeded");
567            self.heap.free(value);
568            return Err(error);
569        }
570        let old = self.stack[self.sp];
571        self.stack[self.sp] = value;
572        self.heap.free(old);
573        self.sp += 1;
574        Ok(())
575    }
576
577    /// Move the top stack slot's owned reference to the caller.
578    ///
579    /// The caller must either free the returned ref or store it in another
580    /// owning location. The vacated stack slot is reset to a null ref.
581    fn pop_owned(&mut self) -> Result<GcRef, GcRuntimeError> {
582        if self.sp == 0 {
583            return Err(self.runtime_error("stack underflow"));
584        }
585        self.sp -= 1;
586        let value = self.stack[self.sp];
587        self.stack[self.sp] = self.heap.dup(self.null);
588        Ok(value)
589    }
590
591    fn pop_discard(&mut self) -> Result<(), GcRuntimeError> {
592        let value = self.pop_owned()?;
593        self.heap.free(self.last_popped);
594        self.last_popped = value;
595        Ok(())
596    }
597
598    /// Pop the top two owned references (top first). On underflow neither
599    /// reference leaks.
600    fn pop_owned_pair(&mut self) -> Result<(GcRef, GcRef), GcRuntimeError> {
601        let top = self.pop_owned()?;
602        match self.pop_owned() {
603            Ok(below) => Ok((top, below)),
604            Err(error) => {
605                self.heap.free(top);
606                Err(error)
607            }
608        }
609    }
610
611    /// Bounds-checked `sp - count` for opcodes that consume `count` stack
612    /// slots.
613    fn stack_base_for(&self, count: usize) -> Result<usize, GcRuntimeError> {
614        self.sp
615            .checked_sub(count)
616            .ok_or_else(|| self.runtime_error("stack underflow"))
617    }
618
619    fn local_slot(&self, base: usize, local_index: usize) -> Result<usize, GcRuntimeError> {
620        let slot = base + local_index;
621        if slot >= STACK_SIZE {
622            return Err(self.runtime_error(format!("local index {} out of range", local_index)));
623        }
624        Ok(slot)
625    }
626
627    fn clear_stack_range(&mut self, start: usize, end: usize) {
628        for index in start..end {
629            let old = self.stack[index];
630            self.stack[index] = self.heap.dup(self.null);
631            self.heap.free(old);
632        }
633    }
634
635    fn execute_binary_operation(&mut self, opcode: Opcode) -> Result<(), GcRuntimeError> {
636        let (right, left) = self.pop_owned_pair()?;
637        let left_value = get_value(&self.heap, left).clone();
638        let right_value = get_value(&self.heap, right).clone();
639        let result = match (&left_value, &right_value) {
640            (Value::Integer(l), Value::Integer(r)) => match opcode {
641                Opcode::OpAdd => Ok(Value::Integer(l + r)),
642                Opcode::OpSub => Ok(Value::Integer(l - r)),
643                Opcode::OpMul => Ok(Value::Integer(l * r)),
644                Opcode::OpDiv if *r != 0 => l
645                    .checked_div(*r)
646                    .map(Value::Integer)
647                    .ok_or_else(|| "integer overflow in division".to_string()),
648                Opcode::OpDiv => Err("division by zero".to_string()),
649                _ => unreachable!(),
650            },
651            (Value::String(l), Value::String(r)) if opcode == Opcode::OpAdd => {
652                Ok(Value::String(l.to_string() + r))
653            }
654            _ => Err(format!(
655                "unsupported binary operation for {} and {}",
656                value_to_string(&self.heap, left),
657                value_to_string(&self.heap, right)
658            )),
659        };
660        self.heap.free(left);
661        self.heap.free(right);
662        match result {
663            Ok(value) => self.alloc_and_push(value),
664            Err(message) => Err(self.runtime_error(message)),
665        }
666    }
667
668    fn execute_comparison(&mut self, opcode: Opcode) -> Result<(), GcRuntimeError> {
669        let (right, left) = self.pop_owned_pair()?;
670        let result = match (get_value(&self.heap, left), get_value(&self.heap, right)) {
671            (Value::Integer(l), Value::Integer(r)) => match opcode {
672                Opcode::OpEqual => Some(l == r),
673                Opcode::OpNotEqual => Some(l != r),
674                Opcode::OpGreaterThan => Some(l > r),
675                Opcode::OpLessThan => Some(l < r),
676                _ => unreachable!(),
677            },
678            (Value::Boolean(l), Value::Boolean(r)) => match opcode {
679                Opcode::OpEqual => Some(l == r),
680                Opcode::OpNotEqual => Some(l != r),
681                _ => None,
682            },
683            (Value::String(l), Value::String(r)) => match opcode {
684                Opcode::OpEqual => Some(l == r),
685                Opcode::OpNotEqual => Some(l != r),
686                _ => None,
687            },
688            (Value::Null, Value::Null) => match opcode {
689                Opcode::OpEqual => Some(true),
690                Opcode::OpNotEqual => Some(false),
691                _ => None,
692            },
693            (Value::Class(_), Value::Class(_))
694            | (Value::Instance(_), Value::Instance(_))
695            | (Value::BoundMethod(_), Value::BoundMethod(_)) => match opcode {
696                Opcode::OpEqual => Some(left == right),
697                Opcode::OpNotEqual => Some(left != right),
698                _ => None,
699            },
700            _ => None,
701        };
702        let message = if result.is_none() {
703            Some(format!(
704                "unsupported comparison for {} and {}",
705                value_to_string(&self.heap, left),
706                value_to_string(&self.heap, right)
707            ))
708        } else {
709            None
710        };
711        self.heap.free(left);
712        self.heap.free(right);
713        if let Some(result) = result {
714            self.alloc_and_push(Value::Boolean(result))
715        } else {
716            Err(self.runtime_error(message.unwrap()))
717        }
718    }
719
720    fn execute_minus_operation(&mut self) -> Result<(), GcRuntimeError> {
721        let operand = self.pop_owned()?;
722        let negated = match get_value(&self.heap, operand) {
723            Value::Integer(value) => Some(-value),
724            _ => None,
725        };
726        let message = negated.is_none().then(|| {
727            format!("unsupported type for negation: {}", value_to_string(&self.heap, operand))
728        });
729        self.heap.free(operand);
730        if let Some(negated) = negated {
731            self.alloc_and_push(Value::Integer(negated))
732        } else {
733            Err(self.runtime_error(message.unwrap()))
734        }
735    }
736
737    fn execute_bang_operation(&mut self) -> Result<(), GcRuntimeError> {
738        let operand = self.pop_owned()?;
739        let result = match get_value(&self.heap, operand) {
740            Value::Boolean(l) => !l,
741            _ => false,
742        };
743        self.heap.free(operand);
744        self.alloc_and_push(Value::Boolean(result))
745    }
746
747    fn build_array(&mut self, start: usize, end: usize) -> Vec<GcRef> {
748        let mut elements = Vec::with_capacity(end - start);
749        for i in start..end {
750            elements.push(self.stack[i]);
751        }
752        elements
753    }
754
755    fn build_hash(
756        &mut self,
757        start: usize,
758        end: usize,
759    ) -> Result<HashMap<HashKey, GcRef>, GcRuntimeError> {
760        let mut elements = HashMap::new();
761        for i in (start..end).step_by(2) {
762            let key_ref = self.stack[i];
763            let key = HashKey::from_value(get_value(&self.heap, key_ref)).ok_or_else(|| {
764                self.runtime_error(format!(
765                    "hash key must be hashable, got {}",
766                    value_to_string(&self.heap, key_ref)
767                ))
768            })?;
769            elements.insert(key, self.stack[i + 1]);
770        }
771        Ok(elements)
772    }
773
774    fn execute_index_operation(&mut self, left: GcRef, index: GcRef) -> Result<(), GcRuntimeError> {
775        let left_value = get_value(&self.heap, left).clone();
776        let index_value = get_value(&self.heap, index).clone();
777        match (&left_value, &index_value) {
778            (Value::Array(array), Value::Integer(i)) => self.execute_array_index(array, *i),
779            (Value::Hash(hash), _) => self.execute_hash_index(hash, &index_value),
780            _ => Err(self.runtime_error(format!(
781                "unsupported index operation for {} and {}",
782                value_to_string(&self.heap, left),
783                value_to_string(&self.heap, index)
784            ))),
785        }
786    }
787
788    fn execute_array_index(&mut self, array: &[GcRef], index: i64) -> Result<(), GcRuntimeError> {
789        if index < array.len() as i64 && index >= 0 {
790            self.dup_and_push(array[index as usize])
791        } else {
792            self.dup_and_push(self.null)
793        }
794    }
795
796    fn execute_hash_index(
797        &mut self,
798        hash: &HashMap<HashKey, GcRef>,
799        index: &Value,
800    ) -> Result<(), GcRuntimeError> {
801        let key = HashKey::from_value(index)
802            .ok_or_else(|| self.runtime_error("unsupported hash index key"))?;
803        match hash.get(&key) {
804            Some(value) => self.dup_and_push(*value),
805            None => self.dup_and_push(self.null),
806        }
807    }
808
809    fn current_frame(&mut self) -> &mut Frame {
810        &mut self.frames[self.frame_index - 1]
811    }
812
813    fn push_frame(&mut self, frame: Frame) -> Result<(), GcRuntimeError> {
814        if self.frame_index >= MAX_FRAMES {
815            return Err(self.runtime_error("frame limit exceeded"));
816        }
817        self.frames[self.frame_index] = frame;
818        self.frame_index += 1;
819        Ok(())
820    }
821
822    fn pop_frame(&mut self) -> Frame {
823        self.frame_index -= 1;
824        self.frames[self.frame_index].clone()
825    }
826
827    fn execute_call(&mut self, num_args: usize) -> Result<(), GcRuntimeError> {
828        let callee_slot = self.stack_base_for(num_args + 1)?;
829        let callee = self.stack[callee_slot];
830        match callee_kind(&self.heap, callee) {
831            CalleeKind::Closure(closure) => self.call_closure(closure, num_args),
832            CalleeKind::Builtin(builtin) => self.call_builtin(builtin, num_args),
833            CalleeKind::BoundMethod(bound) => self.call_bound_method(bound, num_args),
834            CalleeKind::Class(name) => {
835                Err(self.runtime_error(format!("class {} must be constructed with new", name)))
836            }
837            CalleeKind::Other(value) => Err(self.runtime_error(format!("cannot call {}", value))),
838        }
839    }
840
841    fn call_closure(&mut self, closure: GcClosure, num_args: usize) -> Result<(), GcRuntimeError> {
842        let compiled = match get_value(&self.heap, closure.func) {
843            Value::CompiledFunction(f) => f.clone(),
844            _ => return Err(self.runtime_error("closure without compiled function")),
845        };
846        if compiled.num_parameters != num_args {
847            return Err(self.runtime_error(format!(
848                "wrong number of arguments: want={}, got={}",
849                compiled.num_parameters, num_args
850            )));
851        }
852
853        let frame = Frame::new(closure, compiled.instructions, self.sp - num_args);
854        // checked_add: num_locals comes from bytecode, so it can be an
855        // arbitrary usize, not just a compiler-emitted small count.
856        let next_sp = frame
857            .base_pointer
858            .checked_add(compiled.num_locals)
859            .filter(|next_sp| *next_sp <= STACK_SIZE)
860            .ok_or_else(|| self.runtime_error("stack limit exceeded"))?;
861        self.sp = next_sp;
862        self.push_frame(frame)
863    }
864
865    fn call_builtin(&mut self, builtin: BuiltinId, num_args: usize) -> Result<(), GcRuntimeError> {
866        let base = self.sp - num_args - 1;
867        let args = self.stack[self.sp - num_args..self.sp].to_vec();
868        let result = call_builtin(&mut self.heap, builtin, &args, self.null);
869        self.clear_stack_range(base, self.sp);
870        self.sp = base;
871        self.push_raw(result)
872    }
873
874    fn push_closure(&mut self, const_index: usize, num_free: usize) -> Result<(), GcRuntimeError> {
875        let func = self.constant(const_index)?;
876        if !matches!(get_value(&self.heap, func), Value::CompiledFunction(_)) {
877            return Err(self.runtime_error(format!(
878                "cannot build closure over {}",
879                value_to_string(&self.heap, func)
880            )));
881        }
882        let start = self.stack_base_for(num_free)?;
883        let mut free = Vec::with_capacity(num_free);
884        for i in 0..num_free {
885            free.push(self.stack[start + i]);
886        }
887        let closure = alloc_value(
888            &mut self.heap,
889            Value::Closure(GcClosure {
890                func,
891                free,
892            }),
893        );
894        self.clear_stack_range(start, self.sp);
895        self.sp = start;
896        self.push_raw(closure)
897    }
898
899    fn constant(&self, index: usize) -> Result<GcRef, GcRuntimeError> {
900        self.constants
901            .get(index)
902            .copied()
903            .ok_or_else(|| self.runtime_error(format!("constant index {} out of range", index)))
904    }
905
906    fn constant_string(&self, index: usize) -> Result<String, GcRuntimeError> {
907        let constant = self.constant(index)?;
908        match get_value(&self.heap, constant) {
909            Value::String(value) => Ok(value.clone()),
910            value => Err(self.runtime_error(format!("expected string constant, got {}", value))),
911        }
912    }
913
914    fn install_method(
915        &mut self,
916        class: GcRef,
917        name: String,
918        method: GcRef,
919        constructor: bool,
920    ) -> Result<(), GcRuntimeError> {
921        if !matches!(get_value(&self.heap, class), Value::Class(_)) {
922            return Err(self.runtime_error(format!(
923                "cannot install method on {}",
924                value_to_string(&self.heap, class)
925            )));
926        }
927        let owned_method = self.heap.dup(method);
928        let old_method = match get_value_mut(&mut self.heap, class) {
929            Value::Class(class) => {
930                if constructor {
931                    class.constructor.replace(owned_method)
932                } else {
933                    class.methods.insert(name, owned_method)
934                }
935            }
936            _ => unreachable!(),
937        };
938        if let Some(old_method) = old_method {
939            self.heap.free(old_method);
940        }
941        Ok(())
942    }
943
944    fn get_property(&mut self, receiver: GcRef, name: &str) -> Result<GcRef, GcRuntimeError> {
945        let (class, field) = match get_value(&self.heap, receiver) {
946            Value::Instance(instance) => (instance.class, instance.fields.get(name).copied()),
947            _ => {
948                return Err(self.runtime_error(format!(
949                    "cannot read property '{}' of {}",
950                    name,
951                    value_to_string(&self.heap, receiver)
952                )))
953            }
954        };
955        if let Some(field) = field {
956            return Ok(self.heap.dup(field));
957        }
958
959        let (class_name, method) = match get_value(&self.heap, class) {
960            Value::Class(class) => (class.name.clone(), class.methods.get(name).copied()),
961            _ => return Err(self.runtime_error("instance has invalid class")),
962        };
963        match method {
964            Some(method) => Ok(alloc_value(
965                &mut self.heap,
966                Value::BoundMethod(GcBoundMethod {
967                    receiver,
968                    method,
969                    name: name.to_string(),
970                }),
971            )),
972            None => {
973                Err(self
974                    .runtime_error(format!("property '{}' does not exist on {}", name, class_name)))
975            }
976        }
977    }
978
979    fn set_property(
980        &mut self,
981        receiver: GcRef,
982        name: String,
983        value: GcRef,
984    ) -> Result<(), GcRuntimeError> {
985        if !matches!(get_value(&self.heap, receiver), Value::Instance(_)) {
986            return Err(self.runtime_error(format!(
987                "cannot set property '{}' of {}",
988                name,
989                value_to_string(&self.heap, receiver)
990            )));
991        }
992        let owned_value = self.heap.dup(value);
993        let old_value = match get_value_mut(&mut self.heap, receiver) {
994            Value::Instance(instance) => instance.fields.insert(name, owned_value),
995            _ => unreachable!(),
996        };
997        if let Some(old_value) = old_value {
998            self.heap.free(old_value);
999        }
1000        Ok(())
1001    }
1002
1003    fn execute_new(&mut self, num_args: usize) -> Result<(), GcRuntimeError> {
1004        let base = self.stack_base_for(num_args + 1)?;
1005        let class_reference = self.stack[base];
1006        let (class_name, constructor) = match get_value(&self.heap, class_reference) {
1007            Value::Class(class) => (class.name.clone(), class.constructor),
1008            _ => {
1009                return Err(self.runtime_error(format!(
1010                    "cannot construct {}",
1011                    value_to_string(&self.heap, class_reference)
1012                )))
1013            }
1014        };
1015
1016        let Some(constructor) = constructor else {
1017            if num_args != 0 {
1018                return Err(self.runtime_error(format!(
1019                    "wrong number of arguments for {}.constructor: want=0, got={}",
1020                    class_name, num_args
1021                )));
1022            }
1023            let instance = alloc_value(
1024                &mut self.heap,
1025                Value::Instance(GcInstance {
1026                    class: class_reference,
1027                    fields: HashMap::new(),
1028                }),
1029            );
1030            self.clear_stack_range(base, self.sp);
1031            self.sp = base;
1032            return self.push_raw(instance);
1033        };
1034
1035        let closure = match get_value(&self.heap, constructor) {
1036            Value::Closure(closure) => closure.clone(),
1037            _ => return Err(self.runtime_error("constructor is not a closure")),
1038        };
1039        let compiled = match get_value(&self.heap, closure.func) {
1040            Value::CompiledFunction(function) => function.clone(),
1041            _ => return Err(self.runtime_error("constructor closure has invalid function")),
1042        };
1043        let expected = compiled.num_parameters.saturating_sub(1);
1044        if expected != num_args {
1045            return Err(self.runtime_error(format!(
1046                "wrong number of arguments for {}.constructor: want={}, got={}",
1047                class_name, expected, num_args
1048            )));
1049        }
1050
1051        let instance = alloc_value(
1052            &mut self.heap,
1053            Value::Instance(GcInstance {
1054                class: class_reference,
1055                fields: HashMap::new(),
1056            }),
1057        );
1058        self.rewrite_receiver_call(constructor, instance, num_args)?;
1059        self.call_closure(closure, num_args + 1)
1060    }
1061
1062    fn call_bound_method(
1063        &mut self,
1064        bound: GcBoundMethod,
1065        num_args: usize,
1066    ) -> Result<(), GcRuntimeError> {
1067        let closure = match get_value(&self.heap, bound.method) {
1068            Value::Closure(closure) => closure.clone(),
1069            _ => return Err(self.runtime_error("bound method is not a closure")),
1070        };
1071        let compiled = match get_value(&self.heap, closure.func) {
1072            Value::CompiledFunction(function) => function.clone(),
1073            _ => return Err(self.runtime_error("method closure has invalid function")),
1074        };
1075        let expected = compiled.num_parameters.saturating_sub(1);
1076        if expected != num_args {
1077            let class_name = match get_value(&self.heap, bound.receiver) {
1078                Value::Instance(instance) => match get_value(&self.heap, instance.class) {
1079                    Value::Class(class) => class.name.clone(),
1080                    _ => "<invalid class>".to_string(),
1081                },
1082                _ => "<invalid receiver>".to_string(),
1083            };
1084            return Err(self.runtime_error(format!(
1085                "wrong number of arguments for {}.{}: want={}, got={}",
1086                class_name, bound.name, expected, num_args
1087            )));
1088        }
1089        let receiver = self.heap.dup(bound.receiver);
1090        self.rewrite_receiver_call(bound.method, receiver, num_args)?;
1091        self.call_closure(closure, num_args + 1)
1092    }
1093
1094    /// Takes ownership of `receiver` and frees it if the stack cannot hold
1095    /// the rewritten call layout.
1096    fn rewrite_receiver_call(
1097        &mut self,
1098        callable: GcRef,
1099        receiver: GcRef,
1100        num_args: usize,
1101    ) -> Result<(), GcRuntimeError> {
1102        let base = self.sp - num_args - 1;
1103        if base + num_args + 2 > STACK_SIZE {
1104            let error = self.runtime_error("stack limit exceeded");
1105            self.heap.free(receiver);
1106            return Err(error);
1107        }
1108        let callable = self.heap.dup(callable);
1109        let borrowed_arguments = self.stack[self.sp - num_args..self.sp].to_vec();
1110        let arguments = borrowed_arguments
1111            .into_iter()
1112            .map(|argument| self.heap.dup(argument))
1113            .collect::<Vec<_>>();
1114        self.clear_stack_range(base, self.sp);
1115        self.sp = base;
1116        self.push_raw(callable)?;
1117        self.push_raw(receiver)?;
1118        for argument in arguments {
1119            self.push_raw(argument)?;
1120        }
1121        Ok(())
1122    }
1123}
1124
1125fn is_truthy(heap: &GcHeap, condition: GcRef) -> bool {
1126    match get_value(heap, condition) {
1127        Value::Boolean(b) => *b,
1128        Value::Null => false,
1129        _ => true,
1130    }
1131}
1132
1133fn callee_kind(heap: &GcHeap, reference: GcRef) -> CalleeKind {
1134    match get_value(heap, reference) {
1135        Value::Closure(closure) => CalleeKind::Closure(closure.clone()),
1136        Value::Builtin(builtin) => CalleeKind::Builtin(*builtin),
1137        Value::BoundMethod(bound) => CalleeKind::BoundMethod(bound.clone()),
1138        Value::Class(class) => CalleeKind::Class(class.name.clone()),
1139        _ => CalleeKind::Other(value_to_string(heap, reference)),
1140    }
1141}
1142
1143fn compiled_instructions(heap: &GcHeap, func: GcRef) -> Vec<u8> {
1144    match get_value(heap, func) {
1145        Value::CompiledFunction(f) => f.instructions.clone(),
1146        _ => panic!("expected compiled function"),
1147    }
1148}