1use crate::header::{
2 GcId, GcListKind, GcObjectHeader, GcObjectType, GcPhase, RefCountHeader, RefCountId,
3};
4use crate::list::{GcList, GcListIter};
5use crate::malloc::{MallocState, DEFAULT_GC_THRESHOLD};
6use crate::report::GcPhaseStats;
7use std::any::Any;
8
9#[path = "gc_stats.rs"]
12mod stats;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum MarkFunc {
18 Decref, ScanIncref, ScanIncref2, }
22
23pub trait GcObject: Any {
25 fn trace(&self, visit: &mut dyn FnMut(GcId));
26 fn on_free(&mut self, _rt: &mut GcRuntime) {}
27}
28
29struct GcObjectEntry {
30 header: GcObjectHeader,
31 object: Option<Box<dyn GcObject>>,
32}
33
34struct RefCountEntry {
35 header: RefCountHeader,
36 payload: Box<dyn FnOnce(&mut GcRuntime)>,
37}
38
39pub struct GcRuntime {
41 objects: Vec<Option<GcObjectEntry>>,
42 free_slots: Vec<GcId>,
43 ref_counts: Vec<Option<RefCountEntry>>,
44 ref_count_free_slots: Vec<RefCountId>,
45
46 gc_obj_list: GcList,
47 gc_zero_ref_count_list: GcList,
48 tmp_obj_list: GcList,
49 gc_phase: GcPhase,
50 malloc_state: MallocState,
51 malloc_gc_threshold: usize,
52}
53
54impl GcRuntime {
55 pub fn new() -> Self {
56 GcRuntime {
57 objects: Vec::new(),
58 free_slots: Vec::new(),
59 ref_counts: Vec::new(),
60 ref_count_free_slots: Vec::new(),
61 gc_obj_list: GcList::new(),
62 gc_zero_ref_count_list: GcList::new(),
63 tmp_obj_list: GcList::new(),
64 gc_phase: GcPhase::None,
65 malloc_state: MallocState::new(),
66 malloc_gc_threshold: DEFAULT_GC_THRESHOLD,
67 }
68 }
69
70 pub fn run_gc(&mut self) {
72 self.gc_decref();
73 self.gc_scan();
74 self.gc_free_cycles();
75 }
76
77 fn gc_decref(&mut self) {
79 assert!(
80 self.tmp_obj_list.is_empty(),
81 "temporary GC list must be empty before trial deletion"
82 );
83 let mut current = self.gc_obj_list.head;
84 while let Some(id) = current {
85 let next = self.header(id).list_next;
86 debug_assert_eq!(self.header(id).mark, 0);
87 self.mark_children(id, MarkFunc::Decref);
88 let header = self.header_mut(id);
89 header.mark = 1;
90 if header.ref_count == 0 {
91 self.list_move(GcListKind::GcObj, GcListKind::Tmp, id);
92 }
93 current = next;
94 }
95 }
96
97 fn gc_scan(&mut self) {
99 let mut current = self.gc_obj_list.head;
100 while let Some(id) = current {
101 let header = self.header_mut(id);
102 debug_assert!(header.ref_count > 0);
103 header.mark = 0;
104 self.mark_children(id, MarkFunc::ScanIncref);
105 current = self.header(id).list_next;
106 }
107
108 let mut current = self.tmp_obj_list.head;
109 while let Some(id) = current {
110 let next = self.header(id).list_next;
111 self.mark_children(id, MarkFunc::ScanIncref2);
112 current = next;
113 }
114 }
115
116 fn gc_free_cycles(&mut self) {
118 self.gc_phase = GcPhase::RemoveCycles;
119
120 loop {
121 let id = self.tmp_obj_list.head;
122 if id.is_none() {
123 break;
124 }
125 let id = id.unwrap();
126 let gc_obj_type = self.header(id).gc_obj_type;
127 match gc_obj_type {
128 GcObjectType::MonkeyObject | GcObjectType::FunctionBytecode => {
129 self.free_gc_object(id);
130 }
131 _ => {
132 self.list_move(GcListKind::Tmp, GcListKind::ZeroRef, id);
133 }
134 }
135 }
136
137 self.gc_phase = GcPhase::None;
138
139 while let Some(id) = self.gc_zero_ref_count_list.head {
140 let ty = self.header(id).gc_obj_type;
141 debug_assert!(
142 ty == GcObjectType::MonkeyObject || ty == GcObjectType::FunctionBytecode,
143 "unexpected deferred type: {:?}",
144 ty
145 );
146 self.list_remove_current(id);
147 self.free_slot(id);
148 }
149 }
150
151 pub fn malloc_state(&self) -> &MallocState {
152 &self.malloc_state
153 }
154
155 pub fn malloc_state_mut(&mut self) -> &mut MallocState {
156 &mut self.malloc_state
157 }
158
159 pub fn gc_threshold(&self) -> usize {
160 self.malloc_gc_threshold
161 }
162
163 pub fn set_gc_threshold(&mut self, threshold: usize) {
165 self.malloc_gc_threshold = threshold;
166 }
167
168 pub fn gc_phase(&self) -> GcPhase {
169 self.gc_phase
170 }
171
172 pub fn gc_object_count(&self) -> usize {
173 GcListIter {
174 rt: self,
175 current: self.gc_obj_list.head,
176 }
177 .count()
178 }
179
180 pub fn object_ids(&self) -> Vec<GcId> {
181 self.objects
182 .iter()
183 .enumerate()
184 .filter_map(|(id, entry)| entry.as_ref().map(|_| id))
185 .collect()
186 }
187
188 pub(crate) fn header(&self, id: GcId) -> &GcObjectHeader {
189 &self.objects[id].as_ref().expect("invalid GcId").header
190 }
191
192 pub(crate) fn header_mut(&mut self, id: GcId) -> &mut GcObjectHeader {
193 &mut self.objects[id].as_mut().expect("invalid GcId").header
194 }
195
196 fn list_mut(&mut self, kind: GcListKind) -> &mut GcList {
197 match kind {
198 GcListKind::GcObj => &mut self.gc_obj_list,
199 GcListKind::Tmp => &mut self.tmp_obj_list,
200 GcListKind::ZeroRef => &mut self.gc_zero_ref_count_list,
201 }
202 }
203
204 fn list_push_back(&mut self, kind: GcListKind, id: GcId) {
205 let tail = self.list_mut(kind).tail;
206
207 {
208 let header = self.header_mut(id);
209 debug_assert!(
210 header.list_kind.is_none(),
211 "object already belongs to a GC list: {:?}",
212 header.list_kind
213 );
214 header.list_kind = Some(kind);
215 header.list_prev = tail;
216 header.list_next = None;
217 }
218
219 if let Some(tail_id) = tail {
220 self.header_mut(tail_id).list_next = Some(id);
221 } else {
222 self.list_mut(kind).head = Some(id);
223 }
224 self.list_mut(kind).tail = Some(id);
225 }
226
227 fn list_remove(&mut self, kind: GcListKind, id: GcId) {
228 let (prev, next) = {
229 let header = self.header(id);
230 debug_assert_eq!(header.list_kind, Some(kind), "object is not on the expected GC list");
231 (header.list_prev, header.list_next)
232 };
233
234 match prev {
235 Some(p) => self.header_mut(p).list_next = next,
236 None => self.list_mut(kind).head = next,
237 }
238
239 match next {
240 Some(n) => self.header_mut(n).list_prev = prev,
241 None => self.list_mut(kind).tail = prev,
242 }
243
244 let header = self.header_mut(id);
245 header.list_kind = None;
246 header.list_prev = None;
247 header.list_next = None;
248 }
249
250 fn list_remove_current(&mut self, id: GcId) {
251 let kind = self
252 .header(id)
253 .list_kind
254 .expect("object is not on a GC list");
255 self.list_remove(kind, id);
256 }
257
258 fn list_move(&mut self, from: GcListKind, to: GcListKind, id: GcId) {
259 self.list_remove(from, id);
260 self.list_push_back(to, id);
261 }
262
263 fn list_move_current_to(&mut self, to: GcListKind, id: GcId) {
264 let from = self
265 .header(id)
266 .list_kind
267 .expect("object is not on a GC list");
268 self.list_move(from, to, id);
269 }
270
271 fn alloc_slot(&mut self, entry: GcObjectEntry) -> GcId {
272 let id = if let Some(id) = self.free_slots.pop() {
273 self.objects[id] = Some(entry);
274 id
275 } else {
276 let id = self.objects.len();
277 self.objects.push(Some(entry));
278 id
279 };
280 self.malloc_state
281 .record_alloc(std::mem::size_of::<GcObjectEntry>());
282 id
283 }
284
285 fn free_slot(&mut self, id: GcId) {
286 self.malloc_state
287 .record_free(std::mem::size_of::<GcObjectEntry>());
288 self.objects[id] = None;
289 self.free_slots.push(id);
290 }
291
292 pub fn add_gc_object(&mut self, object: Box<dyn GcObject>, gc_obj_type: GcObjectType) -> GcId {
295 let id = self.alloc_slot(GcObjectEntry {
296 header: GcObjectHeader::new(gc_obj_type, 1),
297 object: Some(object),
298 });
299 self.list_push_back(GcListKind::GcObj, id);
300 id
301 }
302
303 pub fn dup_gc(&mut self, id: GcId) -> GcId {
305 self.header_mut(id).ref_count += 1;
306 id
307 }
308
309 pub fn free_gc(&mut self, id: GcId) {
312 let ref_count = match self.objects.get_mut(id).and_then(|slot| slot.as_mut()) {
313 Some(entry) => {
314 entry.header.ref_count -= 1;
315 entry.header.ref_count
316 }
317 None => return,
318 };
319
320 if ref_count > 0 {
321 return;
322 }
323
324 if self.gc_phase != GcPhase::RemoveCycles {
325 self.list_move(GcListKind::GcObj, GcListKind::ZeroRef, id);
326 if self.gc_phase == GcPhase::None {
327 self.free_zero_refcount();
328 }
329 }
330 }
331
332 pub fn add_ref_counted<F>(&mut self, on_free: F) -> RefCountId
334 where
335 F: FnOnce(&mut GcRuntime) + 'static,
336 {
337 let entry = RefCountEntry {
338 header: RefCountHeader::new(1),
339 payload: Box::new(on_free),
340 };
341 let id = if let Some(id) = self.ref_count_free_slots.pop() {
342 self.ref_counts[id] = Some(entry);
343 id
344 } else {
345 let id = self.ref_counts.len();
346 self.ref_counts.push(Some(entry));
347 id
348 };
349 self.malloc_state
350 .record_alloc(std::mem::size_of::<RefCountEntry>());
351 id
352 }
353
354 pub fn dup_ref_counted(&mut self, id: RefCountId) -> RefCountId {
355 self.ref_counts[id]
356 .as_mut()
357 .expect("invalid RefCountId")
358 .header
359 .ref_count += 1;
360 id
361 }
362
363 pub fn free_ref_counted(&mut self, id: RefCountId) {
364 let ref_count = {
365 let entry = self.ref_counts[id].as_mut().expect("invalid RefCountId");
366 entry.header.ref_count -= 1;
367 entry.header.ref_count
368 };
369
370 if ref_count <= 0 {
371 let entry = self.ref_counts[id].take().expect("double free");
372 self.malloc_state
373 .record_free(std::mem::size_of::<RefCountEntry>());
374 (entry.payload)(self);
375 self.ref_count_free_slots.push(id);
376 }
377 }
378
379 pub fn mark_gc_header(&mut self, id: GcId, mark_func: MarkFunc) {
381 match mark_func {
382 MarkFunc::Decref => self.gc_decref_child(id),
383 MarkFunc::ScanIncref => self.gc_scan_incref_child(id),
384 MarkFunc::ScanIncref2 => self.gc_scan_incref_child2(id),
385 }
386 }
387
388 pub fn mark_children(&mut self, id: GcId, mark_func: MarkFunc) {
390 let object = self.objects[id]
393 .as_mut()
394 .expect("invalid GcId")
395 .object
396 .take()
397 .expect("object already finalized");
398 object.trace(&mut |child| {
399 self.mark_gc_header(child, mark_func);
400 });
401 self.objects[id]
402 .as_mut()
403 .expect("object freed while tracing")
404 .object = Some(object);
405 }
406
407 pub fn trigger_gc(&mut self, alloc_size: usize) {
409 let force_gc =
410 self.malloc_state.malloc_size.saturating_add(alloc_size) > self.malloc_gc_threshold;
411 if force_gc {
412 self.run_gc();
413 self.malloc_gc_threshold =
414 self.malloc_state.malloc_size + (self.malloc_state.malloc_size >> 1);
415 }
416 }
417
418 pub fn run_gc_with_stats(&mut self) -> GcPhaseStats {
420 self.run_gc_with_stats_bundle().phases
421 }
422
423 pub fn is_live_object(&self, id: GcId) -> bool {
426 self.objects
427 .get(id)
428 .and_then(|o| o.as_ref())
429 .is_some_and(|e| !e.header.free_mark)
430 }
431
432 pub fn ref_count(&self, id: GcId) -> i32 {
433 self.header(id).ref_count
434 }
435
436 pub fn object_exists(&self, id: GcId) -> bool {
437 self.objects.get(id).and_then(|o| o.as_ref()).is_some()
438 }
439
440 pub fn object_downcast<T: GcObject>(&self, id: GcId) -> Option<&T> {
441 let entry = self.objects.get(id)?.as_ref()?;
442 (entry.object.as_ref()?.as_ref() as &dyn Any).downcast_ref()
443 }
444
445 pub fn object_downcast_mut<T: GcObject>(&mut self, id: GcId) -> Option<&mut T> {
446 let entry = self.objects.get_mut(id)?.as_mut()?;
447 (entry.object.as_mut()?.as_mut() as &mut dyn Any).downcast_mut()
448 }
449
450 fn gc_decref_child(&mut self, id: GcId) {
453 let header = self.header_mut(id);
454 debug_assert!(header.ref_count > 0);
455 header.ref_count -= 1;
456 if header.ref_count == 0 && header.mark == 1 {
457 self.list_move(GcListKind::GcObj, GcListKind::Tmp, id);
458 }
459 }
460
461 fn gc_scan_incref_child(&mut self, id: GcId) {
464 let header = self.header_mut(id);
465 header.ref_count += 1;
466 if header.ref_count == 1 {
467 self.list_move(GcListKind::Tmp, GcListKind::GcObj, id);
468 self.header_mut(id).mark = 0;
469 }
470 }
471
472 fn gc_scan_incref_child2(&mut self, id: GcId) {
473 self.header_mut(id).ref_count += 1;
474 }
475
476 fn free_zero_refcount(&mut self) {
479 self.gc_phase = GcPhase::Decref;
480 loop {
481 let id = self.gc_zero_ref_count_list.head;
482 if id.is_none() {
483 break;
484 }
485 let id = id.unwrap();
486 debug_assert_eq!(self.header(id).ref_count, 0);
487 self.free_gc_object(id);
488 }
489 self.gc_phase = GcPhase::None;
490 }
491
492 fn free_gc_object(&mut self, id: GcId) {
493 match self.header(id).gc_obj_type {
494 GcObjectType::MonkeyObject | GcObjectType::FunctionBytecode => {
495 self.free_heap_object(id)
496 }
497 other => panic!("free_gc_object: unsupported type {:?}", other),
498 }
499 }
500
501 fn free_heap_object(&mut self, id: GcId) {
502 self.header_mut(id).free_mark = true;
503
504 let mut object = self.objects[id]
507 .as_mut()
508 .expect("invalid GcId")
509 .object
510 .take()
511 .expect("object already finalized");
512 object.trace(&mut |child| {
513 self.free_gc(child);
514 });
515 object.on_free(self);
516 drop(object);
517
518 let defer_free = self.gc_phase == GcPhase::RemoveCycles && self.header(id).ref_count != 0;
519 if !defer_free {
520 self.list_remove_current(id);
521 self.free_slot(id);
522 } else {
523 self.list_move_current_to(GcListKind::ZeroRef, id);
524 }
525 }
526}
527
528impl Default for GcRuntime {
529 fn default() -> Self {
530 Self::new()
531 }
532}