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