sim_lib_lang_javascript/
managed.rs1use sim_lib_gc_tracing::{CollectionError, CollectionLimits, CollectionReceipt, collect};
2use sim_lib_mutation::{
3 EdgeId, EdgeVisitor, HardCappedRetainPolicy, ManagedArena, ManagedHandle, ManagedId,
4 ManagedObject,
5};
6
7#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
9pub enum JavascriptManagedKind {
10 #[default]
12 Object,
13 Environment,
15 Frame,
17 Function,
19}
20#[derive(Clone, Debug, Default)]
22pub struct JavascriptManagedObject {
23 pub kind: JavascriptManagedKind,
25 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum JavascriptHeapPolicy {
44 Tracing(CollectionLimits),
46 Retain,
48}
49pub struct JavascriptHeap {
51 arena: ManagedArena<JavascriptManagedObject>,
52 policy: JavascriptHeapPolicy,
53}
54impl JavascriptHeap {
55 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 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 pub fn allocate(
74 &mut self,
75 value: JavascriptManagedObject,
76 ) -> Result<ManagedHandle, sim_lib_mutation::ArenaError> {
77 self.arena.allocate(value)
78 }
79 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 pub fn live_len(&self) -> usize {
90 self.arena.len()
91 }
92 pub const fn policy(&self) -> JavascriptHeapPolicy {
94 self.policy
95 }
96 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 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}