Skip to main content

gc/
vm.rs

1use std::collections::{HashMap, HashSet};
2
3use byteorder::{BigEndian, ByteOrder};
4use compiler::compiler::{BindingDebugInfo, 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::debugger::{collect_hit, DebuggerHit, HitContext, MAX_DEBUGGER_HITS};
12use crate::frame::Frame;
13use crate::report::{
14    empty_value_kind_counts, select_global_roots, summarize_gc_object, GcCollectionReport,
15    GlobalRoot,
16};
17use crate::value::{
18    alloc_value, call_builtin_with_output, export_object, get_value, get_value_mut, import_object,
19    try_export_object, value_to_string, GcBoundMethod, GcClass, GcClosure, GcInstance, HashKey,
20    Value,
21};
22use crate::{GcHeap, GcId, GcRef};
23
24const STACK_SIZE: usize = 2048;
25pub const GLOBAL_SIZE: usize = 65536;
26const MAX_FRAMES: usize = 1024;
27pub const DEFAULT_INSTRUCTION_BUDGET: usize = 100_000;
28
29/// Runtime failure returned by the established VM and runner APIs.
30#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
31#[serde(rename_all = "camelCase")]
32pub struct GcRuntimeError {
33    pub message: String,
34    pub span: Option<Span>,
35}
36
37/// Runtime failure with a stable, machine-readable category.
38#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
39#[serde(rename_all = "camelCase")]
40pub struct GcClassifiedRuntimeError {
41    pub kind: GcRuntimeErrorKind,
42    pub message: String,
43    pub span: Option<Span>,
44}
45
46impl From<GcClassifiedRuntimeError> for GcRuntimeError {
47    fn from(error: GcClassifiedRuntimeError) -> Self {
48        Self {
49            message: error.message,
50            span: error.span,
51        }
52    }
53}
54
55#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
56#[serde(rename_all = "camelCase")]
57pub enum GcRuntimeErrorKind {
58    Arithmetic,
59    Call,
60    ExecutionLimit,
61    Index,
62    Property,
63    Stack,
64    Type,
65    InvalidBytecode,
66}
67
68impl GcRuntimeErrorKind {
69    pub fn as_str(self) -> &'static str {
70        match self {
71            Self::Arithmetic => "arithmetic",
72            Self::Call => "call",
73            Self::ExecutionLimit => "executionLimit",
74            Self::Index => "index",
75            Self::Property => "property",
76            Self::Stack => "stack",
77            Self::Type => "type",
78            Self::InvalidBytecode => "invalidBytecode",
79        }
80    }
81}
82
83enum CalleeKind {
84    Closure(GcClosure),
85    Builtin(BuiltinId),
86    BoundMethod(GcBoundMethod),
87    Class(String),
88    Other(String),
89}
90
91pub struct GcVM {
92    heap: GcHeap,
93    constants: Vec<GcRef>,
94    stack: Vec<GcRef>,
95    sp: usize,
96    globals: Vec<GcRef>,
97    global_bindings: Vec<BindingDebugInfo>,
98    /// One flag per global slot: set once `OpSetGlobal` writes it, so the
99    /// debugger can tell a never-executed `let`'s prefilled null from a real
100    /// null value. Kept across `load_bytecode` like the globals themselves.
101    globals_initialized: Vec<bool>,
102    frames: Vec<Frame>,
103    frame_index: usize,
104    null: GcRef,
105    last_popped: GcRef,
106    main_debug_info: DebugInfo,
107    function_debug_info: HashMap<GcRef, DebugInfo>,
108    debugger_hits: Vec<DebuggerHit>,
109    /// Hits past `MAX_DEBUGGER_HITS` are counted, not recorded, so a
110    /// `debugger;` in a hot loop cannot grow memory without bound.
111    dropped_debugger_hits: usize,
112    output: Option<String>,
113}
114
115impl GcVM {
116    pub fn new(bytecode: Bytecode) -> Self {
117        let Bytecode {
118            instructions,
119            constants: object_constants,
120            debug_info: main_debug_info,
121            function_debug_info: object_function_debug_info,
122        } = bytecode;
123        let mut heap = GcHeap::new();
124        let null = alloc_value(&mut heap, Value::Null);
125        let constants = object_constants
126            .iter()
127            .map(|constant| import_object(&mut heap, constant))
128            .collect::<Vec<_>>();
129        let function_debug_info = object_function_debug_info
130            .into_iter()
131            .filter_map(|(index, debug_info)| {
132                constants
133                    .get(index)
134                    .copied()
135                    .map(|reference| (reference, debug_info))
136            })
137            .collect();
138
139        let main_fn = alloc_value(
140            &mut heap,
141            Value::CompiledFunction(object::CompiledFunction {
142                name: String::new(),
143                instructions: instructions.data,
144                num_locals: 0,
145                num_parameters: 0,
146            }),
147        );
148        let main_instructions = compiled_instructions(&heap, main_fn);
149        // Frames keep borrowed GcRefs. The initial main_fn allocation is the VM
150        // root for these handles; placeholder frames do not take extra refs.
151        let main_frame = Frame::new(
152            GcClosure {
153                func: main_fn,
154                free: vec![],
155            },
156            main_instructions,
157            0,
158            0,
159            0,
160        );
161
162        let empty_frame = Frame::new(
163            GcClosure {
164                func: main_fn,
165                free: vec![],
166            },
167            vec![],
168            0,
169            0,
170            0,
171        );
172
173        let mut frames = vec![empty_frame; MAX_FRAMES];
174        frames[0] = main_frame;
175
176        let stack = (0..STACK_SIZE).map(|_| heap.dup(null)).collect();
177        let globals = (0..GLOBAL_SIZE).map(|_| heap.dup(null)).collect();
178        let last_popped = heap.dup(null);
179
180        GcVM {
181            heap,
182            constants,
183            stack,
184            sp: 0,
185            globals,
186            global_bindings: Vec::new(),
187            globals_initialized: vec![false; GLOBAL_SIZE],
188            frames,
189            frame_index: 1,
190            null,
191            last_popped,
192            main_debug_info,
193            function_debug_info,
194            debugger_hits: Vec::new(),
195            dropped_debugger_hits: 0,
196            output: None,
197        }
198    }
199
200    /// Replace the current main program while preserving the heap and globals.
201    ///
202    /// Used by the REPL so later lines can read bindings created earlier. Old
203    /// constant-pool owning refs are released; objects still reachable from
204    /// globals stay alive.
205    pub fn load_bytecode(&mut self, bytecode: Bytecode) {
206        let Bytecode {
207            instructions,
208            constants: object_constants,
209            debug_info: main_debug_info,
210            function_debug_info: object_function_debug_info,
211        } = bytecode;
212
213        self.clear_stack_range(0, self.sp);
214        self.sp = 0;
215
216        // Reset the last result so statements that never pop (e.g. a bare
217        // `let`) report null instead of the previous program's result.
218        self.heap.free(self.last_popped);
219        self.last_popped = self.heap.dup(self.null);
220
221        for reference in self.constants.drain(..) {
222            self.heap.free(reference);
223        }
224        self.function_debug_info.clear();
225
226        let old_main = self.frames[0].cl.func;
227        self.heap.free(old_main);
228
229        self.constants = object_constants
230            .iter()
231            .map(|constant| import_object(&mut self.heap, constant))
232            .collect::<Vec<_>>();
233        self.function_debug_info = object_function_debug_info
234            .into_iter()
235            .filter_map(|(index, debug_info)| {
236                self.constants
237                    .get(index)
238                    .copied()
239                    .map(|reference| (reference, debug_info))
240            })
241            .collect();
242        self.main_debug_info = main_debug_info;
243
244        let main_fn = alloc_value(
245            &mut self.heap,
246            Value::CompiledFunction(object::CompiledFunction {
247                name: String::new(),
248                instructions: instructions.data,
249                num_locals: 0,
250                num_parameters: 0,
251            }),
252        );
253        let main_instructions = compiled_instructions(&self.heap, main_fn);
254        let main_frame = Frame::new(
255            GcClosure {
256                func: main_fn,
257                free: vec![],
258            },
259            main_instructions,
260            0,
261            0,
262            0,
263        );
264        let empty_frame = Frame::new(
265            GcClosure {
266                func: main_fn,
267                free: vec![],
268            },
269            vec![],
270            0,
271            0,
272            0,
273        );
274        self.frames = vec![empty_frame; MAX_FRAMES];
275        self.frames[0] = main_frame;
276        self.frame_index = 1;
277
278        // Hits describe the program that recorded them; a new program starts
279        // clean. Globals and their initialized bits survive, as above.
280        self.debugger_hits.clear();
281        self.dropped_debugger_hits = 0;
282    }
283
284    pub fn heap(&self) -> &GcHeap {
285        &self.heap
286    }
287
288    pub fn heap_mut(&mut self) -> &mut GcHeap {
289        &mut self.heap
290    }
291
292    /// Capture `puts`/`print` output in-memory instead of writing to stdout.
293    pub fn set_capture_output(&mut self, capture: bool) {
294        self.output = capture.then(String::new);
295    }
296
297    /// Drain captured output while leaving capture enabled.
298    pub fn take_output(&mut self) -> String {
299        self.output.as_mut().map(std::mem::take).unwrap_or_default()
300    }
301
302    /// Record the compiler's global definition ledger (slot order, rebindings
303    /// included), used by GC reports for the named root set (see `GlobalRoot`)
304    /// and by debugger snapshots for slot names.
305    pub fn set_global_bindings(&mut self, bindings: Vec<BindingDebugInfo>) {
306        self.global_bindings = bindings;
307    }
308
309    /// One flag per global slot: has `OpSetGlobal` written it? Debugger
310    /// snapshots use this to tell a user-assigned null from a never-run `let`.
311    pub fn globals_initialized(&self) -> &[bool] {
312        &self.globals_initialized
313    }
314
315    /// Drain recorded `debugger;` snapshots plus the count of hits dropped
316    /// after `MAX_DEBUGGER_HITS`. Recording resumes from zero afterwards.
317    pub fn take_debugger_hits(&mut self) -> (Vec<DebuggerHit>, usize) {
318        let dropped = std::mem::take(&mut self.dropped_debugger_hits);
319        return (std::mem::take(&mut self.debugger_hits), dropped);
320    }
321
322    fn record_debugger_hit(&mut self) {
323        if self.debugger_hits.len() >= MAX_DEBUGGER_HITS {
324            self.dropped_debugger_hits += 1;
325            return;
326        }
327        let hit = collect_hit(HitContext {
328            heap: &self.heap,
329            frames: &self.frames[..self.frame_index],
330            stack: &self.stack,
331            sp: self.sp,
332            globals: &self.globals,
333            global_bindings: &self.global_bindings,
334            globals_initialized: &self.globals_initialized,
335            main_debug_info: &self.main_debug_info,
336            function_debug_info: &self.function_debug_info,
337            index: self.debugger_hits.len() + 1,
338        });
339        self.debugger_hits.push(hit);
340    }
341
342    pub fn collect_garbage(&mut self) -> GcCollectionReport {
343        // Read the named global slots before collecting. Named slots are VM
344        // roots, so every object listed here survives the cycle collector.
345        // Reports show one root per visible name — for a rebound name the
346        // highest slot wins, matching what source-level code can still read —
347        // sorted by name for determinism.
348        let mut visible: HashMap<&str, usize> = HashMap::new();
349        for binding in &self.global_bindings {
350            visible.insert(binding.name.as_str(), binding.slot);
351        }
352        let mut named: Vec<(&str, usize)> = visible.into_iter().collect();
353        named.sort_unstable();
354        let global_roots = named
355            .into_iter()
356            .filter(|(_, slot)| *slot < self.globals.len())
357            .map(|(name, slot)| GlobalRoot {
358                name: name.to_string(),
359                object_id: self.globals[slot].0,
360            })
361            .collect();
362        let before_kinds = self.heap.value_kinds_by_id();
363        let before = self.heap.snapshot();
364        let diagnostics = self.heap.run_gc_with_stats_bundle();
365        let after = self.heap.snapshot();
366        let mut collected_by_value_kind = empty_value_kind_counts();
367        for (id, kind) in before_kinds {
368            if !self.heap.runtime().object_exists(id) {
369                *collected_by_value_kind.entry(kind).or_default() += 1;
370            }
371        }
372        let mut objects = diagnostics.objects;
373        let cataloged: HashSet<GcId> = objects.iter().map(|object| object.id).collect();
374        let (global_roots, omitted_global_roots) = select_global_roots(global_roots, &cataloged);
375        // The phase budgets pick the catalog without looking at the root set,
376        // so a kept root can name an object the catalog dropped. Summarize
377        // those now — named objects survived the collection, so they still
378        // exist. This keeps every reported root resolvable.
379        let mut uncataloged: Vec<GcId> = global_roots
380            .iter()
381            .map(|root| root.object_id)
382            .filter(|id| !cataloged.contains(id))
383            .collect();
384        uncataloged.sort_unstable();
385        uncataloged.dedup();
386        for id in uncataloged {
387            objects.push(summarize_gc_object(self.heap.runtime(), id));
388        }
389        objects.sort_unstable_by_key(|object| object.id);
390        GcCollectionReport {
391            before,
392            after,
393            objects,
394            global_roots,
395            omitted_global_roots,
396            phases: diagnostics.phases,
397            collected_by_value_kind,
398        }
399    }
400
401    /// Every raise site names its error category directly; a message is never
402    /// parsed to recover one.
403    fn runtime_error(
404        &self,
405        kind: GcRuntimeErrorKind,
406        message: impl Into<String>,
407    ) -> GcClassifiedRuntimeError {
408        let frame = &self.frames[self.frame_index - 1];
409        let debug_info = if self.frame_index == 1 {
410            Some(&self.main_debug_info)
411        } else {
412            self.function_debug_info.get(&frame.cl.func)
413        };
414        let span = debug_info.and_then(|debug_info| {
415            (frame.ip >= 0)
416                .then_some(frame.ip as usize)
417                .and_then(|pc| debug_info.span_for_pc(pc).cloned())
418        });
419        GcClassifiedRuntimeError {
420            kind,
421            message: message.into(),
422            span,
423        }
424    }
425
426    pub fn run(&mut self) {
427        self.run_with_budget(usize::MAX)
428            .expect("GC VM execution failed");
429    }
430
431    pub fn run_with_budget(&mut self, instruction_budget: usize) -> Result<(), GcRuntimeError> {
432        self.run_with_budget_classified(instruction_budget)
433            .map_err(Into::into)
434    }
435
436    /// Execute with a budget and retain the category assigned at the raise site.
437    pub fn run_with_budget_classified(
438        &mut self,
439        instruction_budget: usize,
440    ) -> Result<(), GcClassifiedRuntimeError> {
441        let mut executed = 0;
442        while self.current_frame().ip < self.current_frame().instructions.len() as i32 - 1 {
443            self.current_frame().ip += 1;
444            let ip = self.current_frame().ip as usize;
445            if executed >= instruction_budget {
446                return Err(self.runtime_error(
447                    GcRuntimeErrorKind::ExecutionLimit,
448                    format!("instruction limit exceeded (budget: {})", instruction_budget),
449                ));
450            }
451            executed += 1;
452            let ins = self.current_frame().instructions.clone();
453            let op = *ins.get(ip).unwrap();
454            let opcode = Opcode::from_repr(op).ok_or_else(|| {
455                self.runtime_error(
456                    GcRuntimeErrorKind::InvalidBytecode,
457                    format!("unknown opcode 0x{:02x}", op),
458                )
459            })?;
460
461            match opcode {
462                Opcode::OpConst => {
463                    let const_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
464                    self.current_frame().ip += 2;
465                    let constant = self.constant(const_index)?;
466                    self.dup_and_push(constant)?;
467                }
468                Opcode::OpAdd | Opcode::OpSub | Opcode::OpMul | Opcode::OpDiv => {
469                    self.execute_binary_operation(opcode)?;
470                }
471                Opcode::OpPop => {
472                    self.pop_discard()?;
473                }
474                Opcode::OpTrue => {
475                    self.alloc_and_push(Value::Boolean(true))?;
476                }
477                Opcode::OpFalse => {
478                    self.alloc_and_push(Value::Boolean(false))?;
479                }
480                Opcode::OpEqual
481                | Opcode::OpNotEqual
482                | Opcode::OpGreaterThan
483                | Opcode::OpLessThan => {
484                    self.execute_comparison(opcode)?;
485                }
486                Opcode::OpMinus => {
487                    self.execute_minus_operation()?;
488                }
489                Opcode::OpBang => {
490                    self.execute_bang_operation()?;
491                }
492                Opcode::OpJump => {
493                    let pos = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
494                    self.current_frame().ip = pos as i32 - 1;
495                }
496                Opcode::OpJumpNotTruthy => {
497                    let pos = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
498                    self.current_frame().ip += 2;
499                    let condition = self.pop_owned()?;
500                    if !is_truthy(&self.heap, condition) {
501                        self.current_frame().ip = pos as i32 - 1;
502                    }
503                    self.heap.free(condition);
504                }
505                Opcode::OpNull => {
506                    self.dup_and_push(self.null)?;
507                }
508                Opcode::OpGetGlobal => {
509                    let global_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
510                    self.current_frame().ip += 2;
511                    self.dup_and_push(self.globals[global_index])?;
512                }
513                Opcode::OpSetGlobal => {
514                    let global_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
515                    self.current_frame().ip += 2;
516                    let value = self.pop_owned()?;
517                    self.heap.free(self.globals[global_index]);
518                    self.globals[global_index] = value;
519                    self.globals_initialized[global_index] = true;
520                }
521                Opcode::OpArray => {
522                    let count = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
523                    self.current_frame().ip += 2;
524                    let start = self.stack_base_for(count)?;
525                    let elements = self.build_array(start, self.sp);
526                    let array = alloc_value(&mut self.heap, Value::Array(elements));
527                    self.clear_stack_range(start, self.sp);
528                    self.sp = start;
529                    self.push_raw(array)?;
530                }
531                Opcode::OpHash => {
532                    let count = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
533                    self.current_frame().ip += 2;
534                    let start = self.stack_base_for(count)?;
535                    let elements = self.build_hash(start, self.sp)?;
536                    let hash = alloc_value(&mut self.heap, Value::Hash(elements));
537                    self.clear_stack_range(start, self.sp);
538                    self.sp = start;
539                    self.push_raw(hash)?;
540                }
541                Opcode::OpIndex => {
542                    let (index, left) = self.pop_owned_pair()?;
543                    let result = self.execute_index_operation(left, index);
544                    self.heap.free(index);
545                    self.heap.free(left);
546                    result?;
547                }
548                Opcode::OpReturnValue => {
549                    let return_value = self.pop_owned()?;
550                    if self.frame_index == 1 {
551                        // A top-level return ends the program with this value
552                        // as its result, matching the interpreter backend.
553                        self.clear_stack_range(0, self.sp);
554                        self.sp = 0;
555                        self.heap.free(self.last_popped);
556                        self.last_popped = return_value;
557                        break;
558                    }
559                    let frame = self.pop_frame();
560                    let new_sp = frame.base_pointer - 1;
561                    self.clear_stack_range(new_sp, self.sp);
562                    self.sp = new_sp;
563                    self.push_raw(return_value)?;
564                }
565                Opcode::OpReturn => {
566                    if self.frame_index == 1 {
567                        self.clear_stack_range(0, self.sp);
568                        self.sp = 0;
569                        self.heap.free(self.last_popped);
570                        self.last_popped = self.heap.dup(self.null);
571                        break;
572                    }
573                    let frame = self.pop_frame();
574                    let new_sp = frame.base_pointer - 1;
575                    self.clear_stack_range(new_sp, self.sp);
576                    self.sp = new_sp;
577                    self.dup_and_push(self.null)?;
578                }
579                Opcode::OpCall => {
580                    let num_args = ins[ip + 1] as usize;
581                    self.current_frame().ip += 1;
582                    self.execute_call(num_args)?;
583                }
584                Opcode::OpSetLocal => {
585                    let local_index = ins[ip + 1] as usize;
586                    self.current_frame().ip += 1;
587                    let base = self.current_frame().base_pointer;
588                    let slot = self.local_slot(base, local_index)?;
589                    let value = self.pop_owned()?;
590                    self.heap.free(self.stack[slot]);
591                    self.stack[slot] = value;
592                    // get_mut: hostile bytecode can index past num_locals; the
593                    // write above is bounded by STACK_SIZE, the bitset is not.
594                    if let Some(flag) = self.current_frame().initialized.get_mut(local_index) {
595                        *flag = true;
596                    }
597                }
598                Opcode::OpGetLocal => {
599                    let local_index = ins[ip + 1] as usize;
600                    self.current_frame().ip += 1;
601                    let base = self.current_frame().base_pointer;
602                    let slot = self.local_slot(base, local_index)?;
603                    self.dup_and_push(self.stack[slot])?;
604                }
605                Opcode::OpGetBuiltin => {
606                    let built_index = ins[ip + 1] as usize;
607                    self.current_frame().ip += 1;
608                    let definition = BuiltIns.get(built_index).ok_or_else(|| {
609                        self.runtime_error(
610                            GcRuntimeErrorKind::InvalidBytecode,
611                            format!("builtin index {} out of range", built_index),
612                        )
613                    })?;
614                    self.alloc_and_push(Value::Builtin(definition.id))?;
615                }
616                Opcode::OpClosure => {
617                    let const_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
618                    let num_free = ins[ip + 3] as usize;
619                    self.current_frame().ip += 3;
620                    self.push_closure(const_index, num_free)?;
621                }
622                Opcode::OpGetFree => {
623                    let free_index = ins[ip + 1] as usize;
624                    self.current_frame().ip += 1;
625                    let free_var = self.current_frame().cl.free.get(free_index).copied();
626                    let free_var = free_var.ok_or_else(|| {
627                        self.runtime_error(
628                            GcRuntimeErrorKind::InvalidBytecode,
629                            format!("free variable index {} out of range", free_index),
630                        )
631                    })?;
632                    self.dup_and_push(free_var)?;
633                }
634                Opcode::OpCurrentClosure => {
635                    let current = self.current_frame().cl.clone();
636                    self.alloc_and_push(Value::Closure(current))?;
637                }
638                Opcode::OpClass => {
639                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
640                    self.current_frame().ip += 2;
641                    let name = self.constant_string(name_index)?;
642                    self.alloc_and_push(Value::Class(GcClass {
643                        name,
644                        constructor: None,
645                        methods: HashMap::new(),
646                    }))?;
647                }
648                Opcode::OpMethod => {
649                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
650                    let kind = ins[ip + 3];
651                    self.current_frame().ip += 3;
652                    let name = self.constant_string(name_index)?;
653                    let method = self.pop_owned()?;
654                    if self.sp == 0 {
655                        self.heap.free(method);
656                        return Err(
657                            self.runtime_error(GcRuntimeErrorKind::Stack, "stack underflow")
658                        );
659                    }
660                    let class = self.stack[self.sp - 1];
661                    let result = self.install_method(class, name, method, kind == 1);
662                    self.heap.free(method);
663                    result?;
664                }
665                Opcode::OpGetProperty => {
666                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
667                    self.current_frame().ip += 2;
668                    let name = self.constant_string(name_index)?;
669                    let receiver = self.pop_owned()?;
670                    let value = self.get_property(receiver, &name);
671                    self.heap.free(receiver);
672                    self.push_raw(value?)?;
673                }
674                Opcode::OpSetProperty => {
675                    let name_index = BigEndian::read_u16(&ins[ip + 1..ip + 3]) as usize;
676                    self.current_frame().ip += 2;
677                    let name = self.constant_string(name_index)?;
678                    let (value, receiver) = self.pop_owned_pair()?;
679                    let result = self.set_property(receiver, name, value);
680                    self.heap.free(value);
681                    self.heap.free(receiver);
682                    result?;
683                }
684                Opcode::OpNew => {
685                    let num_args = ins[ip + 1] as usize;
686                    self.current_frame().ip += 1;
687                    self.execute_new(num_args)?;
688                }
689                Opcode::OpDebugger => {
690                    // No stack effect; completion semantics stay transparent.
691                    self.record_debugger_hit();
692                }
693            }
694        }
695        Ok(())
696    }
697
698    pub fn last_popped_stack_elm(&self) -> Option<GcRef> {
699        Some(self.last_popped)
700    }
701
702    pub fn export_last_result(&self) -> Option<Object> {
703        self.last_popped_stack_elm()
704            .map(|reference| export_object(&self.heap, reference))
705    }
706
707    pub fn try_export_last_result(&self) -> Result<Object, String> {
708        try_export_object(&self.heap, self.last_popped)
709    }
710
711    pub fn last_result_string(&self) -> String {
712        value_to_string(&self.heap, self.last_popped)
713    }
714
715    fn alloc_and_push(&mut self, value: Value) -> Result<(), GcClassifiedRuntimeError> {
716        let reference = alloc_value(&mut self.heap, value);
717        self.push_raw(reference)
718    }
719
720    fn dup_and_push(&mut self, reference: GcRef) -> Result<(), GcClassifiedRuntimeError> {
721        let duplicated = self.heap.dup(reference);
722        self.push_raw(duplicated)
723    }
724
725    fn push_raw(&mut self, value: GcRef) -> Result<(), GcClassifiedRuntimeError> {
726        if self.sp >= STACK_SIZE {
727            let error = self.runtime_error(GcRuntimeErrorKind::Stack, "stack limit exceeded");
728            self.heap.free(value);
729            return Err(error);
730        }
731        let old = self.stack[self.sp];
732        self.stack[self.sp] = value;
733        self.heap.free(old);
734        self.sp += 1;
735        Ok(())
736    }
737
738    /// Move the top stack slot's owned reference to the caller.
739    ///
740    /// The caller must either free the returned ref or store it in another
741    /// owning location. The vacated stack slot is reset to a null ref.
742    fn pop_owned(&mut self) -> Result<GcRef, GcClassifiedRuntimeError> {
743        if self.sp == 0 {
744            return Err(self.runtime_error(GcRuntimeErrorKind::Stack, "stack underflow"));
745        }
746        self.sp -= 1;
747        let value = self.stack[self.sp];
748        self.stack[self.sp] = self.heap.dup(self.null);
749        Ok(value)
750    }
751
752    fn pop_discard(&mut self) -> Result<(), GcClassifiedRuntimeError> {
753        let value = self.pop_owned()?;
754        self.heap.free(self.last_popped);
755        self.last_popped = value;
756        Ok(())
757    }
758
759    /// Pop the top two owned references (top first). On underflow neither
760    /// reference leaks.
761    fn pop_owned_pair(&mut self) -> Result<(GcRef, GcRef), GcClassifiedRuntimeError> {
762        let top = self.pop_owned()?;
763        match self.pop_owned() {
764            Ok(below) => Ok((top, below)),
765            Err(error) => {
766                self.heap.free(top);
767                Err(error)
768            }
769        }
770    }
771
772    /// Bounds-checked `sp - count` for opcodes that consume `count` stack
773    /// slots.
774    fn stack_base_for(&self, count: usize) -> Result<usize, GcClassifiedRuntimeError> {
775        self.sp
776            .checked_sub(count)
777            .ok_or_else(|| self.runtime_error(GcRuntimeErrorKind::Stack, "stack underflow"))
778    }
779
780    fn local_slot(
781        &self,
782        base: usize,
783        local_index: usize,
784    ) -> Result<usize, GcClassifiedRuntimeError> {
785        let slot = base + local_index;
786        if slot >= STACK_SIZE {
787            return Err(self.runtime_error(
788                GcRuntimeErrorKind::InvalidBytecode,
789                format!("local index {} out of range", local_index),
790            ));
791        }
792        Ok(slot)
793    }
794
795    fn clear_stack_range(&mut self, start: usize, end: usize) {
796        for index in start..end {
797            let old = self.stack[index];
798            self.stack[index] = self.heap.dup(self.null);
799            self.heap.free(old);
800        }
801    }
802
803    fn execute_binary_operation(&mut self, opcode: Opcode) -> Result<(), GcClassifiedRuntimeError> {
804        let (right, left) = self.pop_owned_pair()?;
805        let left_value = get_value(&self.heap, left).clone();
806        let right_value = get_value(&self.heap, right).clone();
807        let result = match (&left_value, &right_value) {
808            (Value::Integer(l), Value::Integer(r)) => match opcode {
809                Opcode::OpAdd => Ok(Value::Integer(l.wrapping_add(*r))),
810                Opcode::OpSub => Ok(Value::Integer(l.wrapping_sub(*r))),
811                Opcode::OpMul => Ok(Value::Integer(l.wrapping_mul(*r))),
812                Opcode::OpDiv if *r != 0 => {
813                    l.checked_div(*r).map(Value::Integer).ok_or_else(|| {
814                        (GcRuntimeErrorKind::Arithmetic, "integer overflow in division".to_string())
815                    })
816                }
817                Opcode::OpDiv => {
818                    Err((GcRuntimeErrorKind::Arithmetic, "division by zero".to_string()))
819                }
820                _ => unreachable!(),
821            },
822            (Value::String(l), Value::String(r)) if opcode == Opcode::OpAdd => {
823                Ok(Value::String(l.to_string() + r))
824            }
825            _ => Err((
826                GcRuntimeErrorKind::Type,
827                format!(
828                    "unsupported binary operation for {} and {}",
829                    value_to_string(&self.heap, left),
830                    value_to_string(&self.heap, right)
831                ),
832            )),
833        };
834        self.heap.free(left);
835        self.heap.free(right);
836        match result {
837            Ok(value) => self.alloc_and_push(value),
838            Err((kind, message)) => Err(self.runtime_error(kind, message)),
839        }
840    }
841
842    fn execute_comparison(&mut self, opcode: Opcode) -> Result<(), GcClassifiedRuntimeError> {
843        let (right, left) = self.pop_owned_pair()?;
844        let result = match (get_value(&self.heap, left), get_value(&self.heap, right)) {
845            (Value::Integer(l), Value::Integer(r)) => match opcode {
846                Opcode::OpEqual => Some(l == r),
847                Opcode::OpNotEqual => Some(l != r),
848                Opcode::OpGreaterThan => Some(l > r),
849                Opcode::OpLessThan => Some(l < r),
850                _ => unreachable!(),
851            },
852            (Value::Boolean(l), Value::Boolean(r)) => match opcode {
853                Opcode::OpEqual => Some(l == r),
854                Opcode::OpNotEqual => Some(l != r),
855                _ => None,
856            },
857            (Value::String(l), Value::String(r)) => match opcode {
858                Opcode::OpEqual => Some(l == r),
859                Opcode::OpNotEqual => Some(l != r),
860                _ => None,
861            },
862            (Value::Null, Value::Null) => match opcode {
863                Opcode::OpEqual => Some(true),
864                Opcode::OpNotEqual => Some(false),
865                _ => None,
866            },
867            (Value::Class(_), Value::Class(_))
868            | (Value::Instance(_), Value::Instance(_))
869            | (Value::BoundMethod(_), Value::BoundMethod(_)) => match opcode {
870                Opcode::OpEqual => Some(left == right),
871                Opcode::OpNotEqual => Some(left != right),
872                _ => None,
873            },
874            _ => None,
875        };
876        let message = if result.is_none() {
877            Some(format!(
878                "unsupported comparison for {} and {}",
879                value_to_string(&self.heap, left),
880                value_to_string(&self.heap, right)
881            ))
882        } else {
883            None
884        };
885        self.heap.free(left);
886        self.heap.free(right);
887        if let Some(result) = result {
888            self.alloc_and_push(Value::Boolean(result))
889        } else {
890            Err(self.runtime_error(GcRuntimeErrorKind::Type, message.unwrap()))
891        }
892    }
893
894    fn execute_minus_operation(&mut self) -> Result<(), GcClassifiedRuntimeError> {
895        let operand = self.pop_owned()?;
896        let negated = match get_value(&self.heap, operand) {
897            Value::Integer(value) => Some(value.wrapping_neg()),
898            _ => None,
899        };
900        let message = negated.is_none().then(|| {
901            format!("unsupported type for negation: {}", value_to_string(&self.heap, operand))
902        });
903        self.heap.free(operand);
904        if let Some(negated) = negated {
905            self.alloc_and_push(Value::Integer(negated))
906        } else {
907            Err(self.runtime_error(GcRuntimeErrorKind::Type, message.unwrap()))
908        }
909    }
910
911    fn execute_bang_operation(&mut self) -> Result<(), GcClassifiedRuntimeError> {
912        let operand = self.pop_owned()?;
913        let result = match get_value(&self.heap, operand) {
914            Value::Boolean(l) => !l,
915            _ => false,
916        };
917        self.heap.free(operand);
918        self.alloc_and_push(Value::Boolean(result))
919    }
920
921    fn build_array(&mut self, start: usize, end: usize) -> Vec<GcRef> {
922        let mut elements = Vec::with_capacity(end - start);
923        for i in start..end {
924            elements.push(self.stack[i]);
925        }
926        elements
927    }
928
929    fn build_hash(
930        &mut self,
931        start: usize,
932        end: usize,
933    ) -> Result<HashMap<HashKey, GcRef>, GcClassifiedRuntimeError> {
934        let mut elements = HashMap::new();
935        for i in (start..end).step_by(2) {
936            let key_ref = self.stack[i];
937            let key = HashKey::from_value(get_value(&self.heap, key_ref)).ok_or_else(|| {
938                self.runtime_error(
939                    GcRuntimeErrorKind::Index,
940                    format!(
941                        "hash key must be hashable, got {}",
942                        value_to_string(&self.heap, key_ref)
943                    ),
944                )
945            })?;
946            elements.insert(key, self.stack[i + 1]);
947        }
948        Ok(elements)
949    }
950
951    fn execute_index_operation(
952        &mut self,
953        left: GcRef,
954        index: GcRef,
955    ) -> Result<(), GcClassifiedRuntimeError> {
956        let left_value = get_value(&self.heap, left).clone();
957        let index_value = get_value(&self.heap, index).clone();
958        match (&left_value, &index_value) {
959            (Value::Array(array), Value::Integer(i)) => self.execute_array_index(array, *i),
960            (Value::Hash(hash), _) => self.execute_hash_index(hash, &index_value),
961            _ => Err(self.runtime_error(
962                GcRuntimeErrorKind::Index,
963                format!(
964                    "unsupported index operation for {} and {}",
965                    value_to_string(&self.heap, left),
966                    value_to_string(&self.heap, index)
967                ),
968            )),
969        }
970    }
971
972    fn execute_array_index(
973        &mut self,
974        array: &[GcRef],
975        index: i64,
976    ) -> Result<(), GcClassifiedRuntimeError> {
977        if index < array.len() as i64 && index >= 0 {
978            self.dup_and_push(array[index as usize])
979        } else {
980            self.dup_and_push(self.null)
981        }
982    }
983
984    fn execute_hash_index(
985        &mut self,
986        hash: &HashMap<HashKey, GcRef>,
987        index: &Value,
988    ) -> Result<(), GcClassifiedRuntimeError> {
989        let key = HashKey::from_value(index).ok_or_else(|| {
990            self.runtime_error(GcRuntimeErrorKind::Index, "unsupported hash index key")
991        })?;
992        match hash.get(&key) {
993            Some(value) => self.dup_and_push(*value),
994            None => self.dup_and_push(self.null),
995        }
996    }
997
998    fn current_frame(&mut self) -> &mut Frame {
999        &mut self.frames[self.frame_index - 1]
1000    }
1001
1002    fn push_frame(&mut self, frame: Frame) -> Result<(), GcClassifiedRuntimeError> {
1003        if self.frame_index >= MAX_FRAMES {
1004            return Err(self.runtime_error(GcRuntimeErrorKind::Stack, "frame limit exceeded"));
1005        }
1006        self.frames[self.frame_index] = frame;
1007        self.frame_index += 1;
1008        Ok(())
1009    }
1010
1011    fn pop_frame(&mut self) -> Frame {
1012        self.frame_index -= 1;
1013        self.frames[self.frame_index].clone()
1014    }
1015
1016    fn execute_call(&mut self, num_args: usize) -> Result<(), GcClassifiedRuntimeError> {
1017        let callee_slot = self.stack_base_for(num_args + 1)?;
1018        let callee = self.stack[callee_slot];
1019        match callee_kind(&self.heap, callee) {
1020            CalleeKind::Closure(closure) => self.call_closure(closure, num_args),
1021            CalleeKind::Builtin(builtin) => self.call_builtin(builtin, num_args),
1022            CalleeKind::BoundMethod(bound) => self.call_bound_method(bound, num_args),
1023            CalleeKind::Class(name) => Err(self.runtime_error(
1024                GcRuntimeErrorKind::Call,
1025                format!("class {} must be constructed with new", name),
1026            )),
1027            CalleeKind::Other(value) => {
1028                Err(self.runtime_error(GcRuntimeErrorKind::Call, format!("cannot call {}", value)))
1029            }
1030        }
1031    }
1032
1033    fn call_closure(
1034        &mut self,
1035        closure: GcClosure,
1036        num_args: usize,
1037    ) -> Result<(), GcClassifiedRuntimeError> {
1038        let compiled = match get_value(&self.heap, closure.func) {
1039            Value::CompiledFunction(f) => f.clone(),
1040            _ => {
1041                return Err(self.runtime_error(
1042                    GcRuntimeErrorKind::InvalidBytecode,
1043                    "closure without compiled function",
1044                ))
1045            }
1046        };
1047        if compiled.num_parameters != num_args {
1048            return Err(self.runtime_error(
1049                GcRuntimeErrorKind::Call,
1050                format!(
1051                    "wrong number of arguments: want={}, got={}",
1052                    compiled.num_parameters, num_args
1053                ),
1054            ));
1055        }
1056
1057        // checked_add: num_locals comes from bytecode, so it can be an
1058        // arbitrary usize, not just a compiler-emitted small count. Validate
1059        // it before Frame::new allocates the per-local initialized bitset.
1060        let base_pointer = self.sp - num_args;
1061        let next_sp = base_pointer
1062            .checked_add(compiled.num_locals)
1063            .filter(|next_sp| *next_sp <= STACK_SIZE)
1064            .ok_or_else(|| self.runtime_error(GcRuntimeErrorKind::Stack, "stack limit exceeded"))?;
1065        let frame = Frame::new(
1066            closure,
1067            compiled.instructions,
1068            base_pointer,
1069            compiled.num_locals,
1070            compiled.num_parameters,
1071        );
1072        self.sp = next_sp;
1073        self.push_frame(frame)
1074    }
1075
1076    fn call_builtin(
1077        &mut self,
1078        builtin: BuiltinId,
1079        num_args: usize,
1080    ) -> Result<(), GcClassifiedRuntimeError> {
1081        let base = self.sp - num_args - 1;
1082        let args = self.stack[self.sp - num_args..self.sp].to_vec();
1083        let result = call_builtin_with_output(
1084            &mut self.heap,
1085            builtin,
1086            &args,
1087            self.null,
1088            self.output.as_mut(),
1089        );
1090        self.clear_stack_range(base, self.sp);
1091        self.sp = base;
1092        self.push_raw(result)
1093    }
1094
1095    fn push_closure(
1096        &mut self,
1097        const_index: usize,
1098        num_free: usize,
1099    ) -> Result<(), GcClassifiedRuntimeError> {
1100        let func = self.constant(const_index)?;
1101        if !matches!(get_value(&self.heap, func), Value::CompiledFunction(_)) {
1102            return Err(self.runtime_error(
1103                GcRuntimeErrorKind::InvalidBytecode,
1104                format!("cannot build closure over {}", value_to_string(&self.heap, func)),
1105            ));
1106        }
1107        let start = self.stack_base_for(num_free)?;
1108        let mut free = Vec::with_capacity(num_free);
1109        for i in 0..num_free {
1110            free.push(self.stack[start + i]);
1111        }
1112        let closure = alloc_value(
1113            &mut self.heap,
1114            Value::Closure(GcClosure {
1115                func,
1116                free,
1117            }),
1118        );
1119        self.clear_stack_range(start, self.sp);
1120        self.sp = start;
1121        self.push_raw(closure)
1122    }
1123
1124    fn constant(&self, index: usize) -> Result<GcRef, GcClassifiedRuntimeError> {
1125        self.constants.get(index).copied().ok_or_else(|| {
1126            self.runtime_error(
1127                GcRuntimeErrorKind::InvalidBytecode,
1128                format!("constant index {} out of range", index),
1129            )
1130        })
1131    }
1132
1133    fn constant_string(&self, index: usize) -> Result<String, GcClassifiedRuntimeError> {
1134        let constant = self.constant(index)?;
1135        match get_value(&self.heap, constant) {
1136            Value::String(value) => Ok(value.clone()),
1137            value => Err(self.runtime_error(
1138                GcRuntimeErrorKind::InvalidBytecode,
1139                format!("expected string constant, got {}", value),
1140            )),
1141        }
1142    }
1143
1144    fn install_method(
1145        &mut self,
1146        class: GcRef,
1147        name: String,
1148        method: GcRef,
1149        constructor: bool,
1150    ) -> Result<(), GcClassifiedRuntimeError> {
1151        if !matches!(get_value(&self.heap, class), Value::Class(_)) {
1152            return Err(self.runtime_error(
1153                GcRuntimeErrorKind::InvalidBytecode,
1154                format!("cannot install method on {}", value_to_string(&self.heap, class)),
1155            ));
1156        }
1157        let owned_method = self.heap.dup(method);
1158        let old_method = match get_value_mut(&mut self.heap, class) {
1159            Value::Class(class) => {
1160                if constructor {
1161                    class.constructor.replace(owned_method)
1162                } else {
1163                    class.methods.insert(name, owned_method)
1164                }
1165            }
1166            _ => unreachable!(),
1167        };
1168        if let Some(old_method) = old_method {
1169            self.heap.free(old_method);
1170        }
1171        Ok(())
1172    }
1173
1174    fn get_property(
1175        &mut self,
1176        receiver: GcRef,
1177        name: &str,
1178    ) -> Result<GcRef, GcClassifiedRuntimeError> {
1179        let (class, field) = match get_value(&self.heap, receiver) {
1180            Value::Instance(instance) => (instance.class, instance.fields.get(name).copied()),
1181            _ => {
1182                return Err(self.runtime_error(
1183                    GcRuntimeErrorKind::Property,
1184                    format!(
1185                        "cannot read property '{}' of {}",
1186                        name,
1187                        value_to_string(&self.heap, receiver)
1188                    ),
1189                ))
1190            }
1191        };
1192        if let Some(field) = field {
1193            return Ok(self.heap.dup(field));
1194        }
1195
1196        let (class_name, method) = match get_value(&self.heap, class) {
1197            Value::Class(class) => (class.name.clone(), class.methods.get(name).copied()),
1198            _ => {
1199                return Err(self.runtime_error(
1200                    GcRuntimeErrorKind::InvalidBytecode,
1201                    "instance has invalid class",
1202                ))
1203            }
1204        };
1205        match method {
1206            Some(method) => Ok(alloc_value(
1207                &mut self.heap,
1208                Value::BoundMethod(GcBoundMethod {
1209                    receiver,
1210                    method,
1211                    name: name.to_string(),
1212                }),
1213            )),
1214            None => Err(self.runtime_error(
1215                GcRuntimeErrorKind::Property,
1216                format!("property '{}' does not exist on {}", name, class_name),
1217            )),
1218        }
1219    }
1220
1221    fn set_property(
1222        &mut self,
1223        receiver: GcRef,
1224        name: String,
1225        value: GcRef,
1226    ) -> Result<(), GcClassifiedRuntimeError> {
1227        if !matches!(get_value(&self.heap, receiver), Value::Instance(_)) {
1228            return Err(self.runtime_error(
1229                GcRuntimeErrorKind::Property,
1230                format!(
1231                    "cannot set property '{}' of {}",
1232                    name,
1233                    value_to_string(&self.heap, receiver)
1234                ),
1235            ));
1236        }
1237        let owned_value = self.heap.dup(value);
1238        let old_value = match get_value_mut(&mut self.heap, receiver) {
1239            Value::Instance(instance) => instance.fields.insert(name, owned_value),
1240            _ => unreachable!(),
1241        };
1242        if let Some(old_value) = old_value {
1243            self.heap.free(old_value);
1244        }
1245        Ok(())
1246    }
1247
1248    fn execute_new(&mut self, num_args: usize) -> Result<(), GcClassifiedRuntimeError> {
1249        let base = self.stack_base_for(num_args + 1)?;
1250        let class_reference = self.stack[base];
1251        let (class_name, constructor) = match get_value(&self.heap, class_reference) {
1252            Value::Class(class) => (class.name.clone(), class.constructor),
1253            _ => {
1254                return Err(self.runtime_error(
1255                    GcRuntimeErrorKind::Call,
1256                    format!("cannot construct {}", value_to_string(&self.heap, class_reference)),
1257                ))
1258            }
1259        };
1260
1261        let Some(constructor) = constructor else {
1262            if num_args != 0 {
1263                return Err(self.runtime_error(
1264                    GcRuntimeErrorKind::Call,
1265                    format!(
1266                        "wrong number of arguments for {}.constructor: want=0, got={}",
1267                        class_name, num_args
1268                    ),
1269                ));
1270            }
1271            let instance = alloc_value(
1272                &mut self.heap,
1273                Value::Instance(GcInstance {
1274                    class: class_reference,
1275                    fields: HashMap::new(),
1276                }),
1277            );
1278            self.clear_stack_range(base, self.sp);
1279            self.sp = base;
1280            return self.push_raw(instance);
1281        };
1282
1283        let closure = match get_value(&self.heap, constructor) {
1284            Value::Closure(closure) => closure.clone(),
1285            _ => {
1286                return Err(self.runtime_error(
1287                    GcRuntimeErrorKind::InvalidBytecode,
1288                    "constructor is not a closure",
1289                ))
1290            }
1291        };
1292        let compiled = match get_value(&self.heap, closure.func) {
1293            Value::CompiledFunction(function) => function.clone(),
1294            _ => {
1295                return Err(self.runtime_error(
1296                    GcRuntimeErrorKind::InvalidBytecode,
1297                    "constructor closure has invalid function",
1298                ))
1299            }
1300        };
1301        let expected = compiled.num_parameters.saturating_sub(1);
1302        if expected != num_args {
1303            return Err(self.runtime_error(
1304                GcRuntimeErrorKind::Call,
1305                format!(
1306                    "wrong number of arguments for {}.constructor: want={}, got={}",
1307                    class_name, expected, num_args
1308                ),
1309            ));
1310        }
1311
1312        let instance = alloc_value(
1313            &mut self.heap,
1314            Value::Instance(GcInstance {
1315                class: class_reference,
1316                fields: HashMap::new(),
1317            }),
1318        );
1319        self.rewrite_receiver_call(constructor, instance, num_args)?;
1320        self.call_closure(closure, num_args + 1)
1321    }
1322
1323    fn call_bound_method(
1324        &mut self,
1325        bound: GcBoundMethod,
1326        num_args: usize,
1327    ) -> Result<(), GcClassifiedRuntimeError> {
1328        let closure = match get_value(&self.heap, bound.method) {
1329            Value::Closure(closure) => closure.clone(),
1330            _ => {
1331                return Err(self.runtime_error(
1332                    GcRuntimeErrorKind::InvalidBytecode,
1333                    "bound method is not a closure",
1334                ))
1335            }
1336        };
1337        let compiled = match get_value(&self.heap, closure.func) {
1338            Value::CompiledFunction(function) => function.clone(),
1339            _ => {
1340                return Err(self.runtime_error(
1341                    GcRuntimeErrorKind::InvalidBytecode,
1342                    "method closure has invalid function",
1343                ))
1344            }
1345        };
1346        let expected = compiled.num_parameters.saturating_sub(1);
1347        if expected != num_args {
1348            let class_name = match get_value(&self.heap, bound.receiver) {
1349                Value::Instance(instance) => match get_value(&self.heap, instance.class) {
1350                    Value::Class(class) => class.name.clone(),
1351                    _ => "<invalid class>".to_string(),
1352                },
1353                _ => "<invalid receiver>".to_string(),
1354            };
1355            return Err(self.runtime_error(
1356                GcRuntimeErrorKind::Call,
1357                format!(
1358                    "wrong number of arguments for {}.{}: want={}, got={}",
1359                    class_name, bound.name, expected, num_args
1360                ),
1361            ));
1362        }
1363        let receiver = self.heap.dup(bound.receiver);
1364        self.rewrite_receiver_call(bound.method, receiver, num_args)?;
1365        self.call_closure(closure, num_args + 1)
1366    }
1367
1368    /// Takes ownership of `receiver` and frees it if the stack cannot hold
1369    /// the rewritten call layout.
1370    fn rewrite_receiver_call(
1371        &mut self,
1372        callable: GcRef,
1373        receiver: GcRef,
1374        num_args: usize,
1375    ) -> Result<(), GcClassifiedRuntimeError> {
1376        let base = self.sp - num_args - 1;
1377        if base + num_args + 2 > STACK_SIZE {
1378            let error = self.runtime_error(GcRuntimeErrorKind::Stack, "stack limit exceeded");
1379            self.heap.free(receiver);
1380            return Err(error);
1381        }
1382        let callable = self.heap.dup(callable);
1383        let borrowed_arguments = self.stack[self.sp - num_args..self.sp].to_vec();
1384        let arguments = borrowed_arguments
1385            .into_iter()
1386            .map(|argument| self.heap.dup(argument))
1387            .collect::<Vec<_>>();
1388        self.clear_stack_range(base, self.sp);
1389        self.sp = base;
1390        self.push_raw(callable)?;
1391        self.push_raw(receiver)?;
1392        for argument in arguments {
1393            self.push_raw(argument)?;
1394        }
1395        Ok(())
1396    }
1397}
1398
1399fn is_truthy(heap: &GcHeap, condition: GcRef) -> bool {
1400    match get_value(heap, condition) {
1401        Value::Boolean(b) => *b,
1402        Value::Null => false,
1403        _ => true,
1404    }
1405}
1406
1407fn callee_kind(heap: &GcHeap, reference: GcRef) -> CalleeKind {
1408    match get_value(heap, reference) {
1409        Value::Closure(closure) => CalleeKind::Closure(closure.clone()),
1410        Value::Builtin(builtin) => CalleeKind::Builtin(*builtin),
1411        Value::BoundMethod(bound) => CalleeKind::BoundMethod(bound.clone()),
1412        Value::Class(class) => CalleeKind::Class(class.name.clone()),
1413        _ => CalleeKind::Other(value_to_string(heap, reference)),
1414    }
1415}
1416
1417fn compiled_instructions(heap: &GcHeap, func: GcRef) -> Vec<u8> {
1418    match get_value(heap, func) {
1419        Value::CompiledFunction(f) => f.instructions.clone(),
1420        _ => panic!("expected compiled function"),
1421    }
1422}