spore_vm/gc/
keep_reachable_set.rs

1use std::{
2    collections::{hash_map::Entry, HashMap},
3    hash::Hash,
4};
5
6use compact_str::CompactString;
7use log::*;
8
9use crate::val::{custom::CustomVal, ByteCode, ListVal, StructVal, UnsafeVal, ValId};
10
11use super::is_garbage_collected;
12
13type ReferenceCounter = usize;
14
15/// A set that keeps tracks of garbage collected values to keep reachable. Keeping reachable
16/// prevents the garbage collector from cleaning up its values.
17#[derive(Debug, Default)]
18pub struct KeepReachableSet {
19    strings: HashMap<ValId<CompactString>, ReferenceCounter>,
20    mutable_boxes: HashMap<ValId<UnsafeVal>, ReferenceCounter>,
21    lists: HashMap<ValId<ListVal>, ReferenceCounter>,
22    structs: HashMap<ValId<StructVal>, ReferenceCounter>,
23    bytecodes: HashMap<ValId<ByteCode>, ReferenceCounter>,
24    customs: HashMap<ValId<CustomVal>, ReferenceCounter>,
25}
26
27impl KeepReachableSet {
28    /// Iterate over all values that should be kept reachable.
29    pub fn iter(&self) -> impl '_ + Iterator<Item = UnsafeVal> {
30        self.strings
31            .keys()
32            .copied()
33            .map(Into::into)
34            .chain(self.mutable_boxes.keys().copied().map(Into::into))
35            .chain(self.lists.keys().copied().map(Into::into))
36            .chain(self.bytecodes.keys().copied().map(Into::into))
37            .chain(self.customs.keys().copied().map(Into::into))
38    }
39
40    /// Insert a value into the set of reachable values.
41    pub fn insert(&mut self, val: UnsafeVal) {
42        match val {
43            UnsafeVal::String(x) => self.strings.increment(x),
44            UnsafeVal::MutableBox(x) => self.mutable_boxes.increment(x),
45            UnsafeVal::List(x) => self.lists.increment(x),
46            UnsafeVal::Struct(x) => self.structs.increment(x),
47            UnsafeVal::ByteCodeFunction(x) => self.bytecodes.increment(x),
48            UnsafeVal::Custom(x) => self.customs.increment(x),
49            v => assert!(!is_garbage_collected(v)),
50        }
51    }
52
53    /// Remove a value from the set to keep reachable.
54    pub fn remove(&mut self, val: UnsafeVal) {
55        match val {
56            UnsafeVal::String(x) => self.strings.decrement(x),
57            UnsafeVal::MutableBox(x) => self.mutable_boxes.decrement(x),
58            UnsafeVal::List(x) => self.lists.decrement(x),
59            UnsafeVal::Struct(x) => self.structs.decrement(x),
60            UnsafeVal::ByteCodeFunction(x) => self.bytecodes.decrement(x),
61            UnsafeVal::Custom(x) => self.customs.decrement(x),
62            v => assert!(!is_garbage_collected(v)),
63        }
64    }
65}
66
67/// A private trait with some helper methods around reachable counters.
68trait ReachableStoreSealed {
69    type K: Copy + std::fmt::Debug + Hash + Eq;
70    fn as_mut_hashmap(&mut self) -> &mut HashMap<Self::K, ReferenceCounter>;
71
72    fn increment(&mut self, k: Self::K) {
73        match self.as_mut_hashmap().entry(k) {
74            Entry::Occupied(entry) => {
75                *entry.into_mut() += 1;
76            }
77            Entry::Vacant(entry) => {
78                entry.insert(1);
79            }
80        };
81    }
82    fn decrement(&mut self, k: Self::K) {
83        match self.as_mut_hashmap().entry(k) {
84            Entry::Occupied(mut entry) => {
85                match entry.get().saturating_sub(1) {
86                    0 => entry.remove(),
87                    x => entry.insert(x),
88                };
89            }
90            Entry::Vacant(_) => {
91                warn!("Tried to remove non-existant value {k:?} from keep reachable set. The object reference likely outlived its lifetime. Although not a not considered unsafe, this is likely a memory leak.");
92            }
93        };
94    }
95}
96
97impl<T: std::fmt::Debug> ReachableStoreSealed for HashMap<ValId<T>, ReferenceCounter> {
98    type K = ValId<T>;
99
100    fn as_mut_hashmap(&mut self) -> &mut HashMap<Self::K, ReferenceCounter> {
101        self
102    }
103}