1use std::cell::{Cell, RefCell};
41use std::collections::{HashMap, HashSet};
42use std::hash::{BuildHasherDefault, Hasher};
43use std::marker::PhantomData;
44use std::ptr::NonNull;
45
46#[derive(Default)]
47struct IdentityHasher {
48 value: u64,
49}
50
51impl Hasher for IdentityHasher {
52 #[inline]
53 fn write(&mut self, bytes: &[u8]) {
54 const PRIME: u64 = 0x0000_0100_0000_01b3;
55 for &byte in bytes {
56 self.value ^= u64::from(byte);
57 self.value = self.value.wrapping_mul(PRIME);
58 }
59 }
60
61 #[inline]
62 fn write_usize(&mut self, i: usize) {
63 self.value = i as u64;
64 }
65
66 #[inline]
67 fn write_u64(&mut self, i: u64) {
68 self.value = i;
69 }
70
71 #[inline]
72 fn finish(&self) -> u64 {
73 let mut x = self.value;
74 x ^= x >> 30;
75 x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9);
76 x ^= x >> 27;
77 x = x.wrapping_mul(0x94d0_49bb_1331_11eb);
78 x ^ (x >> 31)
79 }
80}
81
82type IdentityBuildHasher = BuildHasherDefault<IdentityHasher>;
83type IdentityHashSet = HashSet<usize, IdentityBuildHasher>;
84type IdentityHashMap<V> = HashMap<usize, V, IdentityBuildHasher>;
85
86thread_local! {
100 static CURRENT_HEAP_STACK: RefCell<Vec<NonNull<Heap>>> = const { RefCell::new(Vec::new()) };
101}
102
103pub struct HeapGuard {
107 _private: (),
110}
111
112impl HeapGuard {
113 pub fn push(heap: &Heap) -> Self {
122 let ptr = NonNull::from(heap);
123 CURRENT_HEAP_STACK.with(|stack| stack.borrow_mut().push(ptr));
124 HeapGuard { _private: () }
125 }
126}
127
128impl Drop for HeapGuard {
129 fn drop(&mut self) {
130 CURRENT_HEAP_STACK.with(|stack| {
131 let popped = stack.borrow_mut().pop();
132 debug_assert!(popped.is_some(), "HeapGuard::drop with empty stack");
133 });
134 }
135}
136
137pub fn with_current_heap<R>(f: impl for<'a> FnOnce(Option<&'a Heap>) -> R) -> R {
144 CURRENT_HEAP_STACK.with(|stack| {
145 let ptr = stack.borrow().last().copied();
146 let heap = ptr.map(|ptr| unsafe { &*ptr.as_ptr() });
150 f(heap)
151 })
152}
153
154#[derive(Copy, Clone, Debug)]
155pub struct HeapRef {
156 ptr: NonNull<Heap>,
157}
158
159impl HeapRef {
160 pub fn from_heap(heap: &Heap) -> Self {
161 HeapRef {
162 ptr: NonNull::from(heap),
163 }
164 }
165
166 pub fn contains_allocation(self, identity: usize, token: usize) -> bool {
167 unsafe { self.ptr.as_ref() }.contains_allocation(identity, token)
172 }
173}
174
175#[derive(Copy, Clone, PartialEq, Eq, Debug)]
177pub enum Color {
178 White0,
182 White1,
184 Gray,
186 Black,
188}
189
190impl Color {
191 pub fn is_white(self) -> bool {
192 matches!(self, Color::White0 | Color::White1)
193 }
194
195 fn other_white(self) -> Self {
196 match self {
197 Color::White0 => Color::White1,
198 Color::White1 => Color::White0,
199 Color::Gray | Color::Black => self,
200 }
201 }
202}
203
204#[derive(Copy, Clone, PartialEq, Eq, Debug)]
208pub enum GcAge {
209 New,
210 Survival,
211 Old0,
212 Old1,
213 Old,
214 Touched1,
215 Touched2,
216}
217
218impl GcAge {
219 pub fn is_old(self) -> bool {
220 !matches!(self, GcAge::New | GcAge::Survival)
221 }
222
223 fn next_after_minor(self) -> Self {
224 match self {
225 GcAge::New => GcAge::Survival,
226 GcAge::Survival | GcAge::Old0 => GcAge::Old1,
227 GcAge::Old1 | GcAge::Old | GcAge::Touched2 => GcAge::Old,
228 GcAge::Touched1 => GcAge::Touched2,
229 }
230 }
231}
232
233pub trait FinalizerEntry: Clone {
236 fn identity(&self) -> usize;
237 fn heap_ptr(&self) -> Option<NonNull<GcBox<dyn Trace>>> {
238 None
239 }
240 fn age(&self) -> GcAge;
241 fn is_finalized(&self) -> bool;
242 fn set_finalized(&self, finalized: bool);
243}
244
245pub trait WeakEntry: Clone {
247 type Strong: Clone;
248
249 fn identity(&self) -> usize;
250 fn list_kind(&self) -> WeakListKind;
251 fn upgrade(&self) -> Option<Self::Strong>;
252}
253
254#[derive(Copy, Clone, Debug, PartialEq, Eq)]
255pub enum WeakListKind {
256 WeakValues,
257 Ephemeron,
258 AllWeak,
259}
260
261#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
262pub struct WeakRegistryStats {
263 pub tracked: usize,
264 pub snapshot_live: usize,
265 pub snapshot_dead: usize,
266 pub retained: usize,
267 pub weak_values: usize,
268 pub ephemeron: usize,
269 pub all_weak: usize,
270}
271
272#[derive(Clone, Debug)]
273pub struct WeakRegistry<T: WeakEntry> {
274 weak_values: Vec<T>,
275 ephemeron: Vec<T>,
276 all_weak: Vec<T>,
277 last_stats: WeakRegistryStats,
278}
279
280#[derive(Clone, Debug, PartialEq, Eq)]
281pub struct WeakRegistrySnapshot<T> {
282 pub weak_values: Vec<T>,
283 pub ephemeron: Vec<T>,
284 pub all_weak: Vec<T>,
285}
286
287impl<T> Default for WeakRegistrySnapshot<T> {
288 fn default() -> Self {
289 Self {
290 weak_values: Vec::new(),
291 ephemeron: Vec::new(),
292 all_weak: Vec::new(),
293 }
294 }
295}
296
297impl<T> WeakRegistrySnapshot<T> {
298 pub fn len(&self) -> usize {
299 self.weak_values
300 .len()
301 .saturating_add(self.ephemeron.len())
302 .saturating_add(self.all_weak.len())
303 }
304
305 pub fn into_flat(self) -> Vec<T> {
306 self.weak_values
307 .into_iter()
308 .chain(self.ephemeron)
309 .chain(self.all_weak)
310 .collect()
311 }
312}
313
314impl<T: WeakEntry> Default for WeakRegistry<T> {
315 fn default() -> Self {
316 Self {
317 weak_values: Vec::new(),
318 ephemeron: Vec::new(),
319 all_weak: Vec::new(),
320 last_stats: WeakRegistryStats::default(),
321 }
322 }
323}
324
325impl<T: WeakEntry> WeakRegistry<T> {
326 pub fn len(&self) -> usize {
327 self.weak_values
328 .len()
329 .saturating_add(self.ephemeron.len())
330 .saturating_add(self.all_weak.len())
331 }
332
333 pub fn stats(&self) -> WeakRegistryStats {
334 self.last_stats
335 }
336
337 fn list_mut(&mut self, kind: WeakListKind) -> &mut Vec<T> {
338 match kind {
339 WeakListKind::WeakValues => &mut self.weak_values,
340 WeakListKind::Ephemeron => &mut self.ephemeron,
341 WeakListKind::AllWeak => &mut self.all_weak,
342 }
343 }
344
345 pub fn remove_identity(&mut self, id: usize) {
346 self.weak_values.retain(|entry| entry.identity() != id);
347 self.ephemeron.retain(|entry| entry.identity() != id);
348 self.all_weak.retain(|entry| entry.identity() != id);
349 self.last_stats.tracked = self.len();
350 self.last_stats.retained = self.len();
351 self.update_cohort_stats();
352 }
353
354 fn update_cohort_stats(&mut self) {
355 self.last_stats.weak_values = self.weak_values.len();
356 self.last_stats.ephemeron = self.ephemeron.len();
357 self.last_stats.all_weak = self.all_weak.len();
358 }
359
360 pub fn push_unique(&mut self, entry: T) {
361 let id = entry.identity();
362 self.remove_identity(id);
363 self.list_mut(entry.list_kind()).push(entry);
364 self.last_stats.tracked = self.len();
365 self.last_stats.retained = self.len();
366 self.update_cohort_stats();
367 }
368
369 pub fn live_snapshot_by_kind(&mut self) -> WeakRegistrySnapshot<T::Strong> {
370 let tracked_before = self.len();
371 let weak_values_capacity = self.weak_values.len();
372 let ephemeron_capacity = self.ephemeron.len();
373 let all_weak_capacity = self.all_weak.len();
374 let mut seen = std::collections::HashSet::<usize>::with_capacity(tracked_before);
375 let mut live = WeakRegistrySnapshot {
376 weak_values: Vec::with_capacity(weak_values_capacity),
377 ephemeron: Vec::with_capacity(ephemeron_capacity),
378 all_weak: Vec::with_capacity(all_weak_capacity),
379 };
380 let mut dead = 0usize;
381
382 let entries = std::mem::take(&mut self.weak_values)
383 .into_iter()
384 .chain(std::mem::take(&mut self.ephemeron))
385 .chain(std::mem::take(&mut self.all_weak));
386 for entry in entries {
387 if !seen.insert(entry.identity()) {
388 continue;
389 }
390 match entry.upgrade() {
391 Some(strong) => {
392 match entry.list_kind() {
393 WeakListKind::WeakValues => live.weak_values.push(strong),
394 WeakListKind::Ephemeron => live.ephemeron.push(strong),
395 WeakListKind::AllWeak => live.all_weak.push(strong),
396 }
397 self.list_mut(entry.list_kind()).push(entry);
398 }
399 None => dead += 1,
400 }
401 }
402
403 self.last_stats = WeakRegistryStats {
404 tracked: tracked_before,
405 snapshot_live: live.len(),
406 snapshot_dead: dead,
407 retained: self.len(),
408 weak_values: self.weak_values.len(),
409 ephemeron: self.ephemeron.len(),
410 all_weak: self.all_weak.len(),
411 };
412 live
413 }
414
415 pub fn live_snapshot(&mut self) -> Vec<T::Strong> {
416 self.live_snapshot_by_kind().into_flat()
417 }
418
419 pub fn retain_identities(&mut self, ids: &std::collections::HashSet<usize>) {
420 self.weak_values
421 .retain(|entry| ids.contains(&entry.identity()));
422 self.ephemeron
423 .retain(|entry| ids.contains(&entry.identity()));
424 self.all_weak
425 .retain(|entry| ids.contains(&entry.identity()));
426 self.last_stats.retained = self.len();
427 self.last_stats.tracked = self.len();
428 self.update_cohort_stats();
429 }
430}
431
432#[derive(Clone, Debug)]
433pub struct FinalizerRegistry<T: FinalizerEntry> {
434 pending: Vec<T>,
435 to_be_finalized: Vec<T>,
436 pending_reallyold: usize,
437 pending_old1: usize,
438 pending_survival: usize,
439}
440
441#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
442pub struct FinalizerRegistryStats {
443 pub pending_young: usize,
444 pub pending_old: usize,
445 pub to_be_finalized_young: usize,
446 pub to_be_finalized_old: usize,
447 pub finobj_new: usize,
448 pub finobj_survival: usize,
449 pub finobj_old1: usize,
450 pub finobj_reallyold: usize,
451 pub finobj_minor_scan: usize,
452}
453
454impl<T: FinalizerEntry> Default for FinalizerRegistry<T> {
455 fn default() -> Self {
456 Self {
457 pending: Vec::new(),
458 to_be_finalized: Vec::new(),
459 pending_reallyold: 0,
460 pending_old1: 0,
461 pending_survival: 0,
462 }
463 }
464}
465
466impl<T: FinalizerEntry> FinalizerRegistry<T> {
467 fn pending_new_len(&self) -> usize {
468 self.pending.len().saturating_sub(
469 self.pending_reallyold
470 .saturating_add(self.pending_old1)
471 .saturating_add(self.pending_survival),
472 )
473 }
474
475 fn minor_scan_start(&self) -> usize {
476 self.pending_reallyold.saturating_add(self.pending_old1)
477 }
478
479 fn debug_assert_pending_cohorts(&self) {
480 debug_assert!(
481 self.pending_reallyold
482 .saturating_add(self.pending_old1)
483 .saturating_add(self.pending_survival)
484 <= self.pending.len()
485 );
486 }
487
488 pub fn pending(&self) -> &[T] {
489 &self.pending
490 }
491
492 pub fn pending_snapshot(&self) -> Vec<T> {
493 self.pending.clone()
494 }
495
496 pub fn pending_minor_snapshot(&self) -> Vec<T> {
497 self.pending[self.minor_scan_start().min(self.pending.len())..].to_vec()
498 }
499
500 pub fn to_be_finalized(&self) -> &[T] {
501 &self.to_be_finalized
502 }
503
504 pub fn pending_len(&self) -> usize {
505 self.pending.len()
506 }
507
508 pub fn to_be_finalized_len(&self) -> usize {
509 self.to_be_finalized.len()
510 }
511
512 pub fn has_to_be_finalized(&self) -> bool {
513 !self.to_be_finalized.is_empty()
514 }
515
516 pub fn stats(&self) -> FinalizerRegistryStats {
517 fn count_by_age<T: FinalizerEntry>(objects: &[T]) -> (usize, usize) {
518 objects
519 .iter()
520 .fold((0usize, 0usize), |(young, old), object| {
521 if object.age().is_old() {
522 (young, old + 1)
523 } else {
524 (young + 1, old)
525 }
526 })
527 }
528 let (pending_young, pending_old) = count_by_age(&self.pending);
529 let (to_be_finalized_young, to_be_finalized_old) = count_by_age(&self.to_be_finalized);
530 FinalizerRegistryStats {
531 pending_young,
532 pending_old,
533 to_be_finalized_young,
534 to_be_finalized_old,
535 finobj_new: self.pending_new_len(),
536 finobj_survival: self.pending_survival,
537 finobj_old1: self.pending_old1,
538 finobj_reallyold: self.pending_reallyold,
539 finobj_minor_scan: self.pending.len().saturating_sub(self.minor_scan_start()),
540 }
541 }
542
543 pub fn push_pending_unique(&mut self, object: T) -> bool {
544 if object.is_finalized() {
545 return false;
546 }
547 let id = object.identity();
548 if !self.pending.iter().any(|o| o.identity() == id) {
549 object.set_finalized(true);
550 self.pending.push(object);
551 self.debug_assert_pending_cohorts();
552 true
553 } else {
554 false
555 }
556 }
557
558 pub fn take_pending(&mut self) -> Vec<T> {
559 self.pending_reallyold = 0;
560 self.pending_old1 = 0;
561 self.pending_survival = 0;
562 std::mem::take(&mut self.pending)
563 }
564
565 fn retain_pending_not_in(&mut self, ids: &std::collections::HashSet<usize>) {
566 if ids.is_empty() {
567 return;
568 }
569 let original_reallyold = self.pending_reallyold;
570 let original_old1 = self.pending_old1;
571 let original_survival = self.pending_survival;
572 let mut retained_reallyold = original_reallyold;
573 let mut retained_old1 = original_old1;
574 let mut retained_survival = original_survival;
575 let mut retained = Vec::with_capacity(self.pending.len());
576 for (index, object) in std::mem::take(&mut self.pending).into_iter().enumerate() {
577 if ids.contains(&object.identity()) {
578 if index < original_reallyold {
579 retained_reallyold -= 1;
580 } else if index < original_reallyold + original_old1 {
581 retained_old1 -= 1;
582 } else if index < original_reallyold + original_old1 + original_survival {
583 retained_survival -= 1;
584 }
585 } else {
586 retained.push(object);
587 }
588 }
589 self.pending = retained;
590 self.pending_reallyold = retained_reallyold;
591 self.pending_old1 = retained_old1;
592 self.pending_survival = retained_survival;
593 self.debug_assert_pending_cohorts();
594 }
595
596 pub fn push_to_be_finalized(&mut self, object: T) {
597 object.set_finalized(true);
598 self.to_be_finalized.push(object);
599 }
600
601 fn extend_to_be_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
602 let drain_order: Vec<T> = objects.into_iter().rev().collect();
603 for object in drain_order.iter().cloned() {
604 self.push_to_be_finalized(object);
605 }
606 drain_order
607 }
608
609 pub fn promote_pending_to_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
610 if objects.is_empty() {
611 return Vec::new();
612 }
613 let mut ids: std::collections::HashSet<usize> =
614 std::collections::HashSet::with_capacity(objects.len());
615 ids.extend(objects.iter().map(|object| object.identity()));
616 self.retain_pending_not_in(&ids);
617 self.extend_to_be_finalized(objects)
618 }
619
620 pub fn promote_all_pending_to_old(&mut self) {
621 self.pending_reallyold = self.pending.len();
622 self.pending_old1 = 0;
623 self.pending_survival = 0;
624 }
625
626 pub fn reset_generation_boundaries(&mut self) {
627 self.pending_reallyold = 0;
628 self.pending_old1 = 0;
629 self.pending_survival = 0;
630 }
631
632 pub fn finish_minor_collection(&mut self) {
633 let new = self.pending_new_len();
634 self.pending_reallyold = self.pending_reallyold.saturating_add(self.pending_old1);
635 self.pending_old1 = self.pending_survival;
636 self.pending_survival = new;
637 self.debug_assert_pending_cohorts();
638 }
639
640 pub fn pop_to_be_finalized(&mut self) -> Option<T> {
641 let object = if self.to_be_finalized.is_empty() {
642 None
643 } else {
644 Some(self.to_be_finalized.remove(0))
645 };
646 if let Some(ref object) = object {
647 object.set_finalized(false);
648 }
649 object
650 }
651}
652
653#[repr(C)]
655pub struct GcHeader {
656 color: Cell<Color>,
657 age: Cell<GcAge>,
658 finalized: Cell<bool>,
662 collected: Cell<bool>,
671 next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
673 gray_next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
675 gray_listed: Cell<bool>,
677 size: Cell<usize>,
683 type_name: &'static str,
686}
687
688impl GcHeader {
689 fn new_white(size: usize, color: Color, type_name: &'static str) -> Self {
690 Self {
691 color: Cell::new(color),
692 age: Cell::new(GcAge::New),
693 finalized: Cell::new(false),
694 collected: Cell::new(false),
695 next: Cell::new(None),
696 gray_next: Cell::new(None),
697 gray_listed: Cell::new(false),
698 size: Cell::new(size),
699 type_name,
700 }
701 }
702}
703
704#[repr(C)]
706pub struct GcBox<T: ?Sized> {
707 header: GcHeader,
708 value: T,
709}
710
711impl<T: ?Sized> GcBox<T> {
712 pub fn header(&self) -> &GcHeader {
713 &self.header
714 }
715 pub fn value(&self) -> &T {
716 &self.value
717 }
718}
719
720pub struct Gc<T: ?Sized> {
722 ptr: NonNull<GcBox<T>>,
723 _marker: PhantomData<T>,
725}
726
727impl<T: ?Sized> Copy for Gc<T> {}
731impl<T: ?Sized> Clone for Gc<T> {
732 fn clone(&self) -> Self {
733 *self
734 }
735}
736
737impl<T: ?Sized> PartialEq for Gc<T> {
738 fn eq(&self, other: &Self) -> bool {
739 std::ptr::addr_eq(self.ptr.as_ptr(), other.ptr.as_ptr())
740 }
741}
742impl<T: ?Sized> Eq for Gc<T> {}
743
744impl<T: ?Sized> std::hash::Hash for Gc<T> {
745 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
746 self.ptr.as_ptr().hash(state)
747 }
748}
749
750impl<T: ?Sized> std::fmt::Debug for Gc<T> {
751 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
752 write!(f, "Gc({:p})", self.ptr.as_ptr())
753 }
754}
755
756impl<T: Trace + 'static> Gc<T> {
757 pub fn new_uncollected(value: T) -> Self {
763 let size = std::mem::size_of::<T>();
764 let boxed = Box::new(GcBox {
765 header: GcHeader::new_white(size, Color::White0, std::any::type_name::<T>()),
766 value,
767 });
768 Gc {
769 ptr: NonNull::new(Box::into_raw(boxed)).expect("Box::into_raw is non-null"),
770 _marker: PhantomData,
771 }
772 }
773
774 pub fn as_trace_ptr(self) -> NonNull<GcBox<dyn Trace>> {
776 self.ptr
777 }
778}
779
780impl<T: ?Sized> Gc<T> {
781 pub fn ptr_eq(a: Self, b: Self) -> bool {
783 std::ptr::addr_eq(a.ptr.as_ptr(), b.ptr.as_ptr())
784 }
785
786 pub fn identity(self) -> usize {
789 self.ptr.as_ptr() as *const () as usize
790 }
791
792 fn as_box(&self) -> &GcBox<T> {
796 unsafe { self.ptr.as_ref() }
802 }
803
804 fn header(&self) -> &GcHeader {
805 &self.as_box().header
806 }
807
808 pub fn color(self) -> Color {
809 self.header().color.get()
810 }
811
812 pub fn set_color(self, color: Color) {
813 self.header().color.set(color);
814 }
815
816 pub fn age(self) -> GcAge {
817 self.header().age.get()
818 }
819
820 pub fn set_age(self, age: GcAge) {
821 self.header().age.set(age);
822 }
823
824 pub fn is_finalized(self) -> bool {
825 self.header().finalized.get()
826 }
827
828 pub fn set_finalized(self, finalized: bool) {
829 self.header().finalized.set(finalized);
830 }
831
832 pub fn account_buffer(&self, heap: &Heap, delta: isize) {
842 if delta == 0 {
843 return;
844 }
845 let header = self.header();
846 if !header.collected.get() {
847 return;
848 }
849 if delta >= 0 {
850 header
851 .size
852 .set(header.size.get().saturating_add(delta as usize));
853 } else {
854 header
855 .size
856 .set(header.size.get().saturating_sub((-delta) as usize));
857 }
858 heap.adjust_bytes(delta);
859 }
860}
861
862impl<T: ?Sized> std::ops::Deref for Gc<T> {
863 type Target = T;
864 fn deref(&self) -> &T {
865 &self.as_box().value
866 }
867}
868
869impl<T: ?Sized> AsRef<T> for Gc<T> {
870 fn as_ref(&self) -> &T {
871 &self.as_box().value
872 }
873}
874
875pub trait Trace {
892 fn trace(&self, m: &mut Marker);
893}
894
895impl<T: Trace> Trace for Vec<T> {
897 fn trace(&self, m: &mut Marker) {
898 for item in self.iter() {
899 item.trace(m);
900 }
901 }
902}
903
904impl<T: Trace> Trace for Option<T> {
905 fn trace(&self, m: &mut Marker) {
906 if let Some(v) = self {
907 v.trace(m);
908 }
909 }
910}
911
912impl<T: Trace + ?Sized> Trace for Box<T> {
913 fn trace(&self, m: &mut Marker) {
914 (**self).trace(m);
915 }
916}
917
918impl<T: Trace + ?Sized> Trace for std::rc::Rc<T> {
919 fn trace(&self, m: &mut Marker) {
920 (**self).trace(m);
921 }
922}
923
924impl<T: Trace> Trace for RefCell<T> {
925 fn trace(&self, m: &mut Marker) {
926 self.borrow().trace(m);
927 }
928}
929
930impl<T: Trace + 'static> Trace for Gc<T> {
932 fn trace(&self, m: &mut Marker) {
933 m.mark(*self);
934 }
935}
936
937macro_rules! trace_noop {
939 ($($t:ty),*) => {
940 $(impl Trace for $t {
941 fn trace(&self, _m: &mut Marker) {}
942 })*
943 };
944}
945trace_noop!(
946 bool, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, char, String,
947 str
948);
949
950impl<T> Trace for std::marker::PhantomData<T> {
951 fn trace(&self, _m: &mut Marker) {}
952}
953
954#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
959pub struct MarkerStats {
960 pub marked: usize,
961 pub marked_young: usize,
962 pub marked_old: usize,
963 pub traced: usize,
964 pub traced_young: usize,
965 pub traced_old: usize,
966}
967
968#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
970pub struct SweepStats {
971 pub visited: usize,
972 pub visited_young: usize,
973 pub visited_old: usize,
974 pub revisit: usize,
975 pub freed: usize,
976 pub freed_bytes: usize,
977}
978
979impl SweepStats {
980 fn record_visit(&mut self, age: GcAge) {
981 self.visited += 1;
982 if age.is_old() {
983 self.visited_old += 1;
984 } else {
985 self.visited_young += 1;
986 }
987 }
988
989 fn record_free(&mut self, bytes: usize) {
990 self.freed += 1;
991 self.freed_bytes += bytes;
992 }
993
994 fn add(&mut self, other: Self) {
995 self.visited += other.visited;
996 self.visited_young += other.visited_young;
997 self.visited_old += other.visited_old;
998 self.revisit += other.revisit;
999 self.freed += other.freed;
1000 self.freed_bytes += other.freed_bytes;
1001 }
1002}
1003
1004struct OldRevisitTracker {
1005 old_revisit_ids: Vec<usize>,
1006 processed_ids: Vec<usize>,
1007}
1008
1009impl OldRevisitTracker {
1010 fn new(old_revisit: &[NonNull<GcBox<dyn Trace>>]) -> Option<Self> {
1011 if old_revisit.is_empty() {
1012 return None;
1013 }
1014 let mut old_revisit_ids: Vec<usize> = old_revisit
1015 .iter()
1016 .map(|ptr| ptr.as_ptr() as *const () as usize)
1017 .collect();
1018 old_revisit_ids.sort_unstable();
1019 old_revisit_ids.dedup();
1020 Some(Self {
1021 old_revisit_ids,
1022 processed_ids: Vec::new(),
1023 })
1024 }
1025
1026 #[inline(always)]
1027 fn record_processed(&mut self, id: usize) {
1028 if self.old_revisit_ids.binary_search(&id).is_ok() {
1029 self.processed_ids.push(id);
1030 }
1031 }
1032
1033 fn finish(&mut self) {
1034 self.processed_ids.sort_unstable();
1035 self.processed_ids.dedup();
1036 }
1037
1038 #[inline(always)]
1039 fn was_processed(&self, id: usize) -> bool {
1040 self.processed_ids.binary_search(&id).is_ok()
1041 }
1042}
1043
1044#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1046pub struct AllGcCohortStats {
1047 pub new: usize,
1048 pub survival: usize,
1049 pub old1: usize,
1050 pub old: usize,
1051}
1052
1053#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1054enum MarkerMode {
1055 Full,
1056 Minor,
1057}
1058
1059pub struct Marker {
1061 gray_queue: Vec<NonNull<GcBox<dyn Trace>>>,
1062 visited: IdentityHashSet,
1063 stats: MarkerStats,
1064 mode: MarkerMode,
1065}
1066
1067impl Marker {
1068 fn new_with_capacity(mode: MarkerMode, capacity: usize) -> Self {
1069 Self {
1070 gray_queue: Vec::with_capacity(256),
1071 visited: IdentityHashSet::with_capacity_and_hasher(
1072 capacity,
1073 IdentityBuildHasher::default(),
1074 ),
1075 stats: MarkerStats::default(),
1076 mode,
1077 }
1078 }
1079
1080 fn new_reserving(capacity: usize) -> Self {
1081 Self::new_with_capacity(MarkerMode::Full, capacity)
1082 }
1083
1084 fn new_minor_reserving(capacity: usize) -> Self {
1085 Self::new_with_capacity(MarkerMode::Minor, capacity)
1086 }
1087
1088 fn should_trace_age(&self, age: GcAge) -> bool {
1089 match self.mode {
1090 MarkerMode::Full => true,
1091 MarkerMode::Minor => !matches!(age, GcAge::Old),
1092 }
1093 }
1094
1095 pub fn mark<T: Trace + 'static>(&mut self, gc: Gc<T>) {
1108 let ptr: NonNull<GcBox<dyn Trace>> = gc.ptr;
1109 self.mark_box(ptr, gc.header(), gc.identity());
1110 }
1111
1112 fn mark_box(&mut self, ptr: NonNull<GcBox<dyn Trace>>, header: &GcHeader, id: usize) {
1113 if self.visited.insert(id) {
1114 let age = header.age.get();
1115 self.stats.marked += 1;
1116 if age.is_old() {
1117 self.stats.marked_old += 1;
1118 } else {
1119 self.stats.marked_young += 1;
1120 }
1121 if self.should_trace_age(age) {
1122 header.color.set(Color::Gray);
1123 self.gray_queue.push(ptr);
1124 }
1125 }
1126 }
1127
1128 pub fn try_visit(&mut self, addr: usize) -> bool {
1133 self.visited.insert(addr)
1134 }
1135
1136 pub fn is_visited(&self, id: usize) -> bool {
1141 self.visited.contains(&id)
1142 }
1143
1144 pub fn is_marked_or_old<T: Trace + 'static>(&self, gc: Gc<T>) -> bool {
1147 self.is_visited(gc.identity())
1148 || (matches!(self.mode, MarkerMode::Minor) && gc.age().is_old())
1149 }
1150
1151 pub fn visited_count(&self) -> usize {
1155 self.visited.len()
1156 }
1157
1158 pub fn stats(&self) -> MarkerStats {
1160 self.stats
1161 }
1162
1163 pub fn drain_gray_queue(&mut self) {
1172 while let Some(gray_ptr) = self.gray_queue.pop() {
1173 unsafe {
1174 let bx = gray_ptr.as_ref();
1175 self.stats.traced += 1;
1176 if bx.header.age.get().is_old() {
1177 self.stats.traced_old += 1;
1178 } else {
1179 self.stats.traced_young += 1;
1180 }
1181 bx.header.color.set(Color::Black);
1182 bx.value.trace(self);
1183 }
1184 }
1185 }
1186}
1187
1188#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1207pub enum GcState {
1208 Pause,
1209 Propagate,
1210 EnterAtomic,
1211 Atomic,
1212 SweepAllGc,
1213 SweepFinObj,
1214 SweepToBeFnz,
1215 SweepEnd,
1216 CallFin,
1217}
1218
1219impl GcState {
1220 pub fn is_pause(self) -> bool {
1221 matches!(self, GcState::Pause)
1222 }
1223 pub fn is_propagate(self) -> bool {
1224 matches!(self, GcState::Propagate)
1225 }
1226 pub fn is_invariant(self) -> bool {
1227 matches!(
1228 self,
1229 GcState::Propagate | GcState::EnterAtomic | GcState::Atomic
1230 )
1231 }
1232 pub fn is_sweep(self) -> bool {
1233 matches!(
1234 self,
1235 GcState::SweepAllGc | GcState::SweepFinObj | GcState::SweepToBeFnz | GcState::SweepEnd
1236 )
1237 }
1238}
1239
1240#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1242pub enum StepOutcome {
1243 Paused,
1245 InProgress,
1248 SkippedStopped,
1251}
1252
1253#[derive(Copy, Clone, Debug)]
1261pub struct StepBudget {
1262 pub remaining_work: isize,
1263 pub max_credit: isize,
1264}
1265
1266impl StepBudget {
1267 pub fn from_work(work: isize) -> Self {
1269 Self {
1270 remaining_work: work.max(1),
1271 max_credit: work.max(1),
1272 }
1273 }
1274}
1275
1276const GC_MIN_THRESHOLD: usize = 1024 * 1024;
1291
1292pub struct Heap {
1293 head: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1296 finobj: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1298 tobefnz: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1300 survival: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1303 old1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1306 reallyold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1309 firstold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1312 finobjsur: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1314 finobjold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1316 finobjrold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1318 bytes: Cell<usize>,
1320 objects: Cell<usize>,
1322 current_white: Cell<Color>,
1324 allocation_tokens: RefCell<IdentityHashMap<usize>>,
1328 next_allocation_token: Cell<usize>,
1330 threshold: Cell<usize>,
1332 pause_multiplier: Cell<usize>,
1334 state: Cell<GcState>,
1336 paused: Cell<bool>,
1339 collections: Cell<usize>,
1341 minor_collections: Cell<usize>,
1343 full_collections: Cell<usize>,
1345 last_mark_stats: Cell<MarkerStats>,
1347 last_sweep_stats: Cell<SweepStats>,
1349 grayagain: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1353 marker: RefCell<Option<Marker>>,
1356 sweep_prev_next: Cell<Option<NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>>>,
1361}
1362
1363impl Default for Heap {
1364 fn default() -> Self {
1365 Self::new()
1366 }
1367}
1368
1369impl Heap {
1370 pub fn new() -> Self {
1371 Self {
1372 head: Cell::new(None),
1373 finobj: Cell::new(None),
1374 tobefnz: Cell::new(None),
1375 survival: Cell::new(None),
1376 old1: Cell::new(None),
1377 reallyold: Cell::new(None),
1378 firstold1: Cell::new(None),
1379 finobjsur: Cell::new(None),
1380 finobjold1: Cell::new(None),
1381 finobjrold: Cell::new(None),
1382 bytes: Cell::new(0),
1383 objects: Cell::new(0),
1384 current_white: Cell::new(Color::White0),
1385 allocation_tokens: RefCell::new(IdentityHashMap::default()),
1386 next_allocation_token: Cell::new(1),
1387 threshold: Cell::new(64 * 1024), pause_multiplier: Cell::new(200), state: Cell::new(GcState::Pause),
1390 paused: Cell::new(true), collections: Cell::new(0),
1392 minor_collections: Cell::new(0),
1393 full_collections: Cell::new(0),
1394 last_mark_stats: Cell::new(MarkerStats::default()),
1395 last_sweep_stats: Cell::new(SweepStats::default()),
1396 grayagain: Cell::new(None),
1397 marker: RefCell::new(None),
1398 sweep_prev_next: Cell::new(None),
1399 }
1400 }
1401
1402 pub fn unpause(&self) {
1405 self.paused.set(false);
1406 }
1407
1408 pub fn is_paused(&self) -> bool {
1409 self.paused.get()
1410 }
1411
1412 pub fn allocate<T: Trace + 'static>(&self, value: T) -> Gc<T> {
1414 let size = std::mem::size_of::<GcBox<T>>();
1415 let boxed = Box::new(GcBox {
1416 header: GcHeader::new_white(size, self.current_white.get(), std::any::type_name::<T>()),
1417 value,
1418 });
1419 boxed.header.next.set(self.head.get());
1420 boxed.header.collected.set(true);
1421 let raw: *mut GcBox<T> = Box::into_raw(boxed);
1422 let ptr: NonNull<GcBox<T>> = NonNull::new(raw).expect("Box::into_raw is non-null");
1423 let dyn_ptr: NonNull<GcBox<dyn Trace>> = ptr;
1424 let identity = ptr.as_ptr() as *const () as usize;
1425 let token = self.next_token();
1426 self.allocation_tokens.borrow_mut().insert(identity, token);
1427 self.head.set(Some(dyn_ptr));
1428 self.bytes.set(self.bytes.get() + size);
1429 self.objects.set(self.objects.get() + 1);
1430 Gc {
1431 ptr,
1432 _marker: PhantomData,
1433 }
1434 }
1435
1436 pub fn bytes_used(&self) -> usize {
1438 self.bytes.get()
1439 }
1440
1441 pub fn adjust_bytes(&self, delta: isize) {
1446 if delta >= 0 {
1447 self.bytes
1448 .set(self.bytes.get().saturating_add(delta as usize));
1449 } else {
1450 self.bytes
1451 .set(self.bytes.get().saturating_sub((-delta) as usize));
1452 }
1453 }
1454
1455 pub fn threshold_bytes(&self) -> usize {
1460 self.threshold.get()
1461 }
1462
1463 pub fn set_threshold_bytes(&self, threshold: usize) {
1469 self.threshold.set(threshold.max(1));
1470 }
1471
1472 pub fn would_collect(&self) -> bool {
1476 !self.paused.get() && self.bytes.get() >= self.threshold.get()
1477 }
1478
1479 pub fn collections(&self) -> usize {
1480 self.collections.get()
1481 }
1482
1483 pub fn minor_collections(&self) -> usize {
1484 self.minor_collections.get()
1485 }
1486
1487 pub fn full_collections(&self) -> usize {
1488 self.full_collections.get()
1489 }
1490
1491 pub fn last_mark_stats(&self) -> MarkerStats {
1492 self.last_mark_stats.get()
1493 }
1494
1495 pub fn last_sweep_stats(&self) -> SweepStats {
1496 self.last_sweep_stats.get()
1497 }
1498
1499 pub fn allgc_cohort_stats(&self) -> AllGcCohortStats {
1500 let survival = self.survival.get();
1501 let old1 = self.old1.get();
1502 let reallyold = self.reallyold.get();
1503 let mut stats = AllGcCohortStats::default();
1504 let mut cursor = self.head.get();
1505 let mut seen = IdentityHashSet::default();
1506 let mut cohort = 0u8;
1507 while let Some(ptr) = cursor {
1508 let id = ptr.as_ptr() as *const () as usize;
1509 if !seen.insert(id) {
1510 break;
1511 }
1512 if Some(ptr) == reallyold {
1513 cohort = 3;
1514 } else if Some(ptr) == old1 {
1515 cohort = 2;
1516 } else if Some(ptr) == survival {
1517 cohort = 1;
1518 }
1519 match cohort {
1520 0 => stats.new += 1,
1521 1 => stats.survival += 1,
1522 2 => stats.old1 += 1,
1523 _ => stats.old += 1,
1524 }
1525 cursor = self.header_from_ptr(ptr).next.get();
1526 }
1527 stats
1528 }
1529
1530 fn next_token(&self) -> usize {
1531 let token = self.next_allocation_token.get().max(1);
1532 let next = token.checked_add(1).unwrap_or(1).max(1);
1533 self.next_allocation_token.set(next);
1534 token
1535 }
1536
1537 fn current_white(&self) -> Color {
1538 self.current_white.get()
1539 }
1540
1541 fn other_white(&self) -> Color {
1542 self.current_white.get().other_white()
1543 }
1544
1545 fn flip_current_white(&self) {
1546 self.current_white.set(self.other_white());
1547 }
1548
1549 fn for_each_list_header(
1550 &self,
1551 head: Option<NonNull<GcBox<dyn Trace>>>,
1552 f: &mut impl FnMut(&GcHeader),
1553 ) {
1554 let mut cursor = head;
1555 while let Some(ptr) = cursor {
1556 let header = self.header_from_ptr(ptr);
1557 cursor = header.next.get();
1558 f(header);
1559 }
1560 }
1561
1562 fn for_each_header(&self, mut f: impl FnMut(&GcHeader)) {
1563 self.for_each_list_header(self.head.get(), &mut f);
1564 self.for_each_list_header(self.finobj.get(), &mut f);
1565 self.for_each_list_header(self.tobefnz.get(), &mut f);
1566 }
1567
1568 fn header_from_ptr<'a>(&'a self, ptr: NonNull<GcBox<dyn Trace>>) -> &'a GcHeader {
1569 unsafe { &(*ptr.as_ptr()).header }
1570 }
1571
1572 fn clear_generation_cursors(&self) {
1573 self.survival.set(None);
1574 self.old1.set(None);
1575 self.reallyold.set(None);
1576 self.firstold1.set(None);
1577 self.finobjsur.set(None);
1578 self.finobjold1.set(None);
1579 self.finobjrold.set(None);
1580 self.clear_grayagain();
1581 }
1582
1583 fn set_all_cursors_to_head(&self) {
1584 let head = self.head.get();
1585 self.survival.set(head);
1586 self.old1.set(head);
1587 self.reallyold.set(head);
1588 self.firstold1.set(None);
1589 let finobj = self.finobj.get();
1590 self.finobjsur.set(finobj);
1591 self.finobjold1.set(finobj);
1592 self.finobjrold.set(finobj);
1593 self.clear_grayagain();
1594 }
1595
1596 fn correct_generation_pointers(
1597 &self,
1598 removed: NonNull<GcBox<dyn Trace>>,
1599 next: Option<NonNull<GcBox<dyn Trace>>>,
1600 ) {
1601 if self.survival.get() == Some(removed) {
1602 self.survival.set(next);
1603 }
1604 if self.old1.get() == Some(removed) {
1605 self.old1.set(next);
1606 }
1607 if self.reallyold.get() == Some(removed) {
1608 self.reallyold.set(next);
1609 }
1610 if self.firstold1.get() == Some(removed) {
1611 self.firstold1.set(next);
1612 }
1613 if self.finobjsur.get() == Some(removed) {
1614 self.finobjsur.set(next);
1615 }
1616 if self.finobjold1.get() == Some(removed) {
1617 self.finobjold1.set(next);
1618 }
1619 if self.finobjrold.get() == Some(removed) {
1620 self.finobjrold.set(next);
1621 }
1622 if self.header_from_ptr(removed).gray_listed.get() {
1623 self.unlink_grayagain(removed);
1624 }
1625 }
1626
1627 fn unlink_from_list(
1628 &self,
1629 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1630 ptr: NonNull<GcBox<dyn Trace>>,
1631 ) -> bool {
1632 let mut prev_cell = list;
1633 loop {
1634 let Some(current) = prev_cell.get() else {
1635 return false;
1636 };
1637 let header = self.header_from_ptr(current);
1638 let next = header.next.get();
1639 if std::ptr::addr_eq(current.as_ptr(), ptr.as_ptr()) {
1640 prev_cell.set(next);
1641 let prev_next_ptr = NonNull::from(prev_cell);
1642 let removed_next_ptr = NonNull::from(&self.header_from_ptr(ptr).next);
1643 if self.sweep_prev_next.get() == Some(removed_next_ptr) {
1644 self.sweep_prev_next.set(Some(prev_next_ptr));
1645 }
1646 self.correct_generation_pointers(ptr, next);
1647 header.next.set(None);
1648 return true;
1649 }
1650 prev_cell = &header.next;
1651 }
1652 }
1653
1654 fn link_to_head(
1655 &self,
1656 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1657 ptr: NonNull<GcBox<dyn Trace>>,
1658 ) {
1659 let header = self.header_from_ptr(ptr);
1660 header.next.set(list.get());
1661 list.set(Some(ptr));
1662 }
1663
1664 fn link_to_tail(
1665 &self,
1666 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1667 ptr: NonNull<GcBox<dyn Trace>>,
1668 ) {
1669 let mut last_cell = list;
1670 loop {
1671 let Some(current) = last_cell.get() else {
1672 let header = self.header_from_ptr(ptr);
1673 header.next.set(None);
1674 last_cell.set(Some(ptr));
1675 return;
1676 };
1677 last_cell = &self.header_from_ptr(current).next;
1678 }
1679 }
1680
1681 pub fn move_allgc_to_finobj(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1682 let header = self.header_from_ptr(ptr);
1683 if !header.collected.get() {
1684 return false;
1685 }
1686 if !self.unlink_from_list(&self.head, ptr) {
1687 return false;
1688 }
1689 if self.state.get().is_sweep() {
1690 header.color.set(self.current_white());
1691 }
1692 self.link_to_head(&self.finobj, ptr);
1693 true
1694 }
1695
1696 pub fn move_finobj_to_tobefnz(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1697 if !self.unlink_from_list(&self.finobj, ptr) {
1698 return false;
1699 }
1700 self.link_to_tail(&self.tobefnz, ptr);
1701 true
1702 }
1703
1704 pub fn move_tobefnz_to_allgc(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1705 let header = self.header_from_ptr(ptr);
1706 if !self.unlink_from_list(&self.tobefnz, ptr) {
1707 return false;
1708 }
1709 if self.state.get().is_sweep() {
1710 header.color.set(self.current_white());
1711 }
1712 self.link_to_head(&self.head, ptr);
1713 if header.age.get() == GcAge::Old1 {
1714 self.firstold1.set(Some(ptr));
1715 }
1716 true
1717 }
1718
1719 fn remember_minor_revisit(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1720 let header = self.header_from_ptr(ptr);
1721 if header.gray_listed.get() {
1722 return;
1723 }
1724 header.gray_next.set(self.grayagain.get());
1725 header.gray_listed.set(true);
1726 self.grayagain.set(Some(ptr));
1727 }
1728
1729 fn mark_minor_revisit_objects(&self, marker: &mut Marker) {
1730 let mut cursor = self.grayagain.get();
1731 while let Some(ptr) = cursor {
1732 let header = self.header_from_ptr(ptr);
1733 cursor = header.gray_next.get();
1734 let id = ptr.as_ptr() as *const () as usize;
1735 marker.mark_box(ptr, header, id);
1736 }
1737 }
1738
1739 fn clear_grayagain(&self) {
1740 let mut cursor = self.grayagain.get();
1741 self.grayagain.set(None);
1742 while let Some(ptr) = cursor {
1743 let header = self.header_from_ptr(ptr);
1744 cursor = header.gray_next.get();
1745 header.gray_next.set(None);
1746 header.gray_listed.set(false);
1747 }
1748 }
1749
1750 fn take_grayagain(&self) -> Vec<NonNull<GcBox<dyn Trace>>> {
1751 let mut objects = Vec::new();
1752 let mut cursor = self.grayagain.get();
1753 self.grayagain.set(None);
1754 while let Some(ptr) = cursor {
1755 let header = self.header_from_ptr(ptr);
1756 cursor = header.gray_next.get();
1757 header.gray_next.set(None);
1758 header.gray_listed.set(false);
1759 objects.push(ptr);
1760 }
1761 objects
1762 }
1763
1764 fn replace_grayagain(&self, objects: Vec<NonNull<GcBox<dyn Trace>>>) {
1765 self.clear_grayagain();
1766 for ptr in objects.into_iter().rev() {
1767 self.remember_minor_revisit(ptr);
1768 }
1769 }
1770
1771 fn unlink_grayagain(&self, removed: NonNull<GcBox<dyn Trace>>) {
1772 let keep = self
1773 .take_grayagain()
1774 .into_iter()
1775 .filter(|ptr| !std::ptr::addr_eq(ptr.as_ptr(), removed.as_ptr()))
1776 .collect();
1777 self.replace_grayagain(keep);
1778 }
1779
1780 pub fn grayagain_count(&self) -> usize {
1781 let mut count = 0usize;
1782 let mut cursor = self.grayagain.get();
1783 while let Some(ptr) = cursor {
1784 count += 1;
1785 cursor = self.header_from_ptr(ptr).gray_next.get();
1786 }
1787 count
1788 }
1789
1790 pub fn allocation_token(&self, identity: usize) -> Option<usize> {
1795 self.allocation_tokens.borrow().get(&identity).copied()
1796 }
1797
1798 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
1803 self.allocation_token(identity) == Some(token)
1804 }
1805
1806 pub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1816 where
1817 P: Trace + 'static,
1818 C: Trace + 'static,
1819 {
1820 if self.paused.get() || self.state.get().is_pause() {
1821 return;
1822 }
1823 if parent.header().color.get() != Color::Black {
1824 return;
1825 }
1826 if !child.header().color.get().is_white() {
1827 return;
1828 }
1829 child.header().color.set(Color::Gray);
1830 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
1831 if let Some(m) = m_opt.as_mut() {
1832 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
1833 m.gray_queue.push(ptr);
1834 m.visited.insert(child.identity());
1835 }
1836 }
1837 }
1838
1839 pub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1842 where
1843 P: Trace + 'static,
1844 C: Trace + 'static,
1845 {
1846 if self.paused.get() || self.state.get().is_pause() {
1847 return;
1848 }
1849 if parent.header().color.get() != Color::Black {
1850 return;
1851 }
1852 if !child.header().color.get().is_white() {
1853 return;
1854 }
1855 parent.header().color.set(Color::Gray);
1856 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
1857 if let Some(m) = m_opt.as_mut() {
1858 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
1859 m.gray_queue.push(ptr);
1860 m.visited.insert(parent.identity());
1861 }
1862 }
1863 }
1864
1865 pub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1870 where
1871 P: Trace + 'static,
1872 C: Trace + 'static,
1873 {
1874 if parent.age().is_old() && !child.age().is_old() {
1875 child.set_age(GcAge::Old0);
1876 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
1877 self.remember_minor_revisit(ptr);
1878 }
1879 self.barrier(parent, child);
1880 }
1881
1882 pub fn generational_backward_barrier<P>(&self, parent: Gc<P>)
1886 where
1887 P: Trace + 'static,
1888 {
1889 if parent.age().is_old() {
1890 parent.set_color(Color::Gray);
1891 parent.set_age(GcAge::Touched1);
1892 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
1893 self.remember_minor_revisit(ptr);
1894 }
1895 }
1896
1897 pub fn step(&self, roots: &dyn Trace) {
1901 self.step_with_post_mark(roots, |_: &mut Marker| {});
1902 }
1903
1904 pub fn step_with_post_mark<F: FnMut(&mut Marker)>(&self, roots: &dyn Trace, post_mark: F) {
1909 if self.paused.get() {
1910 return;
1911 }
1912 if self.bytes.get() < self.threshold.get() {
1913 return;
1914 }
1915 self.full_collect_with_post_mark(roots, post_mark);
1916 }
1917
1918 pub fn full_collect(&self, roots: &dyn Trace) {
1921 self.full_collect_with_post_mark(roots, |_: &mut Marker| {});
1922 }
1923
1924 pub fn mark_only_with_post_mark<F: FnMut(&mut Marker)>(
1929 &self,
1930 roots: &dyn Trace,
1931 mut post_mark: F,
1932 ) {
1933 if self.paused.get() {
1934 return;
1935 }
1936 let mut marker = Marker::new_reserving(self.objects.get());
1937 roots.trace(&mut marker);
1938 marker.drain_gray_queue();
1939 post_mark(&mut marker);
1940 marker.drain_gray_queue();
1941 self.last_mark_stats.set(marker.stats());
1942 }
1943
1944 pub fn promote_all_to_old(&self) {
1947 self.for_each_header(|header| {
1948 header.age.set(GcAge::Old);
1949 header.color.set(Color::Black);
1950 });
1951 self.set_all_cursors_to_head();
1952 }
1953
1954 pub fn reset_all_ages(&self) {
1957 let current_white = self.current_white();
1958 self.for_each_header(|header| {
1959 header.age.set(GcAge::New);
1960 header.color.set(current_white);
1961 });
1962 self.clear_generation_cursors();
1963 }
1964
1965 pub fn minor_collect_with_post_mark<F: FnMut(&mut Marker)>(
1972 &self,
1973 roots: &dyn Trace,
1974 mut post_mark: F,
1975 ) {
1976 if self.paused.get() {
1977 return;
1978 }
1979 if !self.state.get().is_pause() {
1980 self.abort_cycle();
1981 }
1982
1983 self.state.set(GcState::Propagate);
1984 let mut marker = Marker::new_minor_reserving(self.objects.get());
1985 self.last_sweep_stats.set(SweepStats::default());
1986 self.mark_minor_revisit_objects(&mut marker);
1987 roots.trace(&mut marker);
1988 marker.drain_gray_queue();
1989
1990 self.state.set(GcState::EnterAtomic);
1991 self.state.set(GcState::Atomic);
1992 post_mark(&mut marker);
1993 marker.drain_gray_queue();
1994 self.last_mark_stats.set(marker.stats());
1995
1996 self.state.set(GcState::SweepAllGc);
1997 self.sweep_young();
1998 *self.marker.borrow_mut() = None;
1999 self.sweep_prev_next.set(None);
2000 self.state.set(GcState::Pause);
2001 self.collections.set(self.collections.get() + 1);
2002 self.minor_collections.set(self.minor_collections.get() + 1);
2003 }
2004
2005 pub fn full_collect_with_post_mark<F: FnMut(&mut Marker)>(
2012 &self,
2013 roots: &dyn Trace,
2014 mut post_mark: F,
2015 ) {
2016 if self.paused.get() {
2017 return;
2018 }
2019 if !self.state.get().is_pause() {
2020 self.abort_cycle();
2021 }
2022 self.full_collections.set(self.full_collections.get() + 1);
2023 let unlimited = StepBudget {
2024 remaining_work: isize::MAX,
2025 max_credit: isize::MAX,
2026 };
2027 loop {
2028 let outcome = self.incremental_step_with_post_mark(roots, unlimited, &mut post_mark);
2029 if matches!(outcome, StepOutcome::Paused | StepOutcome::SkippedStopped) {
2030 break;
2031 }
2032 }
2033 }
2034
2035 pub fn incremental_step_with_post_mark<F: FnMut(&mut Marker)>(
2050 &self,
2051 roots: &dyn Trace,
2052 mut budget: StepBudget,
2053 mut post_mark: F,
2054 ) -> StepOutcome {
2055 if self.paused.get() {
2056 return StepOutcome::SkippedStopped;
2057 }
2058 self.run_budgeted(roots, &mut budget, &mut post_mark);
2059 if self.state.get().is_pause() {
2060 StepOutcome::Paused
2061 } else {
2062 StepOutcome::InProgress
2063 }
2064 }
2065
2066 fn run_budgeted(
2067 &self,
2068 roots: &dyn Trace,
2069 budget: &mut StepBudget,
2070 post_mark: &mut dyn FnMut(&mut Marker),
2071 ) -> bool {
2072 self.run_budgeted_until(roots, budget, post_mark, None)
2073 }
2074
2075 fn run_budgeted_until(
2076 &self,
2077 roots: &dyn Trace,
2078 budget: &mut StepBudget,
2079 post_mark: &mut dyn FnMut(&mut Marker),
2080 stop_at: Option<GcState>,
2081 ) -> bool {
2082 let mut did_work = false;
2083 loop {
2084 if stop_at == Some(self.state.get()) {
2085 return did_work;
2086 }
2087 if budget.remaining_work <= -budget.max_credit {
2088 return did_work;
2089 }
2090 match self.state.get() {
2091 GcState::Pause => {
2092 self.start_cycle(roots);
2093 self.state.set(GcState::Propagate);
2094 budget.remaining_work -= 1;
2095 did_work = true;
2096 if stop_at == Some(GcState::Propagate) {
2097 return did_work;
2098 }
2099 }
2100 GcState::Propagate => {
2101 let work = self.drain_gray_budgeted(budget.remaining_work.max(1));
2102 budget.remaining_work -= work as isize;
2103 did_work = did_work || work > 0;
2104 let empty = {
2105 let m = self.marker.borrow();
2106 m.as_ref().map(|m| m.gray_queue.is_empty()).unwrap_or(true)
2107 };
2108 if empty {
2109 self.state.set(GcState::EnterAtomic);
2110 if stop_at == Some(GcState::EnterAtomic) {
2111 return did_work;
2112 }
2113 } else if budget.remaining_work <= 0 {
2114 return did_work;
2115 }
2116 }
2117 GcState::EnterAtomic => {
2118 self.state.set(GcState::Atomic);
2119 budget.remaining_work -= 1;
2120 did_work = true;
2121 if stop_at == Some(GcState::Atomic) || budget.remaining_work <= 0 {
2122 return did_work;
2123 }
2124 }
2125 GcState::Atomic => {
2126 self.run_atomic(post_mark);
2127 self.state.set(GcState::SweepAllGc);
2128 budget.remaining_work -= 1;
2129 did_work = true;
2130 if stop_at == Some(GcState::SweepAllGc) {
2131 return did_work;
2132 }
2133 }
2134 GcState::SweepAllGc => {
2135 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2136 budget.remaining_work -= work as isize;
2137 did_work = did_work || work > 0;
2138 if self.sweep_prev_next.get().is_none() {
2139 self.state.set(GcState::SweepFinObj);
2140 self.sweep_prev_next.set(Some(NonNull::from(&self.finobj)));
2141 if stop_at == Some(GcState::SweepFinObj) {
2142 return did_work;
2143 }
2144 } else if budget.remaining_work <= 0 {
2145 return did_work;
2146 }
2147 }
2148 GcState::SweepFinObj => {
2149 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2150 budget.remaining_work -= work as isize;
2151 did_work = did_work || work > 0;
2152 if self.sweep_prev_next.get().is_none() {
2153 self.state.set(GcState::SweepToBeFnz);
2154 self.sweep_prev_next.set(Some(NonNull::from(&self.tobefnz)));
2155 if stop_at == Some(GcState::SweepToBeFnz) {
2156 return did_work;
2157 }
2158 } else if budget.remaining_work <= 0 {
2159 return did_work;
2160 }
2161 }
2162 GcState::SweepToBeFnz => {
2163 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2164 budget.remaining_work -= work as isize;
2165 did_work = did_work || work > 0;
2166 if self.sweep_prev_next.get().is_none() {
2167 self.state.set(GcState::SweepEnd);
2168 if stop_at == Some(GcState::SweepEnd) {
2169 return did_work;
2170 }
2171 } else if budget.remaining_work <= 0 {
2172 return did_work;
2173 }
2174 }
2175 GcState::SweepEnd => {
2176 self.state.set(GcState::CallFin);
2177 budget.remaining_work -= 1;
2178 did_work = true;
2179 if stop_at == Some(GcState::CallFin) || budget.remaining_work <= 0 {
2180 return did_work;
2181 }
2182 }
2183 GcState::CallFin => {
2184 self.finish_cycle();
2185 self.state.set(GcState::Pause);
2186 if stop_at == Some(GcState::Pause) {
2187 return did_work;
2188 }
2189 return did_work;
2190 }
2191 }
2192 }
2193 }
2194
2195 pub fn incremental_run_until_state_with_post_mark<F: FnMut(&mut Marker)>(
2200 &self,
2201 roots: &dyn Trace,
2202 target: GcState,
2203 max_work: isize,
2204 mut post_mark: F,
2205 ) -> StepOutcome {
2206 if self.paused.get() {
2207 return StepOutcome::SkippedStopped;
2208 }
2209 let work = max_work.max(1);
2210 let mut budget = StepBudget {
2211 remaining_work: work,
2212 max_credit: work,
2213 };
2214 self.run_budgeted_until(roots, &mut budget, &mut post_mark, Some(target));
2215 if self.state.get().is_pause() {
2216 StepOutcome::Paused
2217 } else {
2218 StepOutcome::InProgress
2219 }
2220 }
2221
2222 fn start_cycle(&self, roots: &dyn Trace) {
2223 self.flip_current_white();
2224 let dead_white = self.other_white();
2225 self.for_each_header(|header| {
2226 header.color.set(dead_white);
2227 });
2228 let mut marker = Marker::new_reserving(self.objects.get());
2229 roots.trace(&mut marker);
2230 *self.marker.borrow_mut() = Some(marker);
2231 self.sweep_prev_next.set(None);
2232 }
2233
2234 fn drain_gray_budgeted(&self, max_units: isize) -> usize {
2235 let mut m_opt = self.marker.borrow_mut();
2236 let marker = match m_opt.as_mut() {
2237 Some(m) => m,
2238 None => return 0,
2239 };
2240 let mut work = 0usize;
2241 let mut budget = max_units;
2242 while budget > 0 {
2243 let next = match marker.gray_queue.pop() {
2244 Some(p) => p,
2245 None => break,
2246 };
2247 unsafe {
2248 let bx = next.as_ref();
2249 marker.stats.traced += 1;
2250 if bx.header.age.get().is_old() {
2251 marker.stats.traced_old += 1;
2252 } else {
2253 marker.stats.traced_young += 1;
2254 }
2255 bx.header.color.set(Color::Black);
2256 bx.value.trace(marker);
2257 }
2258 work += 1;
2259 budget -= 1;
2260 }
2261 work
2262 }
2263
2264 fn run_atomic(&self, post_mark: &mut dyn FnMut(&mut Marker)) {
2265 let mut m_opt = self.marker.borrow_mut();
2266 if let Some(marker) = m_opt.as_mut() {
2267 marker.drain_gray_queue();
2268 post_mark(marker);
2269 marker.drain_gray_queue();
2270 }
2271 self.sweep_prev_next.set(Some(NonNull::from(&self.head)));
2272 self.last_sweep_stats.set(SweepStats::default());
2273 }
2274
2275 fn sweep_budgeted(&self, max_units: isize) -> usize {
2276 let mut work = 0usize;
2277 let mut budget = max_units;
2278 let mut freed_bytes = 0usize;
2279 let mut stats = SweepStats::default();
2280 let current_white = self.current_white();
2281 let dead_white = self.other_white();
2282 let mut prev_next_ptr = match self.sweep_prev_next.get() {
2283 Some(p) => p,
2284 None => return 0,
2285 };
2286 while budget > 0 {
2287 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2288 let cursor = prev_cell.get();
2289 let ptr = match cursor {
2290 Some(p) => p,
2291 None => {
2292 self.sweep_prev_next.set(None);
2293 break;
2294 }
2295 };
2296 let header = self.header_from_ptr(ptr);
2297 let next = header.next.get();
2298 let age = header.age.get();
2299 stats.record_visit(age);
2300 let color = header.color.get();
2301 if color == dead_white {
2302 prev_cell.set(next);
2303 let size = header.size.get();
2304 freed_bytes += size;
2305 stats.record_free(size);
2306 self.correct_generation_pointers(ptr, next);
2307 self.allocation_tokens
2308 .borrow_mut()
2309 .remove(&(ptr.as_ptr() as *const () as usize));
2310 self.objects.set(self.objects.get().saturating_sub(1));
2311 unsafe {
2312 let _ = Box::from_raw(ptr.as_ptr());
2313 }
2314 } else {
2315 if matches!(color, Color::Black | Color::Gray) {
2316 header.color.set(current_white);
2317 }
2318 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2319 self.sweep_prev_next.set(Some(prev_next_ptr));
2320 }
2321 work += 1;
2322 budget -= 1;
2323 }
2324 if freed_bytes > 0 {
2325 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2326 }
2327 if stats.visited > 0 {
2328 let mut total = self.last_sweep_stats.get();
2329 total.add(stats);
2330 self.last_sweep_stats.set(total);
2331 }
2332 work
2333 }
2334
2335 fn push_next_revisit(
2336 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2337 seen: &mut IdentityHashSet,
2338 ptr: NonNull<GcBox<dyn Trace>>,
2339 age: GcAge,
2340 ) {
2341 if matches!(
2342 age,
2343 GcAge::Old0 | GcAge::Old1 | GcAge::Touched1 | GcAge::Touched2
2344 ) {
2345 let id = ptr.as_ptr() as *const () as usize;
2346 if seen.insert(id) {
2347 next_revisit.push(ptr);
2348 }
2349 }
2350 }
2351
2352 fn sweep_young_range(
2353 &self,
2354 mut prev_next_ptr: NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2355 limit: Option<NonNull<GcBox<dyn Trace>>>,
2356 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2357 next_revisit_seen: &mut IdentityHashSet,
2358 processed: &mut Option<OldRevisitTracker>,
2359 firstold1: &mut Option<NonNull<GcBox<dyn Trace>>>,
2360 freed_bytes: &mut usize,
2361 stats: &mut SweepStats,
2362 ) -> (
2363 NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2364 Option<NonNull<GcBox<dyn Trace>>>,
2365 ) {
2366 let current_white = self.current_white();
2367 loop {
2368 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2369 let Some(ptr) = prev_cell.get() else {
2370 return (prev_next_ptr, None);
2371 };
2372 if Some(ptr) == limit {
2373 return (prev_next_ptr, Some(ptr));
2374 }
2375 let header = self.header_from_ptr(ptr);
2376 let next = header.next.get();
2377 let age = header.age.get();
2378 stats.record_visit(age);
2379 if let Some(processed) = processed.as_mut() {
2380 processed.record_processed(ptr.as_ptr() as *const () as usize);
2381 }
2382 if header.color.get().is_white() && !age.is_old() {
2383 prev_cell.set(next);
2384 let size = header.size.get();
2385 *freed_bytes += size;
2386 stats.record_free(size);
2387 self.correct_generation_pointers(ptr, next);
2388 self.allocation_tokens
2389 .borrow_mut()
2390 .remove(&(ptr.as_ptr() as *const () as usize));
2391 self.objects.set(self.objects.get().saturating_sub(1));
2392 unsafe {
2393 let _ = Box::from_raw(ptr.as_ptr());
2394 }
2395 continue;
2396 }
2397
2398 if !header.color.get().is_white() {
2399 let next_age = age.next_after_minor();
2400 header.age.set(next_age);
2401 if next_age == GcAge::Old1 && firstold1.is_none() {
2402 *firstold1 = Some(ptr);
2403 }
2404 match age {
2405 GcAge::New => header.color.set(current_white),
2406 GcAge::Touched1 | GcAge::Touched2 => header.color.set(Color::Black),
2407 _ => {}
2408 }
2409 Self::push_next_revisit(next_revisit, next_revisit_seen, ptr, next_age);
2410 }
2411 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2412 }
2413 }
2414
2415 fn sweep_young(&self) {
2416 let mut freed_bytes = 0usize;
2417 let mut next_revisit = Vec::new();
2418 let mut next_revisit_seen = IdentityHashSet::default();
2419 let mut firstold1 = None;
2420 let mut stats = SweepStats::default();
2421 let old_revisit = self.take_grayagain();
2422 let mut processed = OldRevisitTracker::new(&old_revisit);
2423 let survival = self.survival.get();
2424 let old1 = self.old1.get();
2425
2426 let (psurvival, new_old1) = self.sweep_young_range(
2427 NonNull::from(&self.head),
2428 survival,
2429 &mut next_revisit,
2430 &mut next_revisit_seen,
2431 &mut processed,
2432 &mut firstold1,
2433 &mut freed_bytes,
2434 &mut stats,
2435 );
2436 self.sweep_young_range(
2437 psurvival,
2438 old1,
2439 &mut next_revisit,
2440 &mut next_revisit_seen,
2441 &mut processed,
2442 &mut firstold1,
2443 &mut freed_bytes,
2444 &mut stats,
2445 );
2446
2447 let finobjsur = self.finobjsur.get();
2448 let finobjold1 = self.finobjold1.get();
2449 let mut dummy_firstold1 = None;
2450 let (pfinobjsur, new_finobjold1) = self.sweep_young_range(
2451 NonNull::from(&self.finobj),
2452 finobjsur,
2453 &mut next_revisit,
2454 &mut next_revisit_seen,
2455 &mut processed,
2456 &mut dummy_firstold1,
2457 &mut freed_bytes,
2458 &mut stats,
2459 );
2460 self.sweep_young_range(
2461 pfinobjsur,
2462 finobjold1,
2463 &mut next_revisit,
2464 &mut next_revisit_seen,
2465 &mut processed,
2466 &mut dummy_firstold1,
2467 &mut freed_bytes,
2468 &mut stats,
2469 );
2470 self.sweep_young_range(
2471 NonNull::from(&self.tobefnz),
2472 None,
2473 &mut next_revisit,
2474 &mut next_revisit_seen,
2475 &mut processed,
2476 &mut dummy_firstold1,
2477 &mut freed_bytes,
2478 &mut stats,
2479 );
2480
2481 if let Some(processed) = processed.as_mut() {
2482 processed.finish();
2483 }
2484
2485 for ptr in old_revisit {
2486 let id = ptr.as_ptr() as *const () as usize;
2487 if processed
2488 .as_ref()
2489 .is_some_and(|processed| processed.was_processed(id))
2490 {
2491 continue;
2492 }
2493 stats.revisit += 1;
2494 let header = self.header_from_ptr(ptr);
2495 if header.color.get().is_white() {
2496 continue;
2497 }
2498 let age = header.age.get();
2499 let next_age = age.next_after_minor();
2500 header.age.set(next_age);
2501 if next_age == GcAge::Old1 && firstold1.is_none() {
2502 firstold1 = Some(ptr);
2503 }
2504 if matches!(age, GcAge::Touched1 | GcAge::Touched2) {
2505 header.color.set(Color::Black);
2506 }
2507 Self::push_next_revisit(&mut next_revisit, &mut next_revisit_seen, ptr, next_age);
2508 }
2509
2510 if freed_bytes > 0 {
2511 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2512 }
2513 self.replace_grayagain(next_revisit);
2514 self.reallyold.set(old1);
2515 self.old1.set(new_old1);
2516 self.survival.set(self.head.get());
2517 self.firstold1.set(firstold1);
2518 self.finobjrold.set(finobjold1);
2519 self.finobjold1.set(new_finobjold1);
2520 self.finobjsur.set(self.finobj.get());
2521 self.last_sweep_stats.set(stats);
2522 }
2523
2524 fn finish_cycle(&self) {
2525 let stats = self
2526 .marker
2527 .borrow()
2528 .as_ref()
2529 .map(|marker| marker.stats())
2530 .unwrap_or_default();
2531 self.last_mark_stats.set(stats);
2532 *self.marker.borrow_mut() = None;
2533 self.sweep_prev_next.set(None);
2534 let next = self.bytes.get().saturating_mul(self.pause_multiplier.get()) / 100;
2535 self.threshold.set(next.max(GC_MIN_THRESHOLD));
2536 self.collections.set(self.collections.get() + 1);
2537 }
2538
2539 pub fn finish_callfin_phase(&self) -> bool {
2542 if self.state.get() != GcState::CallFin {
2543 return false;
2544 }
2545 self.finish_cycle();
2546 self.state.set(GcState::Pause);
2547 true
2548 }
2549
2550 fn abort_cycle(&self) {
2551 if !self.state.get().is_pause() {
2552 *self.marker.borrow_mut() = None;
2553 self.sweep_prev_next.set(None);
2554 let current_white = self.current_white();
2555 self.for_each_header(|header| {
2556 header.color.set(current_white);
2557 });
2558 self.state.set(GcState::Pause);
2559 }
2560 }
2561
2562 pub fn gc_state(&self) -> GcState {
2564 self.state.get()
2565 }
2566
2567 pub fn allgc_count(&self) -> usize {
2569 self.objects.get()
2570 }
2571
2572 pub fn type_name_count(&self, mut predicate: impl FnMut(&'static str) -> bool) -> usize {
2576 let mut count = 0usize;
2577 self.for_each_header(|header| {
2578 if predicate(header.type_name) {
2579 count += 1;
2580 }
2581 });
2582 count
2583 }
2584
2585 pub fn drop_all(&self) {
2589 *self.marker.borrow_mut() = None;
2590 self.sweep_prev_next.set(None);
2591 self.clear_generation_cursors();
2592 self.state.set(GcState::Pause);
2593 self.allocation_tokens.borrow_mut().clear();
2594 self.drop_list(&self.head);
2595 self.drop_list(&self.finobj);
2596 self.drop_list(&self.tobefnz);
2597 self.bytes.set(0);
2598 self.objects.set(0);
2599 }
2600
2601 fn drop_list(&self, list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>) {
2602 let mut cursor = list.get();
2603 list.set(None);
2604 while let Some(ptr) = cursor {
2605 let next = unsafe {
2607 let next = (*ptr.as_ptr()).header.next.get();
2608 let _ = Box::from_raw(ptr.as_ptr());
2609 next
2610 };
2611 cursor = next;
2612 }
2613 }
2614}
2615
2616impl Drop for Heap {
2617 fn drop(&mut self) {
2618 self.drop_all();
2619 }
2620}
2621
2622#[cfg(test)]
2627mod tests {
2628 use super::*;
2629
2630 struct Cell0 {
2632 next: Cell<Option<Gc<Cell0>>>,
2633 marker_calls: Cell<usize>,
2634 }
2635
2636 impl Trace for Cell0 {
2637 fn trace(&self, m: &mut Marker) {
2638 self.marker_calls.set(self.marker_calls.get() + 1);
2639 if let Some(n) = self.next.get() {
2640 m.mark(n);
2641 }
2642 }
2643 }
2644
2645 struct OneRoot(Option<Gc<Cell0>>);
2647 impl Trace for OneRoot {
2648 fn trace(&self, m: &mut Marker) {
2649 if let Some(g) = self.0 {
2650 m.mark(g);
2651 }
2652 }
2653 }
2654
2655 struct TwoRoots {
2656 first: Option<Gc<Cell0>>,
2657 second: Option<Gc<Cell0>>,
2658 }
2659
2660 impl Trace for TwoRoots {
2661 fn trace(&self, m: &mut Marker) {
2662 if let Some(g) = self.first {
2663 m.mark(g);
2664 }
2665 if let Some(g) = self.second {
2666 m.mark(g);
2667 }
2668 }
2669 }
2670
2671 #[derive(Clone)]
2672 struct FinalizerCell {
2673 id: usize,
2674 age: GcAge,
2675 finalized: std::rc::Rc<Cell<bool>>,
2676 }
2677
2678 impl FinalizerCell {
2679 fn new(id: usize) -> Self {
2680 Self {
2681 id,
2682 age: GcAge::New,
2683 finalized: std::rc::Rc::new(Cell::new(false)),
2684 }
2685 }
2686 }
2687
2688 impl FinalizerEntry for FinalizerCell {
2689 fn identity(&self) -> usize {
2690 self.id
2691 }
2692
2693 fn age(&self) -> GcAge {
2694 self.age
2695 }
2696
2697 fn is_finalized(&self) -> bool {
2698 self.finalized.get()
2699 }
2700
2701 fn set_finalized(&self, finalized: bool) {
2702 self.finalized.set(finalized);
2703 }
2704 }
2705
2706 fn finalizer_ids(objects: &[FinalizerCell]) -> Vec<usize> {
2707 objects.iter().map(|object| object.id).collect()
2708 }
2709
2710 #[derive(Clone)]
2711 struct WeakCell {
2712 id: usize,
2713 live: bool,
2714 kind: WeakListKind,
2715 }
2716
2717 impl WeakEntry for WeakCell {
2718 type Strong = usize;
2719
2720 fn identity(&self) -> usize {
2721 self.id
2722 }
2723
2724 fn list_kind(&self) -> WeakListKind {
2725 self.kind
2726 }
2727
2728 fn upgrade(&self) -> Option<Self::Strong> {
2729 self.live.then_some(self.id)
2730 }
2731 }
2732
2733 #[test]
2734 fn weak_registry_dedups_snapshots_and_retains_live_ids() {
2735 let mut registry = WeakRegistry::default();
2736 registry.push_unique(WeakCell {
2737 id: 1,
2738 live: true,
2739 kind: WeakListKind::WeakValues,
2740 });
2741 registry.push_unique(WeakCell {
2742 id: 1,
2743 live: true,
2744 kind: WeakListKind::Ephemeron,
2745 });
2746 registry.push_unique(WeakCell {
2747 id: 2,
2748 live: false,
2749 kind: WeakListKind::AllWeak,
2750 });
2751 registry.push_unique(WeakCell {
2752 id: 3,
2753 live: true,
2754 kind: WeakListKind::WeakValues,
2755 });
2756
2757 let stats = registry.stats();
2758 assert_eq!(stats.weak_values, 1);
2759 assert_eq!(stats.ephemeron, 1);
2760 assert_eq!(stats.all_weak, 1);
2761
2762 let snapshot = registry.live_snapshot();
2763 assert_eq!(snapshot, vec![3, 1]);
2764 assert_eq!(
2765 registry.stats(),
2766 WeakRegistryStats {
2767 tracked: 3,
2768 snapshot_live: 2,
2769 snapshot_dead: 1,
2770 retained: 2,
2771 weak_values: 1,
2772 ephemeron: 1,
2773 all_weak: 0,
2774 }
2775 );
2776
2777 let keep: std::collections::HashSet<usize> = [3usize].into_iter().collect();
2778 registry.retain_identities(&keep);
2779 assert_eq!(registry.len(), 1);
2780 assert_eq!(registry.stats().retained, 1);
2781 assert_eq!(registry.stats().weak_values, 1);
2782 registry.remove_identity(3);
2783 assert_eq!(registry.len(), 0);
2784 }
2785
2786 #[test]
2787 fn finalizer_registry_tracks_generational_cohorts() {
2788 let mut registry = FinalizerRegistry::default();
2789 registry.push_pending_unique(FinalizerCell::new(1));
2790 registry.push_pending_unique(FinalizerCell::new(2));
2791
2792 let stats = registry.stats();
2793 assert_eq!(stats.finobj_new, 2);
2794 assert_eq!(stats.finobj_survival, 0);
2795 assert_eq!(stats.finobj_old1, 0);
2796 assert_eq!(stats.finobj_reallyold, 0);
2797 assert_eq!(stats.finobj_minor_scan, 2);
2798
2799 registry.finish_minor_collection();
2800 let stats = registry.stats();
2801 assert_eq!(stats.finobj_new, 0);
2802 assert_eq!(stats.finobj_survival, 2);
2803 assert_eq!(stats.finobj_old1, 0);
2804 assert_eq!(stats.finobj_reallyold, 0);
2805 assert_eq!(stats.finobj_minor_scan, 2);
2806
2807 registry.push_pending_unique(FinalizerCell::new(3));
2808 registry.finish_minor_collection();
2809 let stats = registry.stats();
2810 assert_eq!(stats.finobj_new, 0);
2811 assert_eq!(stats.finobj_survival, 1);
2812 assert_eq!(stats.finobj_old1, 2);
2813 assert_eq!(stats.finobj_reallyold, 0);
2814 assert_eq!(stats.finobj_minor_scan, 1);
2815
2816 registry.finish_minor_collection();
2817 let stats = registry.stats();
2818 assert_eq!(stats.finobj_new, 0);
2819 assert_eq!(stats.finobj_survival, 0);
2820 assert_eq!(stats.finobj_old1, 1);
2821 assert_eq!(stats.finobj_reallyold, 2);
2822 assert_eq!(stats.finobj_minor_scan, 0);
2823 }
2824
2825 #[test]
2826 fn finalizer_registry_minor_snapshot_uses_cohort_boundaries() {
2827 let mut registry = FinalizerRegistry::default();
2828 registry.push_pending_unique(FinalizerCell::new(1));
2829 registry.push_pending_unique(FinalizerCell::new(2));
2830 registry.push_pending_unique(FinalizerCell::new(3));
2831 registry.finish_minor_collection();
2832 registry.finish_minor_collection();
2833 registry.push_pending_unique(FinalizerCell::new(4));
2834 registry.push_pending_unique(FinalizerCell::new(5));
2835
2836 assert_eq!(
2837 finalizer_ids(®istry.pending_minor_snapshot()),
2838 vec![4, 5],
2839 "minor finalizer scan must skip the old1/reallyold prefix"
2840 );
2841
2842 registry.push_to_be_finalized(FinalizerCell::new(99));
2843 registry.promote_pending_to_finalized(vec![
2844 FinalizerCell::new(1),
2845 FinalizerCell::new(2),
2846 FinalizerCell::new(4),
2847 ]);
2848
2849 let stats = registry.stats();
2850 assert_eq!(stats.finobj_old1, 1);
2851 assert_eq!(stats.finobj_new, 1);
2852 assert_eq!(stats.finobj_minor_scan, 1);
2853 assert_eq!(finalizer_ids(registry.pending()), vec![3, 5]);
2854 assert_eq!(
2855 finalizer_ids(registry.to_be_finalized()),
2856 vec![99, 4, 2, 1],
2857 "new to-be-finalized batches append behind older queued finalizers"
2858 );
2859 }
2860
2861 #[test]
2862 fn finalizer_registry_marks_and_clears_finalized_bit() {
2863 let mut registry = FinalizerRegistry::default();
2864 let object = FinalizerCell::new(1);
2865
2866 assert!(!object.is_finalized());
2867 registry.push_pending_unique(object.clone());
2868 assert!(object.is_finalized());
2869
2870 registry.push_pending_unique(object.clone());
2871 assert_eq!(registry.pending_len(), 1);
2872
2873 registry.promote_pending_to_finalized(vec![object.clone()]);
2874 assert!(object.is_finalized());
2875 assert_eq!(registry.pending_len(), 0);
2876 assert_eq!(registry.to_be_finalized_len(), 1);
2877
2878 let popped = registry.pop_to_be_finalized().unwrap();
2879 assert_eq!(popped.id, 1);
2880 assert!(!object.is_finalized());
2881
2882 registry.push_pending_unique(object.clone());
2883 assert!(object.is_finalized());
2884 assert_eq!(registry.pending_len(), 1);
2885 }
2886
2887 #[test]
2888 fn alloc_and_drop_all() {
2889 let heap = Heap::new();
2890 heap.unpause();
2891 let _a = heap.allocate(Cell0 {
2892 next: Cell::new(None),
2893 marker_calls: Cell::new(0),
2894 });
2895 let _b = heap.allocate(Cell0 {
2896 next: Cell::new(None),
2897 marker_calls: Cell::new(0),
2898 });
2899 assert!(heap.bytes_used() > 0);
2900 heap.drop_all();
2901 assert_eq!(heap.bytes_used(), 0);
2902 }
2903
2904 fn list_len(heap: &Heap, mut cursor: Option<NonNull<GcBox<dyn Trace>>>) -> usize {
2905 let mut count = 0usize;
2906 while let Some(ptr) = cursor {
2907 count += 1;
2908 cursor = heap.header_from_ptr(ptr).next.get();
2909 }
2910 count
2911 }
2912
2913 #[test]
2914 fn finalizer_intrusive_lists_sweep_and_drop() {
2915 let heap = Heap::new();
2916 heap.unpause();
2917 let _normal = heap.allocate(Cell0 {
2918 next: Cell::new(None),
2919 marker_calls: Cell::new(0),
2920 });
2921 let finobj = heap.allocate(Cell0 {
2922 next: Cell::new(None),
2923 marker_calls: Cell::new(0),
2924 });
2925 let tobefnz = heap.allocate(Cell0 {
2926 next: Cell::new(None),
2927 marker_calls: Cell::new(0),
2928 });
2929
2930 assert!(heap.move_allgc_to_finobj(finobj.as_trace_ptr()));
2931 assert!(heap.move_allgc_to_finobj(tobefnz.as_trace_ptr()));
2932 assert!(heap.move_finobj_to_tobefnz(tobefnz.as_trace_ptr()));
2933 assert_eq!(list_len(&heap, heap.head.get()), 1);
2934 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
2935 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
2936 assert_eq!(heap.allgc_count(), 3);
2937
2938 heap.full_collect(&TwoRoots {
2939 first: Some(finobj),
2940 second: Some(tobefnz),
2941 });
2942 assert_eq!(list_len(&heap, heap.head.get()), 0);
2943 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
2944 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
2945 assert_eq!(heap.allgc_count(), 2);
2946
2947 assert!(heap.move_tobefnz_to_allgc(tobefnz.as_trace_ptr()));
2948 heap.full_collect(&OneRoot(Some(tobefnz)));
2949 assert_eq!(list_len(&heap, heap.head.get()), 1);
2950 assert_eq!(list_len(&heap, heap.finobj.get()), 0);
2951 assert_eq!(list_len(&heap, heap.tobefnz.get()), 0);
2952 assert_eq!(heap.allgc_count(), 1);
2953
2954 heap.drop_all();
2955 assert_eq!(heap.bytes_used(), 0);
2956 }
2957
2958 #[test]
2959 fn account_buffer_refunded_on_sweep() {
2960 let heap = Heap::new();
2961 heap.unpause();
2962 let baseline = heap.bytes_used();
2963 {
2964 let a = heap.allocate(Cell0 {
2965 next: Cell::new(None),
2966 marker_calls: Cell::new(0),
2967 });
2968 assert!(heap.bytes_used() > baseline);
2969 a.account_buffer(&heap, 4096);
2970 assert_eq!(
2971 a.header().size.get(),
2972 std::mem::size_of::<GcBox<Cell0>>() + 4096
2973 );
2974 }
2975 heap.full_collect(&OneRoot(None));
2978 assert_eq!(
2979 heap.bytes_used(),
2980 baseline,
2981 "account_buffer charge must be fully refunded by sweep"
2982 );
2983 }
2984
2985 #[test]
2986 fn account_buffer_noop_on_uncollected() {
2987 let heap = Heap::new();
2988 heap.unpause();
2989 let g = Gc::new_uncollected(Cell0 {
2990 next: Cell::new(None),
2991 marker_calls: Cell::new(0),
2992 });
2993 let before = heap.bytes_used();
2994 g.account_buffer(&heap, 8192);
2995 assert_eq!(
2996 heap.bytes_used(),
2997 before,
2998 "uncollected box must not charge the pacer"
2999 );
3000 }
3001
3002 #[test]
3003 fn collect_unreachable_frees_bytes() {
3004 let heap = Heap::new();
3005 heap.unpause();
3006 let _a = heap.allocate(Cell0 {
3007 next: Cell::new(None),
3008 marker_calls: Cell::new(0),
3009 });
3010 let bytes_before = heap.bytes_used();
3011 assert!(bytes_before > 0);
3012 heap.full_collect(&OneRoot(None));
3014 assert_eq!(heap.bytes_used(), 0);
3015 assert_eq!(heap.collections(), 1);
3016 }
3017
3018 #[test]
3019 fn collect_keeps_reachable() {
3020 let heap = Heap::new();
3021 heap.unpause();
3022 let root = heap.allocate(Cell0 {
3023 next: Cell::new(None),
3024 marker_calls: Cell::new(0),
3025 });
3026 let bytes_before = heap.bytes_used();
3027 heap.full_collect(&OneRoot(Some(root)));
3028 assert_eq!(heap.bytes_used(), bytes_before);
3029 assert_eq!(root.marker_calls.get(), 1);
3030 }
3031
3032 #[test]
3033 fn allocations_start_new_and_white() {
3034 let heap = Heap::new();
3035 heap.unpause();
3036 let obj = heap.allocate(Cell0 {
3037 next: Cell::new(None),
3038 marker_calls: Cell::new(0),
3039 });
3040 assert_eq!(obj.age(), GcAge::New);
3041 assert!(obj.color().is_white());
3042 }
3043
3044 #[test]
3045 fn allocation_tokens_track_exact_live_box() {
3046 let heap = Heap::new();
3047 heap.unpause();
3048 let obj = heap.allocate(Cell0 {
3049 next: Cell::new(None),
3050 marker_calls: Cell::new(0),
3051 });
3052 let id = obj.identity();
3053 let token = heap
3054 .allocation_token(id)
3055 .expect("heap allocation should get a token");
3056
3057 assert!(heap.contains_allocation(id, token));
3058 assert!(!heap.contains_allocation(id, token + 1));
3059
3060 heap.full_collect(&OneRoot(None));
3061 assert_eq!(heap.allocation_token(id), None);
3062 assert!(!heap.contains_allocation(id, token));
3063 }
3064
3065 #[test]
3066 fn allocation_during_incremental_sweep_survives_current_cycle() {
3067 let heap = Heap::new();
3068 heap.unpause();
3069 let old_dead = heap.allocate(Cell0 {
3070 next: Cell::new(None),
3071 marker_calls: Cell::new(0),
3072 });
3073 let old_id = old_dead.identity();
3074
3075 let outcome = heap.incremental_run_until_state_with_post_mark(
3076 &OneRoot(None),
3077 GcState::SweepAllGc,
3078 1024,
3079 |_| {},
3080 );
3081 assert_eq!(outcome, StepOutcome::InProgress);
3082 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3083
3084 let new_during_sweep = heap.allocate(Cell0 {
3085 next: Cell::new(None),
3086 marker_calls: Cell::new(0),
3087 });
3088 let new_id = new_during_sweep.identity();
3089
3090 loop {
3091 let outcome = heap.incremental_step_with_post_mark(
3092 &OneRoot(None),
3093 StepBudget::from_work(64),
3094 |_| {},
3095 );
3096 if matches!(outcome, StepOutcome::Paused) {
3097 break;
3098 }
3099 }
3100
3101 assert_eq!(heap.allocation_token(old_id), None);
3102 assert!(heap.allocation_token(new_id).is_some());
3103 assert_eq!(heap.allgc_count(), 1);
3104
3105 heap.full_collect(&OneRoot(None));
3106 assert_eq!(heap.allocation_token(new_id), None);
3107 assert_eq!(heap.allgc_count(), 0);
3108 }
3109
3110 #[test]
3111 fn promote_and_reset_all_ages() {
3112 let heap = Heap::new();
3113 heap.unpause();
3114 let a = heap.allocate(Cell0 {
3115 next: Cell::new(None),
3116 marker_calls: Cell::new(0),
3117 });
3118 let b = heap.allocate(Cell0 {
3119 next: Cell::new(None),
3120 marker_calls: Cell::new(0),
3121 });
3122
3123 heap.promote_all_to_old();
3124 assert_eq!(a.age(), GcAge::Old);
3125 assert_eq!(b.age(), GcAge::Old);
3126 assert_eq!(a.color(), Color::Black);
3127 assert_eq!(b.color(), Color::Black);
3128
3129 heap.reset_all_ages();
3130 assert_eq!(a.age(), GcAge::New);
3131 assert_eq!(b.age(), GcAge::New);
3132 assert!(a.color().is_white());
3133 assert!(b.color().is_white());
3134 }
3135
3136 #[test]
3137 fn generational_barriers_update_ages() {
3138 let heap = Heap::new();
3139 heap.unpause();
3140 let parent = heap.allocate(Cell0 {
3141 next: Cell::new(None),
3142 marker_calls: Cell::new(0),
3143 });
3144 let child = heap.allocate(Cell0 {
3145 next: Cell::new(None),
3146 marker_calls: Cell::new(0),
3147 });
3148
3149 parent.set_age(GcAge::Old);
3150 parent.set_color(Color::Black);
3151 heap.generational_backward_barrier(parent);
3152 assert_eq!(parent.age(), GcAge::Touched1);
3153 assert_eq!(parent.color(), Color::Gray);
3154
3155 heap.generational_forward_barrier(parent, child);
3156 assert_eq!(child.age(), GcAge::Old0);
3157 }
3158
3159 #[test]
3160 fn minor_collect_frees_young_and_keeps_old() {
3161 let heap = Heap::new();
3162 heap.unpause();
3163 let old_unreachable = heap.allocate(Cell0 {
3164 next: Cell::new(None),
3165 marker_calls: Cell::new(0),
3166 });
3167 old_unreachable.set_age(GcAge::Old);
3168 old_unreachable.set_color(Color::Black);
3169 let _young_unreachable = heap.allocate(Cell0 {
3170 next: Cell::new(None),
3171 marker_calls: Cell::new(0),
3172 });
3173 let young_survivor = heap.allocate(Cell0 {
3174 next: Cell::new(None),
3175 marker_calls: Cell::new(0),
3176 });
3177
3178 heap.minor_collect_with_post_mark(&OneRoot(Some(young_survivor)), |_| {});
3179
3180 assert_eq!(heap.allgc_count(), 2);
3181 assert_eq!(old_unreachable.age(), GcAge::Old);
3182 assert_eq!(young_survivor.age(), GcAge::Survival);
3183 assert!(young_survivor.color().is_white());
3184 }
3185
3186 #[test]
3187 fn minor_collect_skips_untouched_old_root_scan_work() {
3188 let heap = Heap::new();
3189 heap.unpause();
3190 let old_root = heap.allocate(Cell0 {
3191 next: Cell::new(None),
3192 marker_calls: Cell::new(0),
3193 });
3194 old_root.set_age(GcAge::Old);
3195 old_root.set_color(Color::Black);
3196 let young_root = heap.allocate(Cell0 {
3197 next: Cell::new(None),
3198 marker_calls: Cell::new(0),
3199 });
3200
3201 heap.minor_collect_with_post_mark(
3202 &TwoRoots {
3203 first: Some(old_root),
3204 second: Some(young_root),
3205 },
3206 |_| {},
3207 );
3208
3209 let stats = heap.last_mark_stats();
3210 assert_eq!(stats.marked, 2);
3211 assert_eq!(stats.marked_old, 1);
3212 assert_eq!(stats.marked_young, 1);
3213 assert_eq!(stats.traced, 1);
3214 assert_eq!(stats.traced_old, 0);
3215 assert_eq!(stats.traced_young, 1);
3216 assert_eq!(old_root.marker_calls.get(), 0);
3217 assert_eq!(young_root.marker_calls.get(), 1);
3218 assert_eq!(old_root.age(), GcAge::Old);
3219 assert_eq!(young_root.age(), GcAge::Survival);
3220 }
3221
3222 #[test]
3223 fn minor_collect_traces_touched_old_parent() {
3224 let heap = Heap::new();
3225 heap.unpause();
3226 let old_root = heap.allocate(Cell0 {
3227 next: Cell::new(None),
3228 marker_calls: Cell::new(0),
3229 });
3230 old_root.set_age(GcAge::Old);
3231 old_root.set_color(Color::Black);
3232 let young_child = heap.allocate(Cell0 {
3233 next: Cell::new(None),
3234 marker_calls: Cell::new(0),
3235 });
3236 old_root.next.set(Some(young_child));
3237 heap.generational_backward_barrier(old_root);
3238
3239 heap.minor_collect_with_post_mark(&OneRoot(Some(old_root)), |_| {});
3240
3241 let stats = heap.last_mark_stats();
3242 assert_eq!(stats.marked, 2);
3243 assert_eq!(stats.marked_old, 1);
3244 assert_eq!(stats.marked_young, 1);
3245 assert_eq!(stats.traced, 2);
3246 assert_eq!(stats.traced_old, 1);
3247 assert_eq!(stats.traced_young, 1);
3248 assert_eq!(old_root.marker_calls.get(), 1);
3249 assert_eq!(young_child.marker_calls.get(), 1);
3250 assert_eq!(old_root.age(), GcAge::Touched2);
3251 assert_eq!(young_child.age(), GcAge::Survival);
3252 }
3253
3254 #[test]
3255 fn minor_sweep_uses_generation_cursors_to_skip_old_tail() {
3256 let heap = Heap::new();
3257 heap.unpause();
3258 let mut old_objects = Vec::new();
3259 for _ in 0..64 {
3260 old_objects.push(heap.allocate(Cell0 {
3261 next: Cell::new(None),
3262 marker_calls: Cell::new(0),
3263 }));
3264 }
3265 heap.promote_all_to_old();
3266 let young_root = heap.allocate(Cell0 {
3267 next: Cell::new(None),
3268 marker_calls: Cell::new(0),
3269 });
3270
3271 heap.minor_collect_with_post_mark(&OneRoot(Some(young_root)), |_| {});
3272
3273 let stats = heap.last_sweep_stats();
3274 assert_eq!(stats.visited, 1);
3275 assert_eq!(stats.visited_young, 1);
3276 assert_eq!(stats.visited_old, 0);
3277 assert_eq!(heap.allgc_count(), old_objects.len() + 1);
3278 assert_eq!(young_root.age(), GcAge::Survival);
3279 }
3280
3281 #[test]
3282 fn full_sweep_corrects_generation_cursors_when_cursor_object_is_freed() {
3283 let heap = Heap::new();
3284 heap.unpause();
3285 let _old = heap.allocate(Cell0 {
3286 next: Cell::new(None),
3287 marker_calls: Cell::new(0),
3288 });
3289 heap.promote_all_to_old();
3290 assert!(heap.survival.get().is_some());
3291 assert!(heap.old1.get().is_some());
3292 assert!(heap.reallyold.get().is_some());
3293
3294 heap.full_collect(&OneRoot(None));
3295
3296 assert_eq!(heap.allgc_count(), 0);
3297 assert_eq!(heap.survival.get(), None);
3298 assert_eq!(heap.old1.get(), None);
3299 assert_eq!(heap.reallyold.get(), None);
3300 assert_eq!(heap.firstold1.get(), None);
3301 assert_eq!(heap.last_sweep_stats().freed, 1);
3302 }
3303
3304 #[test]
3305 fn full_sweep_unlinks_freed_grayagain_entries() {
3306 let heap = Heap::new();
3307 heap.unpause();
3308 let parent = heap.allocate(Cell0 {
3309 next: Cell::new(None),
3310 marker_calls: Cell::new(0),
3311 });
3312 heap.promote_all_to_old();
3313 heap.generational_backward_barrier(parent);
3314 assert_eq!(heap.grayagain_count(), 1);
3315
3316 heap.full_collect(&OneRoot(None));
3317
3318 assert_eq!(heap.allgc_count(), 0);
3319 assert_eq!(heap.grayagain_count(), 0);
3320
3321 let young = heap.allocate(Cell0 {
3322 next: Cell::new(None),
3323 marker_calls: Cell::new(0),
3324 });
3325 heap.minor_collect_with_post_mark(&OneRoot(Some(young)), |_| {});
3326 assert_eq!(young.age(), GcAge::Survival);
3327 }
3328
3329 #[test]
3330 fn grayagain_links_object_once() {
3331 let heap = Heap::new();
3332 heap.unpause();
3333 let parent = heap.allocate(Cell0 {
3334 next: Cell::new(None),
3335 marker_calls: Cell::new(0),
3336 });
3337 parent.set_age(GcAge::Old);
3338 parent.set_color(Color::Black);
3339
3340 heap.generational_backward_barrier(parent);
3341 heap.generational_backward_barrier(parent);
3342
3343 assert_eq!(heap.grayagain_count(), 1);
3344 }
3345
3346 #[test]
3347 fn grayagain_list_carries_old1_until_old() {
3348 let heap = Heap::new();
3349 heap.unpause();
3350 let survivor = heap.allocate(Cell0 {
3351 next: Cell::new(None),
3352 marker_calls: Cell::new(0),
3353 });
3354
3355 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3356 assert_eq!(survivor.age(), GcAge::Survival);
3357
3358 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3359 assert_eq!(survivor.age(), GcAge::Old1);
3360
3361 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3362 assert_eq!(survivor.age(), GcAge::Old);
3363 assert_eq!(heap.allgc_count(), 1);
3364 }
3365
3366 #[test]
3367 fn grayagain_list_carries_touched2_until_old() {
3368 let heap = Heap::new();
3369 heap.unpause();
3370 let parent = heap.allocate(Cell0 {
3371 next: Cell::new(None),
3372 marker_calls: Cell::new(0),
3373 });
3374 parent.set_age(GcAge::Old);
3375 parent.set_color(Color::Black);
3376 heap.generational_backward_barrier(parent);
3377
3378 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3379 assert_eq!(parent.age(), GcAge::Touched2);
3380
3381 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3382 assert_eq!(parent.age(), GcAge::Old);
3383 assert_eq!(parent.marker_calls.get(), 2);
3384 }
3385
3386 #[test]
3387 fn collect_traverses_cycles() {
3388 let heap = Heap::new();
3389 heap.unpause();
3390 let a = heap.allocate(Cell0 {
3391 next: Cell::new(None),
3392 marker_calls: Cell::new(0),
3393 });
3394 let b = heap.allocate(Cell0 {
3395 next: Cell::new(Some(a)),
3396 marker_calls: Cell::new(0),
3397 });
3398 a.next.set(Some(b)); heap.full_collect(&OneRoot(Some(a)));
3401 assert_eq!(a.marker_calls.get(), 1);
3402 assert_eq!(b.marker_calls.get(), 1);
3403 heap.full_collect(&OneRoot(None));
3407 assert_eq!(heap.bytes_used(), 0);
3408 }
3409
3410 #[test]
3411 fn heap_guard_stacks() {
3412 assert!(
3413 with_current_heap(|heap| heap.is_none()),
3414 "no guard initially"
3415 );
3416 let h1 = Heap::new();
3417 h1.unpause();
3418 {
3419 let _g1 = HeapGuard::push(&h1);
3420 assert!(with_current_heap(|heap| heap.is_some()));
3421 let h2 = Heap::new();
3422 h2.unpause();
3423 {
3424 let _g2 = HeapGuard::push(&h2);
3425 with_current_heap(|heap| {
3427 assert!(std::ptr::addr_eq(
3428 heap.unwrap() as *const _,
3429 &h2 as *const _,
3430 ));
3431 });
3432 }
3433 with_current_heap(|heap| {
3435 assert!(std::ptr::addr_eq(
3436 heap.unwrap() as *const _,
3437 &h1 as *const _,
3438 ));
3439 });
3440 }
3441 assert!(
3442 with_current_heap(|heap| heap.is_none()),
3443 "stack empty after all guards drop"
3444 );
3445 }
3446
3447 #[test]
3448 fn step_threshold_triggers_collect() {
3449 let heap = Heap::new();
3450 heap.unpause();
3451 let mut keeps = Vec::new();
3453 for _ in 0..64 {
3454 keeps.push(heap.allocate(Cell0 {
3458 next: Cell::new(None),
3459 marker_calls: Cell::new(0),
3460 }));
3461 }
3462 struct ManyRoots<'a>(&'a [Gc<Cell0>]);
3464 impl<'a> Trace for ManyRoots<'a> {
3465 fn trace(&self, m: &mut Marker) {
3466 for g in self.0.iter() {
3467 m.mark(*g);
3468 }
3469 }
3470 }
3471 heap.step(&ManyRoots(&keeps));
3472 assert!(heap.bytes_used() > 0);
3474 }
3475
3476 #[test]
3477 fn threshold_floored_after_collecting_tiny_heap() {
3478 let heap = Heap::new();
3479 heap.unpause();
3480 struct NoRoots;
3481 impl Trace for NoRoots {
3482 fn trace(&self, _m: &mut Marker) {}
3483 }
3484 for _ in 0..200 {
3485 heap.allocate(Cell0 {
3486 next: Cell::new(None),
3487 marker_calls: Cell::new(0),
3488 });
3489 }
3490 heap.full_collect(&NoRoots);
3491 assert!(
3492 heap.threshold_bytes() >= GC_MIN_THRESHOLD,
3493 "threshold {} collapsed below floor {}; a churning program would full-collect per allocation",
3494 heap.threshold_bytes(),
3495 GC_MIN_THRESHOLD
3496 );
3497 }
3498
3499 fn build_chain(heap: &Heap, len: usize) -> Gc<Cell0> {
3500 let head = heap.allocate(Cell0 {
3501 next: Cell::new(None),
3502 marker_calls: Cell::new(0),
3503 });
3504 let mut cur = head;
3505 for _ in 1..len {
3506 let n = heap.allocate(Cell0 {
3507 next: Cell::new(None),
3508 marker_calls: Cell::new(0),
3509 });
3510 cur.next.set(Some(n));
3511 cur = n;
3512 }
3513 head
3514 }
3515
3516 #[test]
3517 fn budget_zero_does_some_work() {
3518 let heap = Heap::new();
3519 heap.unpause();
3520 let head = build_chain(&heap, 8);
3521 let roots = OneRoot(Some(head));
3522 let budget = StepBudget::from_work(0);
3523 let outcome = heap.incremental_step_with_post_mark(&roots, budget, |_| {});
3524 assert_ne!(outcome, StepOutcome::SkippedStopped);
3525 assert_ne!(heap.gc_state(), GcState::Pause);
3526 }
3527
3528 #[test]
3529 fn run_until_state_stops_before_next_phase_work() {
3530 let heap = Heap::new();
3531 heap.unpause();
3532 let head = build_chain(&heap, 8);
3533 let roots = OneRoot(Some(head));
3534 let atomic_calls = Cell::new(0);
3535
3536 let outcome =
3537 heap.incremental_run_until_state_with_post_mark(&roots, GcState::Atomic, 1024, |_| {
3538 atomic_calls.set(atomic_calls.get() + 1)
3539 });
3540 assert_eq!(outcome, StepOutcome::InProgress);
3541 assert_eq!(heap.gc_state(), GcState::Atomic);
3542 assert_eq!(
3543 atomic_calls.get(),
3544 0,
3545 "atomic hook must not run before inspection"
3546 );
3547
3548 let outcome = heap.incremental_run_until_state_with_post_mark(
3549 &roots,
3550 GcState::SweepAllGc,
3551 1024,
3552 |_| atomic_calls.set(atomic_calls.get() + 1),
3553 );
3554 assert_eq!(outcome, StepOutcome::InProgress);
3555 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3556 assert_eq!(
3557 atomic_calls.get(),
3558 1,
3559 "entering sweep must run the atomic hook once"
3560 );
3561 }
3562
3563 #[test]
3564 fn larger_budget_drains_more_gray_than_smaller() {
3565 let small_heap = Heap::new();
3566 small_heap.unpause();
3567 let h1 = build_chain(&small_heap, 64);
3568 let r1 = OneRoot(Some(h1));
3569 let mut small_calls = 0;
3570 loop {
3571 small_calls += 1;
3572 let outcome =
3573 small_heap.incremental_step_with_post_mark(&r1, StepBudget::from_work(2), |_| {});
3574 if outcome == StepOutcome::Paused {
3575 break;
3576 }
3577 assert!(small_calls < 10_000, "did not converge");
3578 }
3579
3580 let big_heap = Heap::new();
3581 big_heap.unpause();
3582 let h2 = build_chain(&big_heap, 64);
3583 let r2 = OneRoot(Some(h2));
3584 let mut big_calls = 0;
3585 loop {
3586 big_calls += 1;
3587 let outcome =
3588 big_heap.incremental_step_with_post_mark(&r2, StepBudget::from_work(64), |_| {});
3589 if outcome == StepOutcome::Paused {
3590 break;
3591 }
3592 assert!(big_calls < 10_000, "did not converge");
3593 }
3594
3595 assert!(
3596 big_calls < small_calls,
3597 "expected big_calls={} < small_calls={}",
3598 big_calls,
3599 small_calls
3600 );
3601 }
3602
3603 #[test]
3604 fn sweep_can_pause_and_resume() {
3605 let heap = Heap::new();
3606 heap.unpause();
3607 for _ in 0..16 {
3608 let _ = heap.allocate(Cell0 {
3609 next: Cell::new(None),
3610 marker_calls: Cell::new(0),
3611 });
3612 }
3613 let roots = OneRoot(None);
3614 let bytes_before = heap.bytes_used();
3615 assert!(bytes_before > 0);
3616 let mut step_count = 0;
3617 let mut saw_in_progress_during_sweep = false;
3618 loop {
3619 step_count += 1;
3620 let outcome =
3621 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {});
3622 if heap.gc_state().is_sweep() && outcome == StepOutcome::InProgress {
3623 saw_in_progress_during_sweep = true;
3624 }
3625 if outcome == StepOutcome::Paused {
3626 break;
3627 }
3628 assert!(step_count < 10_000, "did not converge");
3629 }
3630 assert!(saw_in_progress_during_sweep, "sweep never paused mid-list");
3631 assert_eq!(heap.bytes_used(), 0);
3632 }
3633
3634 #[test]
3635 fn post_mark_runs_once_per_atomic() {
3636 let heap = Heap::new();
3637 heap.unpause();
3638 for _ in 0..32 {
3639 let _ = heap.allocate(Cell0 {
3640 next: Cell::new(None),
3641 marker_calls: Cell::new(0),
3642 });
3643 }
3644 let roots = OneRoot(None);
3645 let call_count = std::cell::Cell::new(0);
3646 let mut step_count = 0;
3647 loop {
3648 step_count += 1;
3649 let outcome =
3650 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {
3651 call_count.set(call_count.get() + 1);
3652 });
3653 if outcome == StepOutcome::Paused {
3654 break;
3655 }
3656 assert!(step_count < 10_000, "did not converge");
3657 }
3658 assert_eq!(
3659 call_count.get(),
3660 1,
3661 "post_mark must run exactly once per cycle"
3662 );
3663 }
3664
3665 #[test]
3666 fn full_collect_equivalent_to_incremental_to_pause() {
3667 let h1 = Heap::new();
3668 h1.unpause();
3669 let head1 = h1.allocate(Cell0 {
3670 next: Cell::new(None),
3671 marker_calls: Cell::new(0),
3672 });
3673 let _orphan1 = h1.allocate(Cell0 {
3674 next: Cell::new(None),
3675 marker_calls: Cell::new(0),
3676 });
3677 let _orphan2 = h1.allocate(Cell0 {
3678 next: Cell::new(None),
3679 marker_calls: Cell::new(0),
3680 });
3681 let roots1 = OneRoot(Some(head1));
3682 h1.full_collect(&roots1);
3683 let bytes_full = h1.bytes_used();
3684
3685 let h2 = Heap::new();
3686 h2.unpause();
3687 let head2 = h2.allocate(Cell0 {
3688 next: Cell::new(None),
3689 marker_calls: Cell::new(0),
3690 });
3691 let _orphan3 = h2.allocate(Cell0 {
3692 next: Cell::new(None),
3693 marker_calls: Cell::new(0),
3694 });
3695 let _orphan4 = h2.allocate(Cell0 {
3696 next: Cell::new(None),
3697 marker_calls: Cell::new(0),
3698 });
3699 let roots2 = OneRoot(Some(head2));
3700 loop {
3701 let outcome =
3702 h2.incremental_step_with_post_mark(&roots2, StepBudget::from_work(1), |_| {});
3703 if outcome == StepOutcome::Paused {
3704 break;
3705 }
3706 }
3707 assert_eq!(h2.bytes_used(), bytes_full);
3708 }
3709}
3710
3711