atmos/os_lib/js/
cycle_gc.rs1use alloc::rc::Rc;
29use alloc::vec::Vec;
30use core::cell::RefCell;
31
32use super::interp::Scope;
33use super::value::{Obj, ObjKind, Value};
34
35use core::sync::atomic::{AtomicUsize, Ordering};
36
37static MADE: AtomicUsize = AtomicUsize::new(0);
39static NEXT_AT: AtomicUsize = AtomicUsize::new(MIN_INTERVAL);
41const MIN_INTERVAL: usize = 100_000;
43
44pub fn should_collect() -> bool {
46 MADE.load(Ordering::Relaxed) >= NEXT_AT.load(Ordering::Relaxed)
47}
48
49pub fn maybe_collect() -> Option<GcStats> {
51 if !should_collect() {
52 return None;
53 }
54 let st = collect(true);
55 let live = (st.objs + st.scopes).saturating_sub(st.garbage_objs + st.garbage_scopes);
56 NEXT_AT.store(MIN_INTERVAL + live, Ordering::Relaxed);
57 Some(st)
58}
59
60pub const NO_SLOT: u32 = u32::MAX;
62
63struct Registry {
65 objs: Vec<usize>,
66 obj_free: Vec<u32>,
67 scopes: Vec<usize>,
68 scope_free: Vec<u32>,
69}
70
71static REGISTRY: spin::Mutex<Registry> = spin::Mutex::new(Registry {
72 objs: Vec::new(),
73 obj_free: Vec::new(),
74 scopes: Vec::new(),
75 scope_free: Vec::new(),
76});
77
78fn put(list: &mut Vec<usize>, free: &mut Vec<u32>, p: usize) -> u32 {
79 if let Some(i) = free.pop() {
80 if let Some(e) = list.get_mut(i as usize) {
81 *e = p;
82 return i;
83 }
84 }
85 list.push(p);
86 (list.len() - 1) as u32
87}
88
89pub fn track_obj(rc: Rc<RefCell<Obj>>) -> Rc<RefCell<Obj>> {
91 MADE.fetch_add(1, Ordering::Relaxed);
92 let p = Rc::as_ptr(&rc) as usize;
93 let slot = {
94 let mut r = REGISTRY.lock();
95 let Registry { objs, obj_free, .. } = &mut *r;
96 put(objs, obj_free, p)
97 };
98 if let Ok(mut b) = rc.try_borrow_mut() {
99 b.gc_slot = slot;
100 }
101 rc
102}
103
104pub fn track_scope(rc: Rc<RefCell<Scope>>) -> Rc<RefCell<Scope>> {
106 MADE.fetch_add(1, Ordering::Relaxed);
107 let p = Rc::as_ptr(&rc) as usize;
108 let slot = {
109 let mut r = REGISTRY.lock();
110 let Registry { scopes, scope_free, .. } = &mut *r;
111 put(scopes, scope_free, p)
112 };
113 if let Ok(mut b) = rc.try_borrow_mut() {
114 b.gc_slot = slot;
115 }
116 rc
117}
118
119pub fn untrack_obj(slot: u32) {
121 if slot == NO_SLOT {
122 return;
123 }
124 let mut r = REGISTRY.lock();
125 if let Some(e) = r.objs.get_mut(slot as usize) {
126 *e = 0;
127 r.obj_free.push(slot);
128 }
129}
130
131pub fn untrack_scope(slot: u32) {
133 if slot == NO_SLOT {
134 return;
135 }
136 let mut r = REGISTRY.lock();
137 if let Some(e) = r.scopes.get_mut(slot as usize) {
138 *e = 0;
139 r.scope_free.push(slot);
140 }
141}
142
143
144enum Node {
146 O(Rc<RefCell<Obj>>),
147 S(Rc<RefCell<Scope>>),
148}
149
150impl Node {
151 fn strong(&self) -> usize {
152 match self {
153 Node::O(r) => Rc::strong_count(r),
154 Node::S(r) => Rc::strong_count(r),
155 }
156 }
157}
158
159fn children(n: &Node, n_objs: usize, out: &mut Vec<usize>) -> bool {
163 let obj = |o: &Rc<RefCell<Obj>>, out: &mut Vec<usize>| {
164 if let Ok(b) = o.try_borrow() {
165 if b.gc_slot != NO_SLOT {
166 out.push(b.gc_slot as usize);
167 }
168 }
169 };
170 let scope = |s: &Rc<RefCell<Scope>>, out: &mut Vec<usize>| {
171 if let Ok(b) = s.try_borrow() {
172 if b.gc_slot != NO_SLOT {
173 out.push(n_objs + b.gc_slot as usize);
174 }
175 }
176 };
177 let val = |v: &Value, out: &mut Vec<usize>| {
178 if let Value::Object(o) = v {
179 obj(o, out);
180 }
181 };
182 match n {
183 Node::S(s) => {
184 let Ok(b) = s.try_borrow() else { return false };
185 if let Some(p) = &b.parent {
186 scope(p, out);
187 }
188 for v in b.vars.values() {
189 val(v, out);
190 }
191 true
192 }
193 Node::O(o) => {
194 let Ok(b) = o.try_borrow() else { return false };
195 if let Some(p) = &b.proto {
196 obj(p, out);
197 }
198 for v in b.props.values() {
199 val(v, out);
200 }
201 for (_, a) in b.accessors.iter() {
202 if let Some(g) = &a.get {
203 val(g, out);
204 }
205 if let Some(s) = &a.set {
206 val(s, out);
207 }
208 }
209 match &b.kind {
210 ObjKind::Array(items) | ObjKind::SetObj(items) => {
211 for v in items {
212 val(v, out);
213 }
214 }
215 ObjKind::MapObj(entries) => {
216 for (k, v) in entries {
217 val(k, out);
218 val(v, out);
219 }
220 }
221 ObjKind::Function(f) => {
222 scope(&f.closure, out);
223 if let Some(bt) = &f.bound_this {
224 val(bt, out);
225 }
226 }
227 ObjKind::Proxy { target, handler } => {
228 obj(target, out);
229 obj(handler, out);
230 }
231 ObjKind::Bound { target, bound_this, bound_args } => {
232 val(target, out);
233 val(bound_this, out);
234 for v in bound_args {
235 val(v, out);
236 }
237 }
238 _ => {}
240 }
241 true
242 }
243 }
244}
245
246#[derive(Debug, Default, Clone, Copy)]
248pub struct GcStats {
249 pub objs: usize,
250 pub scopes: usize,
251 pub garbage_objs: usize,
252 pub garbage_scopes: usize,
253}
254
255pub fn collect(free: bool) -> GcStats {
257 let (nodes, n_objs) = {
260 let r = REGISTRY.lock();
261 MADE.store(0, Ordering::Relaxed);
262 let n_objs = r.objs.len();
263 let mut nodes: Vec<Option<Node>> = Vec::with_capacity(n_objs + r.scopes.len());
264 for &p in &r.objs {
265 nodes.push((p != 0).then(|| {
266 Node::O(unsafe {
269 let p = p as *const RefCell<Obj>;
270 Rc::increment_strong_count(p);
271 Rc::from_raw(p)
272 })
273 }));
274 }
275 for &p in &r.scopes {
276 nodes.push((p != 0).then(|| {
277 Node::S(unsafe {
279 let p = p as *const RefCell<Scope>;
280 Rc::increment_strong_count(p);
281 Rc::from_raw(p)
282 })
283 }));
284 }
285 (nodes, n_objs)
286 };
287 let n = nodes.len();
288 let mut stats = GcStats::default();
289 for (i, x) in nodes.iter().enumerate() {
290 if x.is_some() {
291 if i < n_objs {
292 stats.objs += 1;
293 } else {
294 stats.scopes += 1;
295 }
296 }
297 }
298
299 const MARK: u32 = 1 << 31;
302 let mut st: Vec<u32> = alloc::vec![0; n];
303 let mut buf: Vec<usize> = Vec::new();
304 for (i, x) in nodes.iter().enumerate() {
305 let Some(x) = x else { continue };
306 buf.clear();
307 if !children(x, n_objs, &mut buf) {
308 st[i] |= MARK;
309 continue;
310 }
311 for &j in &buf {
312 if j < n && nodes[j].is_some() && (st[j] & !MARK) < (MARK - 1) {
313 st[j] += 1;
314 }
315 }
316 }
317
318 let mut stack: Vec<usize> = Vec::new();
320 for (i, x) in nodes.iter().enumerate() {
321 let Some(x) = x else { continue };
322 let internal = (st[i] & !MARK) as usize;
324 if st[i] & MARK != 0 || x.strong().saturating_sub(1) > internal {
325 st[i] |= MARK;
326 stack.push(i);
327 }
328 }
329 while let Some(i) = stack.pop() {
330 let Some(x) = &nodes[i] else { continue };
331 buf.clear();
332 if !children(x, n_objs, &mut buf) {
333 continue;
334 }
335 for &j in &buf {
336 if j < n && nodes[j].is_some() && st[j] & MARK == 0 {
337 st[j] |= MARK;
338 stack.push(j);
339 }
340 }
341 }
342 drop(stack);
343 drop(buf);
344
345 let mut taken: Vec<Value> = Vec::new();
347 let mut taken_scopes: Vec<Rc<RefCell<Scope>>> = Vec::new();
348 let mut taken_kinds: Vec<ObjKind> = Vec::new();
349 for (i, x) in nodes.iter().enumerate() {
350 let Some(x) = x else { continue };
351 if st[i] & MARK != 0 {
352 continue;
353 }
354 match x {
355 Node::O(o) => {
356 stats.garbage_objs += 1;
357 if free {
358 if let Ok(mut b) = o.try_borrow_mut() {
359 if let Some(p) = b.proto.take() {
360 taken.push(Value::Object(p));
361 }
362 let props = core::mem::take(&mut b.props);
363 taken.extend(props.into_iter().map(|(_, v)| v));
364 let acc = core::mem::take(&mut b.accessors);
365 for (_, a) in acc {
366 taken.extend(a.get);
367 taken.extend(a.set);
368 }
369 taken_kinds.push(core::mem::replace(&mut b.kind, ObjKind::Plain));
370 }
371 }
372 }
373 Node::S(s) => {
374 stats.garbage_scopes += 1;
375 if free {
376 if let Ok(mut b) = s.try_borrow_mut() {
377 if let Some(p) = b.parent.take() {
378 taken_scopes.push(p);
379 }
380 let vars = core::mem::take(&mut b.vars);
381 taken.extend(vars.into_values());
382 }
383 }
384 }
385 }
386 }
387 drop(st);
388 drop(taken);
390 drop(taken_scopes);
391 drop(taken_kinds);
392 drop(nodes);
395 stats
396}