Skip to main content

sim_lib_lang_javascript/
managed.rs

1use sim_lib_gc_tracing::{CollectionError, CollectionLimits, CollectionReceipt, collect};
2use sim_lib_mutation::{
3    EdgeId, EdgeVisitor, HardCappedRetainPolicy, ManagedArena, ManagedHandle, ManagedId,
4    ManagedObject,
5};
6
7/// Single-agent role of a managed JavaScript allocation.
8#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
9pub enum JavascriptManagedKind {
10    /// Ordinary cyclic object.
11    #[default]
12    Object,
13    /// Lexical environment or closure.
14    Environment,
15    /// Iterator or suspended frame.
16    Frame,
17    /// Callable identity whose edges include its captured environment.
18    Function,
19}
20/// Cyclic JavaScript payload stored exclusively by the shared arena.
21#[derive(Clone, Debug, Default)]
22pub struct JavascriptManagedObject {
23    /// Language role.
24    pub kind: JavascriptManagedKind,
25    /// Strong language edges.
26    pub edges: Vec<ManagedId>,
27}
28impl ManagedObject for JavascriptManagedObject {
29    fn trace_edges(&self, visitor: &mut dyn EdgeVisitor) {
30        for (i, target) in self.edges.iter().copied().enumerate() {
31            visitor.strong(EdgeId(i as u32), target);
32        }
33    }
34    fn clear_weak_edge(&mut self, _: EdgeId, _: ManagedId) -> bool {
35        false
36    }
37    fn clear_ephemeron_edge(&mut self, _: EdgeId, _: ManagedId, _: ManagedId) -> bool {
38        false
39    }
40}
41/// Explicit collection policy; collection is optional, never a load prerequisite.
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum JavascriptHeapPolicy {
44    /// Shared tracing collector.
45    Tracing(CollectionLimits),
46    /// Retain until teardown, with an inspectable cycle gap.
47    Retain,
48}
49/// JavaScript cyclic state composed from the shared arena and collector.
50pub struct JavascriptHeap {
51    arena: ManagedArena<JavascriptManagedObject>,
52    policy: JavascriptHeapPolicy,
53}
54impl JavascriptHeap {
55    /// Create the standard bounded tracing configuration.
56    pub fn standard(
57        cap: usize,
58        limits: CollectionLimits,
59    ) -> Result<Self, sim_lib_mutation::ArenaError> {
60        Ok(Self {
61            arena: ManagedArena::new(HardCappedRetainPolicy::new(cap)?),
62            policy: JavascriptHeapPolicy::Tracing(limits),
63        })
64    }
65    /// Create the explicit retain configuration.
66    pub fn retaining(cap: usize) -> Result<Self, sim_lib_mutation::ArenaError> {
67        Ok(Self {
68            arena: ManagedArena::new(HardCappedRetainPolicy::new(cap)?),
69            policy: JavascriptHeapPolicy::Retain,
70        })
71    }
72    /// Allocate cyclic-capable state in the one shared owner.
73    pub fn allocate(
74        &mut self,
75        value: JavascriptManagedObject,
76    ) -> Result<ManagedHandle, sim_lib_mutation::ArenaError> {
77        self.arena.allocate(value)
78    }
79    /// Add a strong edge.
80    pub fn connect(
81        &mut self,
82        from: ManagedHandle,
83        to: ManagedHandle,
84    ) -> Result<(), sim_lib_mutation::ArenaError> {
85        self.arena.get_mut(from)?.edges.push(to.id());
86        Ok(())
87    }
88    /// Number of live allocations.
89    pub fn live_len(&self) -> usize {
90        self.arena.len()
91    }
92    /// Selected policy.
93    pub const fn policy(&self) -> JavascriptHeapPolicy {
94        self.policy
95    }
96    /// Explicit retention gap when collection is disabled.
97    pub const fn cycle_leak_gap(&self) -> Option<&'static str> {
98        match self.policy {
99            JavascriptHeapPolicy::Retain => {
100                Some("unreachable JavaScript cycles are retained until teardown")
101            }
102            JavascriptHeapPolicy::Tracing(_) => None,
103        }
104    }
105    /// Run a synchronous safepoint.
106    pub fn collect(&mut self) -> Result<Option<CollectionReceipt>, CollectionError> {
107        match self.policy {
108            JavascriptHeapPolicy::Tracing(l) => collect(&mut self.arena, l).map(Some),
109            JavascriptHeapPolicy::Retain => Ok(None),
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    fn limits() -> CollectionLimits {
118        CollectionLimits {
119            objects: 8,
120            edges: 8,
121            stack: 8,
122            work: 32,
123            clears: 8,
124            finalizers: 0,
125        }
126    }
127    #[test]
128    fn shared_collector_reclaims_cycles() {
129        let mut h = JavascriptHeap::standard(8, limits()).unwrap();
130        let a = h.allocate(JavascriptManagedObject::default()).unwrap();
131        let b = h.allocate(JavascriptManagedObject::default()).unwrap();
132        h.connect(a, b).unwrap();
133        h.connect(b, a).unwrap();
134        assert_eq!(h.collect().unwrap().unwrap().swept.len(), 2);
135    }
136    #[test]
137    fn retention_gap_is_explicit() {
138        assert!(
139            JavascriptHeap::retaining(2)
140                .unwrap()
141                .cycle_leak_gap()
142                .unwrap()
143                .contains("cycles")
144        );
145    }
146}