Skip to main content

sim_lib_lang_javascript/
managed.rs

1use sim_lib_mutation::{
2    ArenaError, EdgeId, EphemeronMutationError, ManagedHandle, ManagedNode,
3    StrongEdgeMutationError, WeakEdgeMutationError,
4};
5
6/// Open JavaScript role label carried by the shared managed node.
7#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
8pub enum JavascriptManagedKind {
9    /// Ordinary cyclic object.
10    #[default]
11    Object,
12    /// Lexical environment or closure.
13    Environment,
14    /// Iterator or suspended frame.
15    Frame,
16    /// Callable identity whose edges include its captured environment.
17    Function,
18}
19
20/// Compatibility name for JavaScript's role-bearing shared managed node.
21pub type JavascriptManagedObject = ManagedNode<JavascriptManagedKind>;
22
23/// Compatibility name for the shared managed heap instantiated for JavaScript.
24pub type JavascriptHeap = sim_lib_gc_tracing::ManagedHeap<JavascriptManagedObject>;
25
26/// Compatibility name for the shared heap policy.
27pub type JavascriptHeapPolicy = sim_lib_gc_tracing::ManagedHeapPolicy;
28
29/// JavaScript-named graph operations over the shared heap and node.
30pub trait JavascriptHeapExt {
31    /// Adds a checked strong edge and returns its stable edge identity.
32    fn connect(
33        &mut self,
34        from: ManagedHandle,
35        to: ManagedHandle,
36    ) -> Result<EdgeId, JavascriptManagedMutationError>;
37
38    /// Adds a checked weak edge and returns its stable edge identity.
39    fn connect_weak(
40        &mut self,
41        from: ManagedHandle,
42        to: ManagedHandle,
43    ) -> Result<EdgeId, JavascriptManagedMutationError>;
44
45    /// Adds a checked ephemeron and returns its stable edge identity.
46    fn connect_ephemeron(
47        &mut self,
48        from: ManagedHandle,
49        key: ManagedHandle,
50        value: ManagedHandle,
51    ) -> Result<EdgeId, JavascriptManagedMutationError>;
52}
53
54/// A checked JavaScript managed-graph mutation failure.
55#[derive(Clone, Debug, Eq, PartialEq)]
56pub enum JavascriptManagedMutationError {
57    /// The owning allocation handle is stale.
58    Arena(ArenaError),
59    /// A strong edge could not be admitted.
60    Strong(StrongEdgeMutationError),
61    /// A weak edge could not be admitted.
62    Weak(WeakEdgeMutationError),
63    /// An ephemeron could not be admitted.
64    Ephemeron(EphemeronMutationError),
65}
66
67impl From<ArenaError> for JavascriptManagedMutationError {
68    fn from(value: ArenaError) -> Self {
69        Self::Arena(value)
70    }
71}
72
73impl JavascriptHeapExt for JavascriptHeap {
74    fn connect(
75        &mut self,
76        from: ManagedHandle,
77        to: ManagedHandle,
78    ) -> Result<EdgeId, JavascriptManagedMutationError> {
79        self.get_mut(from)?
80            .insert_strong(to.id())
81            .map_err(JavascriptManagedMutationError::Strong)
82    }
83
84    fn connect_weak(
85        &mut self,
86        from: ManagedHandle,
87        to: ManagedHandle,
88    ) -> Result<EdgeId, JavascriptManagedMutationError> {
89        self.get_mut(from)?
90            .insert_weak(to.id())
91            .map_err(JavascriptManagedMutationError::Weak)
92    }
93
94    fn connect_ephemeron(
95        &mut self,
96        from: ManagedHandle,
97        key: ManagedHandle,
98        value: ManagedHandle,
99    ) -> Result<EdgeId, JavascriptManagedMutationError> {
100        self.get_mut(from)?
101            .insert_ephemeron(key.id(), value.id())
102            .map_err(JavascriptManagedMutationError::Ephemeron)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use sim_lib_gc_tracing::CollectionLimits;
110
111    fn limits() -> CollectionLimits {
112        CollectionLimits {
113            objects: 8,
114            edges: 16,
115            stack: 8,
116            work: 64,
117            clears: 8,
118            finalizers: 0,
119        }
120    }
121
122    #[test]
123    fn shared_node_preserves_strong_cycle_behavior() {
124        let mut heap = JavascriptHeap::tracing(8, limits()).unwrap();
125        let first = heap
126            .allocate(JavascriptManagedObject::new(JavascriptManagedKind::Object))
127            .unwrap();
128        let second = heap
129            .allocate(JavascriptManagedObject::new(JavascriptManagedKind::Object))
130            .unwrap();
131        heap.connect(first, second).unwrap();
132        heap.connect(second, first).unwrap();
133        assert_eq!(
134            heap.collect().unwrap().unwrap().swept,
135            [first.id(), second.id()]
136        );
137    }
138
139    #[test]
140    fn shared_heap_preserves_explicit_retention_gap() {
141        let mut heap = JavascriptHeap::retaining(2).unwrap();
142        heap.allocate(JavascriptManagedObject::new(JavascriptManagedKind::Object))
143            .unwrap();
144        assert!(heap.cycle_leak_gap().unwrap().contains("cycles"));
145        assert_eq!(heap.collect().unwrap(), None);
146        assert_eq!(heap.live_len(), 1);
147    }
148
149    #[test]
150    fn javascript_weak_and_ephemeron_edges_clear_on_shared_collector() {
151        let mut heap = JavascriptHeap::tracing(8, limits()).unwrap();
152        let owner = heap
153            .allocate(JavascriptManagedObject::new(JavascriptManagedKind::Object))
154            .unwrap();
155        let weak_target = heap
156            .allocate(JavascriptManagedObject::new(JavascriptManagedKind::Object))
157            .unwrap();
158        let key = heap
159            .allocate(JavascriptManagedObject::new(JavascriptManagedKind::Object))
160            .unwrap();
161        let value = heap
162            .allocate(JavascriptManagedObject::new(JavascriptManagedKind::Object))
163            .unwrap();
164        let weak = heap.connect_weak(owner, weak_target).unwrap();
165        let ephemeron = heap.connect_ephemeron(owner, key, value).unwrap();
166        let root = heap.root(owner).unwrap();
167
168        let receipt = heap.collect().unwrap().unwrap();
169        assert_eq!(receipt.swept, [weak_target.id(), key.id(), value.id()]);
170        assert_eq!(receipt.cleared_weak, [(owner.id(), weak)]);
171        assert_eq!(receipt.cleared_ephemerons, [(owner.id(), ephemeron)]);
172        assert!(heap.get(owner).unwrap().edge_snapshot().is_empty());
173        heap.release_root(root).unwrap();
174    }
175}