lox_lang/vm/
gc.rs

1use std::{any::Any, mem};
2
3use crate::Gc;
4
5impl super::VM {
6    fn mark_roots(&mut self) -> Vec<Gc<dyn Any>> {
7        let mut gray_stack = Vec::new();
8
9        // locals and temporaries
10        for el in &self.stack {
11            el.mark(&mut gray_stack);
12        }
13
14        // globals
15        for val in self.globals.values() {
16            val.mark(&mut gray_stack);
17        }
18
19        // call frames
20        for frame in &self.frames {
21            frame.mark(&mut gray_stack);
22        }
23
24        // open upvalues
25        for c in self.open_upvalues.iter() {
26            c.mark();
27            gray_stack.push(c.as_any());
28        }
29
30        // compiler roots
31        for root in &self.compiler_roots {
32            root.mark(&mut gray_stack);
33        }
34
35        gray_stack
36    }
37
38    fn trace_references(gray_stack: &mut Vec<Gc<dyn Any>>) {
39        while let Some(top_obj) = gray_stack.pop() {
40            top_obj.blacken(gray_stack);
41        }
42    }
43
44    fn sweep(&mut self) {
45        let to_drop = self.objects.retain(|obj| {
46            if obj.is_marked() {
47                obj.clear_mark();
48                true
49            } else {
50                false
51            }
52        });
53
54        for ptr in to_drop {
55            self.total_allocations -= mem::size_of_val(&*ptr);
56            ptr.free();
57        }
58    }
59
60    fn collect_garbage(&mut self) {
61        #[cfg(feature = "trace-gc")]
62        let before = self.total_allocations;
63
64        #[cfg(feature = "trace-gc")]
65        log::debug!("gc begin :: total allocations {} bytes", before);
66
67        let mut gray_stack = self.mark_roots();
68        Self::trace_references(&mut gray_stack);
69        self.sweep();
70
71        // adjust threshold
72        self.next_gc = self.total_allocations * 2;
73
74        #[cfg(feature = "trace-gc")]
75        log::debug!(
76            "gc end   :: collected {} bytes (total was {}, now {}) :: next at {}",
77            before - self.total_allocations,
78            before,
79            self.total_allocations,
80            self.next_gc
81        );
82    }
83
84    fn should_collect(&self) -> bool {
85        cfg!(feature = "stress-test-gc") || self.total_allocations > self.next_gc
86    }
87
88    /// Allocate a garbage-collected value on the heap.
89    ///
90    /// This method is how to obtain a `Gc` pointer (not exported from this crate and has no public
91    /// constructor). Values allocated with this method will be owned (and eventually freed) by the
92    /// VM. If the value lives until the VM goes out of scope, it will be freed in the VM's `Drop`
93    /// implementation.
94    ///
95    /// For a usage example, see [`NativeFun`](./type.NativeFun.html).
96    pub fn alloc<T: Any>(&mut self, obj: T) -> Gc<T> {
97        if self.should_collect() {
98            self.collect_garbage();
99        }
100
101        let size = mem::size_of::<T>();
102        self.total_allocations += size;
103
104        let ptr = Gc::new(obj);
105        self.objects.push(ptr.as_any());
106
107        #[cfg(feature = "trace-gc")]
108        log::debug!(
109            "{:p} allocate {} bytes for {}",
110            ptr,
111            size,
112            std::any::type_name::<T>()
113        );
114
115        ptr
116    }
117}