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 if self.header_from_ptr(removed).gray_listed.get() {
1618 self.unlink_grayagain(removed);
1619 }
1620 }
1621
1622 fn unlink_from_list(
1623 &self,
1624 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1625 ptr: NonNull<GcBox<dyn Trace>>,
1626 ) -> bool {
1627 let mut prev_cell = list;
1628 loop {
1629 let Some(current) = prev_cell.get() else {
1630 return false;
1631 };
1632 let header = self.header_from_ptr(current);
1633 let next = header.next.get();
1634 if std::ptr::addr_eq(current.as_ptr(), ptr.as_ptr()) {
1635 prev_cell.set(next);
1636 let prev_next_ptr = NonNull::from(prev_cell);
1637 let removed_next_ptr = NonNull::from(&self.header_from_ptr(ptr).next);
1638 if self.sweep_prev_next.get() == Some(removed_next_ptr) {
1639 self.sweep_prev_next.set(Some(prev_next_ptr));
1640 }
1641 self.correct_generation_pointers(ptr, next);
1642 header.next.set(None);
1643 return true;
1644 }
1645 prev_cell = &header.next;
1646 }
1647 }
1648
1649 fn link_to_head(
1650 &self,
1651 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1652 ptr: NonNull<GcBox<dyn Trace>>,
1653 ) {
1654 let header = self.header_from_ptr(ptr);
1655 header.next.set(list.get());
1656 list.set(Some(ptr));
1657 }
1658
1659 fn link_to_tail(
1660 &self,
1661 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1662 ptr: NonNull<GcBox<dyn Trace>>,
1663 ) {
1664 let mut last_cell = list;
1665 loop {
1666 let Some(current) = last_cell.get() else {
1667 let header = self.header_from_ptr(ptr);
1668 header.next.set(None);
1669 last_cell.set(Some(ptr));
1670 return;
1671 };
1672 last_cell = &self.header_from_ptr(current).next;
1673 }
1674 }
1675
1676 pub fn move_allgc_to_finobj(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1677 let header = self.header_from_ptr(ptr);
1678 if !header.collected.get() {
1679 return false;
1680 }
1681 if !self.unlink_from_list(&self.head, ptr) {
1682 return false;
1683 }
1684 if self.state.get().is_sweep() {
1685 header.color.set(self.current_white());
1686 }
1687 self.link_to_head(&self.finobj, ptr);
1688 true
1689 }
1690
1691 pub fn move_finobj_to_tobefnz(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1692 if !self.unlink_from_list(&self.finobj, ptr) {
1693 return false;
1694 }
1695 self.link_to_tail(&self.tobefnz, ptr);
1696 true
1697 }
1698
1699 pub fn move_tobefnz_to_allgc(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1700 let header = self.header_from_ptr(ptr);
1701 if !self.unlink_from_list(&self.tobefnz, ptr) {
1702 return false;
1703 }
1704 if self.state.get().is_sweep() {
1705 header.color.set(self.current_white());
1706 }
1707 self.link_to_head(&self.head, ptr);
1708 if header.age.get() == GcAge::Old1 {
1709 self.firstold1.set(Some(ptr));
1710 }
1711 true
1712 }
1713
1714 fn remember_minor_revisit(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1715 let header = self.header_from_ptr(ptr);
1716 if header.gray_listed.get() {
1717 return;
1718 }
1719 header.gray_next.set(self.grayagain.get());
1720 header.gray_listed.set(true);
1721 self.grayagain.set(Some(ptr));
1722 }
1723
1724 fn mark_minor_revisit_objects(&self, marker: &mut Marker) {
1725 let mut cursor = self.grayagain.get();
1726 while let Some(ptr) = cursor {
1727 let header = self.header_from_ptr(ptr);
1728 cursor = header.gray_next.get();
1729 let id = ptr.as_ptr() as *const () as usize;
1730 marker.mark_box(ptr, header, id);
1731 }
1732 }
1733
1734 fn clear_grayagain(&self) {
1735 let mut cursor = self.grayagain.get();
1736 self.grayagain.set(None);
1737 while let Some(ptr) = cursor {
1738 let header = self.header_from_ptr(ptr);
1739 cursor = header.gray_next.get();
1740 header.gray_next.set(None);
1741 header.gray_listed.set(false);
1742 }
1743 }
1744
1745 fn take_grayagain(&self) -> Vec<NonNull<GcBox<dyn Trace>>> {
1746 let mut objects = Vec::new();
1747 let mut cursor = self.grayagain.get();
1748 self.grayagain.set(None);
1749 while let Some(ptr) = cursor {
1750 let header = self.header_from_ptr(ptr);
1751 cursor = header.gray_next.get();
1752 header.gray_next.set(None);
1753 header.gray_listed.set(false);
1754 objects.push(ptr);
1755 }
1756 objects
1757 }
1758
1759 fn replace_grayagain(&self, objects: Vec<NonNull<GcBox<dyn Trace>>>) {
1760 self.clear_grayagain();
1761 for ptr in objects.into_iter().rev() {
1762 self.remember_minor_revisit(ptr);
1763 }
1764 }
1765
1766 fn unlink_grayagain(&self, removed: NonNull<GcBox<dyn Trace>>) {
1767 let keep = self
1768 .take_grayagain()
1769 .into_iter()
1770 .filter(|ptr| !std::ptr::addr_eq(ptr.as_ptr(), removed.as_ptr()))
1771 .collect();
1772 self.replace_grayagain(keep);
1773 }
1774
1775 pub fn grayagain_count(&self) -> usize {
1776 let mut count = 0usize;
1777 let mut cursor = self.grayagain.get();
1778 while let Some(ptr) = cursor {
1779 count += 1;
1780 cursor = self.header_from_ptr(ptr).gray_next.get();
1781 }
1782 count
1783 }
1784
1785 pub fn allocation_token(&self, identity: usize) -> Option<usize> {
1790 self.allocation_tokens.borrow().get(&identity).copied()
1791 }
1792
1793 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
1798 self.allocation_token(identity) == Some(token)
1799 }
1800
1801 pub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1811 where
1812 P: Trace + 'static,
1813 C: Trace + 'static,
1814 {
1815 if self.paused.get() || self.state.get().is_pause() {
1816 return;
1817 }
1818 if parent.header().color.get() != Color::Black {
1819 return;
1820 }
1821 if !child.header().color.get().is_white() {
1822 return;
1823 }
1824 child.header().color.set(Color::Gray);
1825 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
1826 if let Some(m) = m_opt.as_mut() {
1827 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
1828 m.gray_queue.push(ptr);
1829 m.visited.insert(child.identity());
1830 }
1831 }
1832 }
1833
1834 pub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1837 where
1838 P: Trace + 'static,
1839 C: Trace + 'static,
1840 {
1841 if self.paused.get() || self.state.get().is_pause() {
1842 return;
1843 }
1844 if parent.header().color.get() != Color::Black {
1845 return;
1846 }
1847 if !child.header().color.get().is_white() {
1848 return;
1849 }
1850 parent.header().color.set(Color::Gray);
1851 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
1852 if let Some(m) = m_opt.as_mut() {
1853 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
1854 m.gray_queue.push(ptr);
1855 m.visited.insert(parent.identity());
1856 }
1857 }
1858 }
1859
1860 pub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1865 where
1866 P: Trace + 'static,
1867 C: Trace + 'static,
1868 {
1869 if parent.age().is_old() && !child.age().is_old() {
1870 child.set_age(GcAge::Old0);
1871 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
1872 self.remember_minor_revisit(ptr);
1873 }
1874 self.barrier(parent, child);
1875 }
1876
1877 pub fn generational_backward_barrier<P>(&self, parent: Gc<P>)
1881 where
1882 P: Trace + 'static,
1883 {
1884 if parent.age().is_old() {
1885 parent.set_color(Color::Gray);
1886 parent.set_age(GcAge::Touched1);
1887 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
1888 self.remember_minor_revisit(ptr);
1889 }
1890 }
1891
1892 pub fn step(&self, roots: &dyn Trace) {
1896 self.step_with_post_mark(roots, |_: &mut Marker| {});
1897 }
1898
1899 pub fn step_with_post_mark<F: FnMut(&mut Marker)>(
1904 &self,
1905 roots: &dyn Trace,
1906 post_mark: F,
1907 ) {
1908 if self.paused.get() {
1909 return;
1910 }
1911 if self.bytes.get() < self.threshold.get() {
1912 return;
1913 }
1914 self.full_collect_with_post_mark(roots, post_mark);
1915 }
1916
1917 pub fn full_collect(&self, roots: &dyn Trace) {
1920 self.full_collect_with_post_mark(roots, |_: &mut Marker| {});
1921 }
1922
1923 pub fn mark_only_with_post_mark<F: FnMut(&mut Marker)>(
1928 &self,
1929 roots: &dyn Trace,
1930 mut post_mark: F,
1931 ) {
1932 if self.paused.get() {
1933 return;
1934 }
1935 let mut marker = Marker::new_reserving(self.objects.get());
1936 roots.trace(&mut marker);
1937 marker.drain_gray_queue();
1938 post_mark(&mut marker);
1939 marker.drain_gray_queue();
1940 self.last_mark_stats.set(marker.stats());
1941 }
1942
1943 pub fn promote_all_to_old(&self) {
1946 self.for_each_header(|header| {
1947 header.age.set(GcAge::Old);
1948 header.color.set(Color::Black);
1949 });
1950 self.set_all_cursors_to_head();
1951 }
1952
1953 pub fn reset_all_ages(&self) {
1956 let current_white = self.current_white();
1957 self.for_each_header(|header| {
1958 header.age.set(GcAge::New);
1959 header.color.set(current_white);
1960 });
1961 self.clear_generation_cursors();
1962 }
1963
1964 pub fn minor_collect_with_post_mark<F: FnMut(&mut Marker)>(
1971 &self,
1972 roots: &dyn Trace,
1973 mut post_mark: F,
1974 ) {
1975 if self.paused.get() {
1976 return;
1977 }
1978 if !self.state.get().is_pause() {
1979 self.abort_cycle();
1980 }
1981
1982 self.state.set(GcState::Propagate);
1983 let mut marker = Marker::new_minor_reserving(self.objects.get());
1984 self.last_sweep_stats.set(SweepStats::default());
1985 self.mark_minor_revisit_objects(&mut marker);
1986 roots.trace(&mut marker);
1987 marker.drain_gray_queue();
1988
1989 self.state.set(GcState::EnterAtomic);
1990 self.state.set(GcState::Atomic);
1991 post_mark(&mut marker);
1992 marker.drain_gray_queue();
1993 self.last_mark_stats.set(marker.stats());
1994
1995 self.state.set(GcState::SweepAllGc);
1996 self.sweep_young();
1997 *self.marker.borrow_mut() = None;
1998 self.sweep_prev_next.set(None);
1999 self.state.set(GcState::Pause);
2000 self.collections.set(self.collections.get() + 1);
2001 self.minor_collections.set(self.minor_collections.get() + 1);
2002 }
2003
2004 pub fn full_collect_with_post_mark<F: FnMut(&mut Marker)>(
2011 &self,
2012 roots: &dyn Trace,
2013 mut post_mark: F,
2014 ) {
2015 if self.paused.get() {
2016 return;
2017 }
2018 if !self.state.get().is_pause() {
2019 self.abort_cycle();
2020 }
2021 self.full_collections.set(self.full_collections.get() + 1);
2022 let unlimited = StepBudget { remaining_work: isize::MAX, max_credit: isize::MAX };
2023 loop {
2024 let outcome = self.incremental_step_with_post_mark(roots, unlimited, &mut post_mark);
2025 if matches!(outcome, StepOutcome::Paused | StepOutcome::SkippedStopped) {
2026 break;
2027 }
2028 }
2029 }
2030
2031 pub fn incremental_step_with_post_mark<F: FnMut(&mut Marker)>(
2046 &self,
2047 roots: &dyn Trace,
2048 mut budget: StepBudget,
2049 mut post_mark: F,
2050 ) -> StepOutcome {
2051 if self.paused.get() {
2052 return StepOutcome::SkippedStopped;
2053 }
2054 self.run_budgeted(roots, &mut budget, &mut post_mark);
2055 if self.state.get().is_pause() {
2056 StepOutcome::Paused
2057 } else {
2058 StepOutcome::InProgress
2059 }
2060 }
2061
2062 fn run_budgeted(
2063 &self,
2064 roots: &dyn Trace,
2065 budget: &mut StepBudget,
2066 post_mark: &mut dyn FnMut(&mut Marker),
2067 ) -> bool {
2068 self.run_budgeted_until(roots, budget, post_mark, None)
2069 }
2070
2071 fn run_budgeted_until(
2072 &self,
2073 roots: &dyn Trace,
2074 budget: &mut StepBudget,
2075 post_mark: &mut dyn FnMut(&mut Marker),
2076 stop_at: Option<GcState>,
2077 ) -> bool {
2078 let mut did_work = false;
2079 loop {
2080 if stop_at == Some(self.state.get()) {
2081 return did_work;
2082 }
2083 if budget.remaining_work <= -budget.max_credit {
2084 return did_work;
2085 }
2086 match self.state.get() {
2087 GcState::Pause => {
2088 self.start_cycle(roots);
2089 self.state.set(GcState::Propagate);
2090 budget.remaining_work -= 1;
2091 did_work = true;
2092 if stop_at == Some(GcState::Propagate) {
2093 return did_work;
2094 }
2095 }
2096 GcState::Propagate => {
2097 let work = self.drain_gray_budgeted(budget.remaining_work.max(1));
2098 budget.remaining_work -= work as isize;
2099 did_work = did_work || work > 0;
2100 let empty = {
2101 let m = self.marker.borrow();
2102 m.as_ref().map(|m| m.gray_queue.is_empty()).unwrap_or(true)
2103 };
2104 if empty {
2105 self.state.set(GcState::EnterAtomic);
2106 if stop_at == Some(GcState::EnterAtomic) {
2107 return did_work;
2108 }
2109 } else if budget.remaining_work <= 0 {
2110 return did_work;
2111 }
2112 }
2113 GcState::EnterAtomic => {
2114 self.state.set(GcState::Atomic);
2115 budget.remaining_work -= 1;
2116 did_work = true;
2117 if stop_at == Some(GcState::Atomic) || budget.remaining_work <= 0 {
2118 return did_work;
2119 }
2120 }
2121 GcState::Atomic => {
2122 self.run_atomic(post_mark);
2123 self.state.set(GcState::SweepAllGc);
2124 budget.remaining_work -= 1;
2125 did_work = true;
2126 if stop_at == Some(GcState::SweepAllGc) {
2127 return did_work;
2128 }
2129 }
2130 GcState::SweepAllGc => {
2131 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2132 budget.remaining_work -= work as isize;
2133 did_work = did_work || work > 0;
2134 if self.sweep_prev_next.get().is_none() {
2135 self.state.set(GcState::SweepFinObj);
2136 self.sweep_prev_next.set(Some(NonNull::from(&self.finobj)));
2137 if stop_at == Some(GcState::SweepFinObj) {
2138 return did_work;
2139 }
2140 } else if budget.remaining_work <= 0 {
2141 return did_work;
2142 }
2143 }
2144 GcState::SweepFinObj => {
2145 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2146 budget.remaining_work -= work as isize;
2147 did_work = did_work || work > 0;
2148 if self.sweep_prev_next.get().is_none() {
2149 self.state.set(GcState::SweepToBeFnz);
2150 self.sweep_prev_next.set(Some(NonNull::from(&self.tobefnz)));
2151 if stop_at == Some(GcState::SweepToBeFnz) {
2152 return did_work;
2153 }
2154 } else if budget.remaining_work <= 0 {
2155 return did_work;
2156 }
2157 }
2158 GcState::SweepToBeFnz => {
2159 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2160 budget.remaining_work -= work as isize;
2161 did_work = did_work || work > 0;
2162 if self.sweep_prev_next.get().is_none() {
2163 self.state.set(GcState::SweepEnd);
2164 if stop_at == Some(GcState::SweepEnd) {
2165 return did_work;
2166 }
2167 } else if budget.remaining_work <= 0 {
2168 return did_work;
2169 }
2170 }
2171 GcState::SweepEnd => {
2172 self.state.set(GcState::CallFin);
2173 budget.remaining_work -= 1;
2174 did_work = true;
2175 if stop_at == Some(GcState::CallFin) || budget.remaining_work <= 0 {
2176 return did_work;
2177 }
2178 }
2179 GcState::CallFin => {
2180 self.finish_cycle();
2181 self.state.set(GcState::Pause);
2182 if stop_at == Some(GcState::Pause) {
2183 return did_work;
2184 }
2185 return did_work;
2186 }
2187 }
2188 }
2189 }
2190
2191 pub fn incremental_run_until_state_with_post_mark<F: FnMut(&mut Marker)>(
2196 &self,
2197 roots: &dyn Trace,
2198 target: GcState,
2199 max_work: isize,
2200 mut post_mark: F,
2201 ) -> StepOutcome {
2202 if self.paused.get() {
2203 return StepOutcome::SkippedStopped;
2204 }
2205 let work = max_work.max(1);
2206 let mut budget = StepBudget {
2207 remaining_work: work,
2208 max_credit: work,
2209 };
2210 self.run_budgeted_until(roots, &mut budget, &mut post_mark, Some(target));
2211 if self.state.get().is_pause() {
2212 StepOutcome::Paused
2213 } else {
2214 StepOutcome::InProgress
2215 }
2216 }
2217
2218 fn start_cycle(&self, roots: &dyn Trace) {
2219 self.flip_current_white();
2220 let dead_white = self.other_white();
2221 self.for_each_header(|header| {
2222 header.color.set(dead_white);
2223 });
2224 let mut marker = Marker::new_reserving(self.objects.get());
2225 roots.trace(&mut marker);
2226 *self.marker.borrow_mut() = Some(marker);
2227 self.sweep_prev_next.set(None);
2228 }
2229
2230 fn drain_gray_budgeted(&self, max_units: isize) -> usize {
2231 let mut m_opt = self.marker.borrow_mut();
2232 let marker = match m_opt.as_mut() {
2233 Some(m) => m,
2234 None => return 0,
2235 };
2236 let mut work = 0usize;
2237 let mut budget = max_units;
2238 while budget > 0 {
2239 let next = match marker.gray_queue.pop() {
2240 Some(p) => p,
2241 None => break,
2242 };
2243 unsafe {
2244 let bx = next.as_ref();
2245 marker.stats.traced += 1;
2246 if bx.header.age.get().is_old() {
2247 marker.stats.traced_old += 1;
2248 } else {
2249 marker.stats.traced_young += 1;
2250 }
2251 bx.header.color.set(Color::Black);
2252 bx.value.trace(marker);
2253 }
2254 work += 1;
2255 budget -= 1;
2256 }
2257 work
2258 }
2259
2260 fn run_atomic(&self, post_mark: &mut dyn FnMut(&mut Marker)) {
2261 let mut m_opt = self.marker.borrow_mut();
2262 if let Some(marker) = m_opt.as_mut() {
2263 marker.drain_gray_queue();
2264 post_mark(marker);
2265 marker.drain_gray_queue();
2266 }
2267 self.sweep_prev_next.set(Some(NonNull::from(&self.head)));
2268 self.last_sweep_stats.set(SweepStats::default());
2269 }
2270
2271 fn sweep_budgeted(&self, max_units: isize) -> usize {
2272 let mut work = 0usize;
2273 let mut budget = max_units;
2274 let mut freed_bytes = 0usize;
2275 let mut stats = SweepStats::default();
2276 let current_white = self.current_white();
2277 let dead_white = self.other_white();
2278 let mut prev_next_ptr = match self.sweep_prev_next.get() {
2279 Some(p) => p,
2280 None => return 0,
2281 };
2282 while budget > 0 {
2283 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2284 let cursor = prev_cell.get();
2285 let ptr = match cursor {
2286 Some(p) => p,
2287 None => {
2288 self.sweep_prev_next.set(None);
2289 break;
2290 }
2291 };
2292 let header = self.header_from_ptr(ptr);
2293 let next = header.next.get();
2294 let age = header.age.get();
2295 stats.record_visit(age);
2296 let color = header.color.get();
2297 if color == dead_white {
2298 prev_cell.set(next);
2299 let size = header.size.get();
2300 freed_bytes += size;
2301 stats.record_free(size);
2302 self.correct_generation_pointers(ptr, next);
2303 self.allocation_tokens
2304 .borrow_mut()
2305 .remove(&(ptr.as_ptr() as *const () as usize));
2306 self.objects.set(self.objects.get().saturating_sub(1));
2307 unsafe {
2308 let _ = Box::from_raw(ptr.as_ptr());
2309 }
2310 } else {
2311 if matches!(color, Color::Black | Color::Gray) {
2312 header.color.set(current_white);
2313 }
2314 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2315 self.sweep_prev_next.set(Some(prev_next_ptr));
2316 }
2317 work += 1;
2318 budget -= 1;
2319 }
2320 if freed_bytes > 0 {
2321 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2322 }
2323 if stats.visited > 0 {
2324 let mut total = self.last_sweep_stats.get();
2325 total.add(stats);
2326 self.last_sweep_stats.set(total);
2327 }
2328 work
2329 }
2330
2331 fn push_next_revisit(
2332 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2333 seen: &mut IdentityHashSet,
2334 ptr: NonNull<GcBox<dyn Trace>>,
2335 age: GcAge,
2336 ) {
2337 if matches!(
2338 age,
2339 GcAge::Old0 | GcAge::Old1 | GcAge::Touched1 | GcAge::Touched2
2340 ) {
2341 let id = ptr.as_ptr() as *const () as usize;
2342 if seen.insert(id) {
2343 next_revisit.push(ptr);
2344 }
2345 }
2346 }
2347
2348 fn sweep_young_range(
2349 &self,
2350 mut prev_next_ptr: NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2351 limit: Option<NonNull<GcBox<dyn Trace>>>,
2352 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2353 next_revisit_seen: &mut IdentityHashSet,
2354 processed: &mut Option<OldRevisitTracker>,
2355 firstold1: &mut Option<NonNull<GcBox<dyn Trace>>>,
2356 freed_bytes: &mut usize,
2357 stats: &mut SweepStats,
2358 ) -> (
2359 NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2360 Option<NonNull<GcBox<dyn Trace>>>,
2361 ) {
2362 let current_white = self.current_white();
2363 loop {
2364 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2365 let Some(ptr) = prev_cell.get() else {
2366 return (prev_next_ptr, None);
2367 };
2368 if Some(ptr) == limit {
2369 return (prev_next_ptr, Some(ptr));
2370 }
2371 let header = self.header_from_ptr(ptr);
2372 let next = header.next.get();
2373 let age = header.age.get();
2374 stats.record_visit(age);
2375 if let Some(processed) = processed.as_mut() {
2376 processed.record_processed(ptr.as_ptr() as *const () as usize);
2377 }
2378 if header.color.get().is_white() && !age.is_old() {
2379 prev_cell.set(next);
2380 let size = header.size.get();
2381 *freed_bytes += size;
2382 stats.record_free(size);
2383 self.correct_generation_pointers(ptr, next);
2384 self.allocation_tokens
2385 .borrow_mut()
2386 .remove(&(ptr.as_ptr() as *const () as usize));
2387 self.objects.set(self.objects.get().saturating_sub(1));
2388 unsafe {
2389 let _ = Box::from_raw(ptr.as_ptr());
2390 }
2391 continue;
2392 }
2393
2394 if !header.color.get().is_white() {
2395 let next_age = age.next_after_minor();
2396 header.age.set(next_age);
2397 if next_age == GcAge::Old1 && firstold1.is_none() {
2398 *firstold1 = Some(ptr);
2399 }
2400 match age {
2401 GcAge::New => header.color.set(current_white),
2402 GcAge::Touched1 | GcAge::Touched2 => header.color.set(Color::Black),
2403 _ => {}
2404 }
2405 Self::push_next_revisit(next_revisit, next_revisit_seen, ptr, next_age);
2406 }
2407 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2408 }
2409 }
2410
2411 fn sweep_young(&self) {
2412 let mut freed_bytes = 0usize;
2413 let mut next_revisit = Vec::new();
2414 let mut next_revisit_seen = IdentityHashSet::default();
2415 let mut firstold1 = None;
2416 let mut stats = SweepStats::default();
2417 let old_revisit = self.take_grayagain();
2418 let mut processed = OldRevisitTracker::new(&old_revisit);
2419 let survival = self.survival.get();
2420 let old1 = self.old1.get();
2421
2422 let (psurvival, new_old1) = self.sweep_young_range(
2423 NonNull::from(&self.head),
2424 survival,
2425 &mut next_revisit,
2426 &mut next_revisit_seen,
2427 &mut processed,
2428 &mut firstold1,
2429 &mut freed_bytes,
2430 &mut stats,
2431 );
2432 self.sweep_young_range(
2433 psurvival,
2434 old1,
2435 &mut next_revisit,
2436 &mut next_revisit_seen,
2437 &mut processed,
2438 &mut firstold1,
2439 &mut freed_bytes,
2440 &mut stats,
2441 );
2442
2443 let finobjsur = self.finobjsur.get();
2444 let finobjold1 = self.finobjold1.get();
2445 let mut dummy_firstold1 = None;
2446 let (pfinobjsur, new_finobjold1) = self.sweep_young_range(
2447 NonNull::from(&self.finobj),
2448 finobjsur,
2449 &mut next_revisit,
2450 &mut next_revisit_seen,
2451 &mut processed,
2452 &mut dummy_firstold1,
2453 &mut freed_bytes,
2454 &mut stats,
2455 );
2456 self.sweep_young_range(
2457 pfinobjsur,
2458 finobjold1,
2459 &mut next_revisit,
2460 &mut next_revisit_seen,
2461 &mut processed,
2462 &mut dummy_firstold1,
2463 &mut freed_bytes,
2464 &mut stats,
2465 );
2466 self.sweep_young_range(
2467 NonNull::from(&self.tobefnz),
2468 None,
2469 &mut next_revisit,
2470 &mut next_revisit_seen,
2471 &mut processed,
2472 &mut dummy_firstold1,
2473 &mut freed_bytes,
2474 &mut stats,
2475 );
2476
2477 if let Some(processed) = processed.as_mut() {
2478 processed.finish();
2479 }
2480
2481 for ptr in old_revisit {
2482 let id = ptr.as_ptr() as *const () as usize;
2483 if processed.as_ref().is_some_and(|processed| processed.was_processed(id)) {
2484 continue;
2485 }
2486 stats.revisit += 1;
2487 let header = self.header_from_ptr(ptr);
2488 if header.color.get().is_white() {
2489 continue;
2490 }
2491 let age = header.age.get();
2492 let next_age = age.next_after_minor();
2493 header.age.set(next_age);
2494 if next_age == GcAge::Old1 && firstold1.is_none() {
2495 firstold1 = Some(ptr);
2496 }
2497 if matches!(age, GcAge::Touched1 | GcAge::Touched2) {
2498 header.color.set(Color::Black);
2499 }
2500 Self::push_next_revisit(&mut next_revisit, &mut next_revisit_seen, ptr, next_age);
2501 }
2502
2503 if freed_bytes > 0 {
2504 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2505 }
2506 self.replace_grayagain(next_revisit);
2507 self.reallyold.set(old1);
2508 self.old1.set(new_old1);
2509 self.survival.set(self.head.get());
2510 self.firstold1.set(firstold1);
2511 self.finobjrold.set(finobjold1);
2512 self.finobjold1.set(new_finobjold1);
2513 self.finobjsur.set(self.finobj.get());
2514 self.last_sweep_stats.set(stats);
2515 }
2516
2517 fn finish_cycle(&self) {
2518 let stats = self
2519 .marker
2520 .borrow()
2521 .as_ref()
2522 .map(|marker| marker.stats())
2523 .unwrap_or_default();
2524 self.last_mark_stats.set(stats);
2525 *self.marker.borrow_mut() = None;
2526 self.sweep_prev_next.set(None);
2527 let next = self
2528 .bytes
2529 .get()
2530 .saturating_mul(self.pause_multiplier.get())
2531 / 100;
2532 self.threshold.set(next.max(GC_MIN_THRESHOLD));
2533 self.collections.set(self.collections.get() + 1);
2534 }
2535
2536 pub fn finish_callfin_phase(&self) -> bool {
2539 if self.state.get() != GcState::CallFin {
2540 return false;
2541 }
2542 self.finish_cycle();
2543 self.state.set(GcState::Pause);
2544 true
2545 }
2546
2547 fn abort_cycle(&self) {
2548 if !self.state.get().is_pause() {
2549 *self.marker.borrow_mut() = None;
2550 self.sweep_prev_next.set(None);
2551 let current_white = self.current_white();
2552 self.for_each_header(|header| {
2553 header.color.set(current_white);
2554 });
2555 self.state.set(GcState::Pause);
2556 }
2557 }
2558
2559 pub fn gc_state(&self) -> GcState {
2561 self.state.get()
2562 }
2563
2564 pub fn allgc_count(&self) -> usize {
2566 self.objects.get()
2567 }
2568
2569 pub fn type_name_count(&self, mut predicate: impl FnMut(&'static str) -> bool) -> usize {
2573 let mut count = 0usize;
2574 self.for_each_header(|header| {
2575 if predicate(header.type_name) {
2576 count += 1;
2577 }
2578 });
2579 count
2580 }
2581
2582 pub fn drop_all(&self) {
2586 *self.marker.borrow_mut() = None;
2587 self.sweep_prev_next.set(None);
2588 self.clear_generation_cursors();
2589 self.state.set(GcState::Pause);
2590 self.allocation_tokens.borrow_mut().clear();
2591 self.drop_list(&self.head);
2592 self.drop_list(&self.finobj);
2593 self.drop_list(&self.tobefnz);
2594 self.bytes.set(0);
2595 self.objects.set(0);
2596 }
2597
2598 fn drop_list(&self, list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>) {
2599 let mut cursor = list.get();
2600 list.set(None);
2601 while let Some(ptr) = cursor {
2602 let next = unsafe {
2604 let next = (*ptr.as_ptr()).header.next.get();
2605 let _ = Box::from_raw(ptr.as_ptr());
2606 next
2607 };
2608 cursor = next;
2609 }
2610 }
2611}
2612
2613impl Drop for Heap {
2614 fn drop(&mut self) {
2615 self.drop_all();
2616 }
2617}
2618
2619#[cfg(test)]
2624mod tests {
2625 use super::*;
2626
2627 struct Cell0 {
2629 next: Cell<Option<Gc<Cell0>>>,
2630 marker_calls: Cell<usize>,
2631 }
2632
2633 impl Trace for Cell0 {
2634 fn trace(&self, m: &mut Marker) {
2635 self.marker_calls.set(self.marker_calls.get() + 1);
2636 if let Some(n) = self.next.get() {
2637 m.mark(n);
2638 }
2639 }
2640 }
2641
2642 struct OneRoot(Option<Gc<Cell0>>);
2644 impl Trace for OneRoot {
2645 fn trace(&self, m: &mut Marker) {
2646 if let Some(g) = self.0 {
2647 m.mark(g);
2648 }
2649 }
2650 }
2651
2652 struct TwoRoots {
2653 first: Option<Gc<Cell0>>,
2654 second: Option<Gc<Cell0>>,
2655 }
2656
2657 impl Trace for TwoRoots {
2658 fn trace(&self, m: &mut Marker) {
2659 if let Some(g) = self.first {
2660 m.mark(g);
2661 }
2662 if let Some(g) = self.second {
2663 m.mark(g);
2664 }
2665 }
2666 }
2667
2668 #[derive(Clone)]
2669 struct FinalizerCell {
2670 id: usize,
2671 age: GcAge,
2672 finalized: std::rc::Rc<Cell<bool>>,
2673 }
2674
2675 impl FinalizerCell {
2676 fn new(id: usize) -> Self {
2677 Self {
2678 id,
2679 age: GcAge::New,
2680 finalized: std::rc::Rc::new(Cell::new(false)),
2681 }
2682 }
2683 }
2684
2685 impl FinalizerEntry for FinalizerCell {
2686 fn identity(&self) -> usize {
2687 self.id
2688 }
2689
2690 fn age(&self) -> GcAge {
2691 self.age
2692 }
2693
2694 fn is_finalized(&self) -> bool {
2695 self.finalized.get()
2696 }
2697
2698 fn set_finalized(&self, finalized: bool) {
2699 self.finalized.set(finalized);
2700 }
2701 }
2702
2703 fn finalizer_ids(objects: &[FinalizerCell]) -> Vec<usize> {
2704 objects.iter().map(|object| object.id).collect()
2705 }
2706
2707 #[derive(Clone)]
2708 struct WeakCell {
2709 id: usize,
2710 live: bool,
2711 kind: WeakListKind,
2712 }
2713
2714 impl WeakEntry for WeakCell {
2715 type Strong = usize;
2716
2717 fn identity(&self) -> usize {
2718 self.id
2719 }
2720
2721 fn list_kind(&self) -> WeakListKind {
2722 self.kind
2723 }
2724
2725 fn upgrade(&self) -> Option<Self::Strong> {
2726 self.live.then_some(self.id)
2727 }
2728 }
2729
2730 #[test]
2731 fn weak_registry_dedups_snapshots_and_retains_live_ids() {
2732 let mut registry = WeakRegistry::default();
2733 registry.push_unique(WeakCell {
2734 id: 1,
2735 live: true,
2736 kind: WeakListKind::WeakValues,
2737 });
2738 registry.push_unique(WeakCell {
2739 id: 1,
2740 live: true,
2741 kind: WeakListKind::Ephemeron,
2742 });
2743 registry.push_unique(WeakCell {
2744 id: 2,
2745 live: false,
2746 kind: WeakListKind::AllWeak,
2747 });
2748 registry.push_unique(WeakCell {
2749 id: 3,
2750 live: true,
2751 kind: WeakListKind::WeakValues,
2752 });
2753
2754 let stats = registry.stats();
2755 assert_eq!(stats.weak_values, 1);
2756 assert_eq!(stats.ephemeron, 1);
2757 assert_eq!(stats.all_weak, 1);
2758
2759 let snapshot = registry.live_snapshot();
2760 assert_eq!(snapshot, vec![3, 1]);
2761 assert_eq!(
2762 registry.stats(),
2763 WeakRegistryStats {
2764 tracked: 3,
2765 snapshot_live: 2,
2766 snapshot_dead: 1,
2767 retained: 2,
2768 weak_values: 1,
2769 ephemeron: 1,
2770 all_weak: 0,
2771 }
2772 );
2773
2774 let keep: std::collections::HashSet<usize> = [3usize].into_iter().collect();
2775 registry.retain_identities(&keep);
2776 assert_eq!(registry.len(), 1);
2777 assert_eq!(registry.stats().retained, 1);
2778 assert_eq!(registry.stats().weak_values, 1);
2779 registry.remove_identity(3);
2780 assert_eq!(registry.len(), 0);
2781 }
2782
2783 #[test]
2784 fn finalizer_registry_tracks_generational_cohorts() {
2785 let mut registry = FinalizerRegistry::default();
2786 registry.push_pending_unique(FinalizerCell::new(1));
2787 registry.push_pending_unique(FinalizerCell::new(2));
2788
2789 let stats = registry.stats();
2790 assert_eq!(stats.finobj_new, 2);
2791 assert_eq!(stats.finobj_survival, 0);
2792 assert_eq!(stats.finobj_old1, 0);
2793 assert_eq!(stats.finobj_reallyold, 0);
2794 assert_eq!(stats.finobj_minor_scan, 2);
2795
2796 registry.finish_minor_collection();
2797 let stats = registry.stats();
2798 assert_eq!(stats.finobj_new, 0);
2799 assert_eq!(stats.finobj_survival, 2);
2800 assert_eq!(stats.finobj_old1, 0);
2801 assert_eq!(stats.finobj_reallyold, 0);
2802 assert_eq!(stats.finobj_minor_scan, 2);
2803
2804 registry.push_pending_unique(FinalizerCell::new(3));
2805 registry.finish_minor_collection();
2806 let stats = registry.stats();
2807 assert_eq!(stats.finobj_new, 0);
2808 assert_eq!(stats.finobj_survival, 1);
2809 assert_eq!(stats.finobj_old1, 2);
2810 assert_eq!(stats.finobj_reallyold, 0);
2811 assert_eq!(stats.finobj_minor_scan, 1);
2812
2813 registry.finish_minor_collection();
2814 let stats = registry.stats();
2815 assert_eq!(stats.finobj_new, 0);
2816 assert_eq!(stats.finobj_survival, 0);
2817 assert_eq!(stats.finobj_old1, 1);
2818 assert_eq!(stats.finobj_reallyold, 2);
2819 assert_eq!(stats.finobj_minor_scan, 0);
2820 }
2821
2822 #[test]
2823 fn finalizer_registry_minor_snapshot_uses_cohort_boundaries() {
2824 let mut registry = FinalizerRegistry::default();
2825 registry.push_pending_unique(FinalizerCell::new(1));
2826 registry.push_pending_unique(FinalizerCell::new(2));
2827 registry.push_pending_unique(FinalizerCell::new(3));
2828 registry.finish_minor_collection();
2829 registry.finish_minor_collection();
2830 registry.push_pending_unique(FinalizerCell::new(4));
2831 registry.push_pending_unique(FinalizerCell::new(5));
2832
2833 assert_eq!(
2834 finalizer_ids(®istry.pending_minor_snapshot()),
2835 vec![4, 5],
2836 "minor finalizer scan must skip the old1/reallyold prefix"
2837 );
2838
2839 registry.push_to_be_finalized(FinalizerCell::new(99));
2840 registry.promote_pending_to_finalized(vec![
2841 FinalizerCell::new(1),
2842 FinalizerCell::new(2),
2843 FinalizerCell::new(4),
2844 ]);
2845
2846 let stats = registry.stats();
2847 assert_eq!(stats.finobj_old1, 1);
2848 assert_eq!(stats.finobj_new, 1);
2849 assert_eq!(stats.finobj_minor_scan, 1);
2850 assert_eq!(finalizer_ids(registry.pending()), vec![3, 5]);
2851 assert_eq!(
2852 finalizer_ids(registry.to_be_finalized()),
2853 vec![99, 4, 2, 1],
2854 "new to-be-finalized batches append behind older queued finalizers"
2855 );
2856 }
2857
2858 #[test]
2859 fn finalizer_registry_marks_and_clears_finalized_bit() {
2860 let mut registry = FinalizerRegistry::default();
2861 let object = FinalizerCell::new(1);
2862
2863 assert!(!object.is_finalized());
2864 registry.push_pending_unique(object.clone());
2865 assert!(object.is_finalized());
2866
2867 registry.push_pending_unique(object.clone());
2868 assert_eq!(registry.pending_len(), 1);
2869
2870 registry.promote_pending_to_finalized(vec![object.clone()]);
2871 assert!(object.is_finalized());
2872 assert_eq!(registry.pending_len(), 0);
2873 assert_eq!(registry.to_be_finalized_len(), 1);
2874
2875 let popped = registry.pop_to_be_finalized().unwrap();
2876 assert_eq!(popped.id, 1);
2877 assert!(!object.is_finalized());
2878
2879 registry.push_pending_unique(object.clone());
2880 assert!(object.is_finalized());
2881 assert_eq!(registry.pending_len(), 1);
2882 }
2883
2884 #[test]
2885 fn alloc_and_drop_all() {
2886 let heap = Heap::new();
2887 heap.unpause();
2888 let _a = heap.allocate(Cell0 {
2889 next: Cell::new(None),
2890 marker_calls: Cell::new(0),
2891 });
2892 let _b = heap.allocate(Cell0 {
2893 next: Cell::new(None),
2894 marker_calls: Cell::new(0),
2895 });
2896 assert!(heap.bytes_used() > 0);
2897 heap.drop_all();
2898 assert_eq!(heap.bytes_used(), 0);
2899 }
2900
2901 fn list_len(heap: &Heap, mut cursor: Option<NonNull<GcBox<dyn Trace>>>) -> usize {
2902 let mut count = 0usize;
2903 while let Some(ptr) = cursor {
2904 count += 1;
2905 cursor = heap.header_from_ptr(ptr).next.get();
2906 }
2907 count
2908 }
2909
2910 #[test]
2911 fn finalizer_intrusive_lists_sweep_and_drop() {
2912 let heap = Heap::new();
2913 heap.unpause();
2914 let _normal = heap.allocate(Cell0 {
2915 next: Cell::new(None),
2916 marker_calls: Cell::new(0),
2917 });
2918 let finobj = heap.allocate(Cell0 {
2919 next: Cell::new(None),
2920 marker_calls: Cell::new(0),
2921 });
2922 let tobefnz = heap.allocate(Cell0 {
2923 next: Cell::new(None),
2924 marker_calls: Cell::new(0),
2925 });
2926
2927 assert!(heap.move_allgc_to_finobj(finobj.as_trace_ptr()));
2928 assert!(heap.move_allgc_to_finobj(tobefnz.as_trace_ptr()));
2929 assert!(heap.move_finobj_to_tobefnz(tobefnz.as_trace_ptr()));
2930 assert_eq!(list_len(&heap, heap.head.get()), 1);
2931 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
2932 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
2933 assert_eq!(heap.allgc_count(), 3);
2934
2935 heap.full_collect(&TwoRoots { first: Some(finobj), second: Some(tobefnz) });
2936 assert_eq!(list_len(&heap, heap.head.get()), 0);
2937 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
2938 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
2939 assert_eq!(heap.allgc_count(), 2);
2940
2941 assert!(heap.move_tobefnz_to_allgc(tobefnz.as_trace_ptr()));
2942 heap.full_collect(&OneRoot(Some(tobefnz)));
2943 assert_eq!(list_len(&heap, heap.head.get()), 1);
2944 assert_eq!(list_len(&heap, heap.finobj.get()), 0);
2945 assert_eq!(list_len(&heap, heap.tobefnz.get()), 0);
2946 assert_eq!(heap.allgc_count(), 1);
2947
2948 heap.drop_all();
2949 assert_eq!(heap.bytes_used(), 0);
2950 }
2951
2952 #[test]
2953 fn account_buffer_refunded_on_sweep() {
2954 let heap = Heap::new();
2955 heap.unpause();
2956 let baseline = heap.bytes_used();
2957 {
2958 let a = heap.allocate(Cell0 {
2959 next: Cell::new(None),
2960 marker_calls: Cell::new(0),
2961 });
2962 assert!(heap.bytes_used() > baseline);
2963 a.account_buffer(&heap, 4096);
2964 assert_eq!(a.header().size.get(), std::mem::size_of::<GcBox<Cell0>>() + 4096);
2965 }
2966 heap.full_collect(&OneRoot(None));
2969 assert_eq!(
2970 heap.bytes_used(),
2971 baseline,
2972 "account_buffer charge must be fully refunded by sweep"
2973 );
2974 }
2975
2976 #[test]
2977 fn account_buffer_noop_on_uncollected() {
2978 let heap = Heap::new();
2979 heap.unpause();
2980 let g = Gc::new_uncollected(Cell0 {
2981 next: Cell::new(None),
2982 marker_calls: Cell::new(0),
2983 });
2984 let before = heap.bytes_used();
2985 g.account_buffer(&heap, 8192);
2986 assert_eq!(heap.bytes_used(), before, "uncollected box must not charge the pacer");
2987 }
2988
2989 #[test]
2990 fn collect_unreachable_frees_bytes() {
2991 let heap = Heap::new();
2992 heap.unpause();
2993 let _a = heap.allocate(Cell0 {
2994 next: Cell::new(None),
2995 marker_calls: Cell::new(0),
2996 });
2997 let bytes_before = heap.bytes_used();
2998 assert!(bytes_before > 0);
2999 heap.full_collect(&OneRoot(None));
3001 assert_eq!(heap.bytes_used(), 0);
3002 assert_eq!(heap.collections(), 1);
3003 }
3004
3005 #[test]
3006 fn collect_keeps_reachable() {
3007 let heap = Heap::new();
3008 heap.unpause();
3009 let root = heap.allocate(Cell0 {
3010 next: Cell::new(None),
3011 marker_calls: Cell::new(0),
3012 });
3013 let bytes_before = heap.bytes_used();
3014 heap.full_collect(&OneRoot(Some(root)));
3015 assert_eq!(heap.bytes_used(), bytes_before);
3016 assert_eq!(root.marker_calls.get(), 1);
3017 }
3018
3019 #[test]
3020 fn allocations_start_new_and_white() {
3021 let heap = Heap::new();
3022 heap.unpause();
3023 let obj = heap.allocate(Cell0 {
3024 next: Cell::new(None),
3025 marker_calls: Cell::new(0),
3026 });
3027 assert_eq!(obj.age(), GcAge::New);
3028 assert!(obj.color().is_white());
3029 }
3030
3031 #[test]
3032 fn allocation_tokens_track_exact_live_box() {
3033 let heap = Heap::new();
3034 heap.unpause();
3035 let obj = heap.allocate(Cell0 {
3036 next: Cell::new(None),
3037 marker_calls: Cell::new(0),
3038 });
3039 let id = obj.identity();
3040 let token = heap
3041 .allocation_token(id)
3042 .expect("heap allocation should get a token");
3043
3044 assert!(heap.contains_allocation(id, token));
3045 assert!(!heap.contains_allocation(id, token + 1));
3046
3047 heap.full_collect(&OneRoot(None));
3048 assert_eq!(heap.allocation_token(id), None);
3049 assert!(!heap.contains_allocation(id, token));
3050 }
3051
3052 #[test]
3053 fn allocation_during_incremental_sweep_survives_current_cycle() {
3054 let heap = Heap::new();
3055 heap.unpause();
3056 let old_dead = heap.allocate(Cell0 {
3057 next: Cell::new(None),
3058 marker_calls: Cell::new(0),
3059 });
3060 let old_id = old_dead.identity();
3061
3062 let outcome = heap.incremental_run_until_state_with_post_mark(
3063 &OneRoot(None),
3064 GcState::SweepAllGc,
3065 1024,
3066 |_| {},
3067 );
3068 assert_eq!(outcome, StepOutcome::InProgress);
3069 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3070
3071 let new_during_sweep = heap.allocate(Cell0 {
3072 next: Cell::new(None),
3073 marker_calls: Cell::new(0),
3074 });
3075 let new_id = new_during_sweep.identity();
3076
3077 loop {
3078 let outcome = heap.incremental_step_with_post_mark(
3079 &OneRoot(None),
3080 StepBudget::from_work(64),
3081 |_| {},
3082 );
3083 if matches!(outcome, StepOutcome::Paused) {
3084 break;
3085 }
3086 }
3087
3088 assert_eq!(heap.allocation_token(old_id), None);
3089 assert!(heap.allocation_token(new_id).is_some());
3090 assert_eq!(heap.allgc_count(), 1);
3091
3092 heap.full_collect(&OneRoot(None));
3093 assert_eq!(heap.allocation_token(new_id), None);
3094 assert_eq!(heap.allgc_count(), 0);
3095 }
3096
3097 #[test]
3098 fn promote_and_reset_all_ages() {
3099 let heap = Heap::new();
3100 heap.unpause();
3101 let a = heap.allocate(Cell0 {
3102 next: Cell::new(None),
3103 marker_calls: Cell::new(0),
3104 });
3105 let b = heap.allocate(Cell0 {
3106 next: Cell::new(None),
3107 marker_calls: Cell::new(0),
3108 });
3109
3110 heap.promote_all_to_old();
3111 assert_eq!(a.age(), GcAge::Old);
3112 assert_eq!(b.age(), GcAge::Old);
3113 assert_eq!(a.color(), Color::Black);
3114 assert_eq!(b.color(), Color::Black);
3115
3116 heap.reset_all_ages();
3117 assert_eq!(a.age(), GcAge::New);
3118 assert_eq!(b.age(), GcAge::New);
3119 assert!(a.color().is_white());
3120 assert!(b.color().is_white());
3121 }
3122
3123 #[test]
3124 fn generational_barriers_update_ages() {
3125 let heap = Heap::new();
3126 heap.unpause();
3127 let parent = heap.allocate(Cell0 {
3128 next: Cell::new(None),
3129 marker_calls: Cell::new(0),
3130 });
3131 let child = heap.allocate(Cell0 {
3132 next: Cell::new(None),
3133 marker_calls: Cell::new(0),
3134 });
3135
3136 parent.set_age(GcAge::Old);
3137 parent.set_color(Color::Black);
3138 heap.generational_backward_barrier(parent);
3139 assert_eq!(parent.age(), GcAge::Touched1);
3140 assert_eq!(parent.color(), Color::Gray);
3141
3142 heap.generational_forward_barrier(parent, child);
3143 assert_eq!(child.age(), GcAge::Old0);
3144 }
3145
3146 #[test]
3147 fn minor_collect_frees_young_and_keeps_old() {
3148 let heap = Heap::new();
3149 heap.unpause();
3150 let old_unreachable = heap.allocate(Cell0 {
3151 next: Cell::new(None),
3152 marker_calls: Cell::new(0),
3153 });
3154 old_unreachable.set_age(GcAge::Old);
3155 old_unreachable.set_color(Color::Black);
3156 let _young_unreachable = heap.allocate(Cell0 {
3157 next: Cell::new(None),
3158 marker_calls: Cell::new(0),
3159 });
3160 let young_survivor = heap.allocate(Cell0 {
3161 next: Cell::new(None),
3162 marker_calls: Cell::new(0),
3163 });
3164
3165 heap.minor_collect_with_post_mark(&OneRoot(Some(young_survivor)), |_| {});
3166
3167 assert_eq!(heap.allgc_count(), 2);
3168 assert_eq!(old_unreachable.age(), GcAge::Old);
3169 assert_eq!(young_survivor.age(), GcAge::Survival);
3170 assert!(young_survivor.color().is_white());
3171 }
3172
3173 #[test]
3174 fn minor_collect_skips_untouched_old_root_scan_work() {
3175 let heap = Heap::new();
3176 heap.unpause();
3177 let old_root = heap.allocate(Cell0 {
3178 next: Cell::new(None),
3179 marker_calls: Cell::new(0),
3180 });
3181 old_root.set_age(GcAge::Old);
3182 old_root.set_color(Color::Black);
3183 let young_root = heap.allocate(Cell0 {
3184 next: Cell::new(None),
3185 marker_calls: Cell::new(0),
3186 });
3187
3188 heap.minor_collect_with_post_mark(
3189 &TwoRoots {
3190 first: Some(old_root),
3191 second: Some(young_root),
3192 },
3193 |_| {},
3194 );
3195
3196 let stats = heap.last_mark_stats();
3197 assert_eq!(stats.marked, 2);
3198 assert_eq!(stats.marked_old, 1);
3199 assert_eq!(stats.marked_young, 1);
3200 assert_eq!(stats.traced, 1);
3201 assert_eq!(stats.traced_old, 0);
3202 assert_eq!(stats.traced_young, 1);
3203 assert_eq!(old_root.marker_calls.get(), 0);
3204 assert_eq!(young_root.marker_calls.get(), 1);
3205 assert_eq!(old_root.age(), GcAge::Old);
3206 assert_eq!(young_root.age(), GcAge::Survival);
3207 }
3208
3209 #[test]
3210 fn minor_collect_traces_touched_old_parent() {
3211 let heap = Heap::new();
3212 heap.unpause();
3213 let old_root = heap.allocate(Cell0 {
3214 next: Cell::new(None),
3215 marker_calls: Cell::new(0),
3216 });
3217 old_root.set_age(GcAge::Old);
3218 old_root.set_color(Color::Black);
3219 let young_child = heap.allocate(Cell0 {
3220 next: Cell::new(None),
3221 marker_calls: Cell::new(0),
3222 });
3223 old_root.next.set(Some(young_child));
3224 heap.generational_backward_barrier(old_root);
3225
3226 heap.minor_collect_with_post_mark(&OneRoot(Some(old_root)), |_| {});
3227
3228 let stats = heap.last_mark_stats();
3229 assert_eq!(stats.marked, 2);
3230 assert_eq!(stats.marked_old, 1);
3231 assert_eq!(stats.marked_young, 1);
3232 assert_eq!(stats.traced, 2);
3233 assert_eq!(stats.traced_old, 1);
3234 assert_eq!(stats.traced_young, 1);
3235 assert_eq!(old_root.marker_calls.get(), 1);
3236 assert_eq!(young_child.marker_calls.get(), 1);
3237 assert_eq!(old_root.age(), GcAge::Touched2);
3238 assert_eq!(young_child.age(), GcAge::Survival);
3239 }
3240
3241 #[test]
3242 fn minor_sweep_uses_generation_cursors_to_skip_old_tail() {
3243 let heap = Heap::new();
3244 heap.unpause();
3245 let mut old_objects = Vec::new();
3246 for _ in 0..64 {
3247 old_objects.push(heap.allocate(Cell0 {
3248 next: Cell::new(None),
3249 marker_calls: Cell::new(0),
3250 }));
3251 }
3252 heap.promote_all_to_old();
3253 let young_root = heap.allocate(Cell0 {
3254 next: Cell::new(None),
3255 marker_calls: Cell::new(0),
3256 });
3257
3258 heap.minor_collect_with_post_mark(&OneRoot(Some(young_root)), |_| {});
3259
3260 let stats = heap.last_sweep_stats();
3261 assert_eq!(stats.visited, 1);
3262 assert_eq!(stats.visited_young, 1);
3263 assert_eq!(stats.visited_old, 0);
3264 assert_eq!(heap.allgc_count(), old_objects.len() + 1);
3265 assert_eq!(young_root.age(), GcAge::Survival);
3266 }
3267
3268 #[test]
3269 fn full_sweep_corrects_generation_cursors_when_cursor_object_is_freed() {
3270 let heap = Heap::new();
3271 heap.unpause();
3272 let _old = heap.allocate(Cell0 {
3273 next: Cell::new(None),
3274 marker_calls: Cell::new(0),
3275 });
3276 heap.promote_all_to_old();
3277 assert!(heap.survival.get().is_some());
3278 assert!(heap.old1.get().is_some());
3279 assert!(heap.reallyold.get().is_some());
3280
3281 heap.full_collect(&OneRoot(None));
3282
3283 assert_eq!(heap.allgc_count(), 0);
3284 assert_eq!(heap.survival.get(), None);
3285 assert_eq!(heap.old1.get(), None);
3286 assert_eq!(heap.reallyold.get(), None);
3287 assert_eq!(heap.firstold1.get(), None);
3288 assert_eq!(heap.last_sweep_stats().freed, 1);
3289 }
3290
3291 #[test]
3292 fn full_sweep_unlinks_freed_grayagain_entries() {
3293 let heap = Heap::new();
3294 heap.unpause();
3295 let parent = heap.allocate(Cell0 {
3296 next: Cell::new(None),
3297 marker_calls: Cell::new(0),
3298 });
3299 heap.promote_all_to_old();
3300 heap.generational_backward_barrier(parent);
3301 assert_eq!(heap.grayagain_count(), 1);
3302
3303 heap.full_collect(&OneRoot(None));
3304
3305 assert_eq!(heap.allgc_count(), 0);
3306 assert_eq!(heap.grayagain_count(), 0);
3307
3308 let young = heap.allocate(Cell0 {
3309 next: Cell::new(None),
3310 marker_calls: Cell::new(0),
3311 });
3312 heap.minor_collect_with_post_mark(&OneRoot(Some(young)), |_| {});
3313 assert_eq!(young.age(), GcAge::Survival);
3314 }
3315
3316 #[test]
3317 fn grayagain_links_object_once() {
3318 let heap = Heap::new();
3319 heap.unpause();
3320 let parent = heap.allocate(Cell0 {
3321 next: Cell::new(None),
3322 marker_calls: Cell::new(0),
3323 });
3324 parent.set_age(GcAge::Old);
3325 parent.set_color(Color::Black);
3326
3327 heap.generational_backward_barrier(parent);
3328 heap.generational_backward_barrier(parent);
3329
3330 assert_eq!(heap.grayagain_count(), 1);
3331 }
3332
3333 #[test]
3334 fn grayagain_list_carries_old1_until_old() {
3335 let heap = Heap::new();
3336 heap.unpause();
3337 let survivor = heap.allocate(Cell0 {
3338 next: Cell::new(None),
3339 marker_calls: Cell::new(0),
3340 });
3341
3342 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3343 assert_eq!(survivor.age(), GcAge::Survival);
3344
3345 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3346 assert_eq!(survivor.age(), GcAge::Old1);
3347
3348 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3349 assert_eq!(survivor.age(), GcAge::Old);
3350 assert_eq!(heap.allgc_count(), 1);
3351 }
3352
3353 #[test]
3354 fn grayagain_list_carries_touched2_until_old() {
3355 let heap = Heap::new();
3356 heap.unpause();
3357 let parent = heap.allocate(Cell0 {
3358 next: Cell::new(None),
3359 marker_calls: Cell::new(0),
3360 });
3361 parent.set_age(GcAge::Old);
3362 parent.set_color(Color::Black);
3363 heap.generational_backward_barrier(parent);
3364
3365 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3366 assert_eq!(parent.age(), GcAge::Touched2);
3367
3368 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3369 assert_eq!(parent.age(), GcAge::Old);
3370 assert_eq!(parent.marker_calls.get(), 2);
3371 }
3372
3373 #[test]
3374 fn collect_traverses_cycles() {
3375 let heap = Heap::new();
3376 heap.unpause();
3377 let a = heap.allocate(Cell0 {
3378 next: Cell::new(None),
3379 marker_calls: Cell::new(0),
3380 });
3381 let b = heap.allocate(Cell0 {
3382 next: Cell::new(Some(a)),
3383 marker_calls: Cell::new(0),
3384 });
3385 a.next.set(Some(b)); heap.full_collect(&OneRoot(Some(a)));
3388 assert_eq!(a.marker_calls.get(), 1);
3389 assert_eq!(b.marker_calls.get(), 1);
3390 heap.full_collect(&OneRoot(None));
3394 assert_eq!(heap.bytes_used(), 0);
3395 }
3396
3397 #[test]
3398 fn heap_guard_stacks() {
3399 assert!(with_current_heap(|heap| heap.is_none()), "no guard initially");
3400 let h1 = Heap::new();
3401 h1.unpause();
3402 {
3403 let _g1 = HeapGuard::push(&h1);
3404 assert!(with_current_heap(|heap| heap.is_some()));
3405 let h2 = Heap::new();
3406 h2.unpause();
3407 {
3408 let _g2 = HeapGuard::push(&h2);
3409 with_current_heap(|heap| {
3411 assert!(std::ptr::addr_eq(
3412 heap.unwrap() as *const _,
3413 &h2 as *const _,
3414 ));
3415 });
3416 }
3417 with_current_heap(|heap| {
3419 assert!(std::ptr::addr_eq(
3420 heap.unwrap() as *const _,
3421 &h1 as *const _,
3422 ));
3423 });
3424 }
3425 assert!(
3426 with_current_heap(|heap| heap.is_none()),
3427 "stack empty after all guards drop"
3428 );
3429 }
3430
3431 #[test]
3432 fn step_threshold_triggers_collect() {
3433 let heap = Heap::new();
3434 heap.unpause();
3435 let mut keeps = Vec::new();
3437 for _ in 0..64 {
3438 keeps.push(heap.allocate(Cell0 {
3442 next: Cell::new(None),
3443 marker_calls: Cell::new(0),
3444 }));
3445 }
3446 struct ManyRoots<'a>(&'a [Gc<Cell0>]);
3448 impl<'a> Trace for ManyRoots<'a> {
3449 fn trace(&self, m: &mut Marker) {
3450 for g in self.0.iter() {
3451 m.mark(*g);
3452 }
3453 }
3454 }
3455 heap.step(&ManyRoots(&keeps));
3456 assert!(heap.bytes_used() > 0);
3458 }
3459
3460 #[test]
3461 fn threshold_floored_after_collecting_tiny_heap() {
3462 let heap = Heap::new();
3463 heap.unpause();
3464 struct NoRoots;
3465 impl Trace for NoRoots {
3466 fn trace(&self, _m: &mut Marker) {}
3467 }
3468 for _ in 0..200 {
3469 heap.allocate(Cell0 {
3470 next: Cell::new(None),
3471 marker_calls: Cell::new(0),
3472 });
3473 }
3474 heap.full_collect(&NoRoots);
3475 assert!(
3476 heap.threshold_bytes() >= GC_MIN_THRESHOLD,
3477 "threshold {} collapsed below floor {}; a churning program would full-collect per allocation",
3478 heap.threshold_bytes(),
3479 GC_MIN_THRESHOLD
3480 );
3481 }
3482
3483 fn build_chain(heap: &Heap, len: usize) -> Gc<Cell0> {
3484 let head = heap.allocate(Cell0 {
3485 next: Cell::new(None),
3486 marker_calls: Cell::new(0),
3487 });
3488 let mut cur = head;
3489 for _ in 1..len {
3490 let n = heap.allocate(Cell0 {
3491 next: Cell::new(None),
3492 marker_calls: Cell::new(0),
3493 });
3494 cur.next.set(Some(n));
3495 cur = n;
3496 }
3497 head
3498 }
3499
3500 #[test]
3501 fn budget_zero_does_some_work() {
3502 let heap = Heap::new();
3503 heap.unpause();
3504 let head = build_chain(&heap, 8);
3505 let roots = OneRoot(Some(head));
3506 let budget = StepBudget::from_work(0);
3507 let outcome = heap.incremental_step_with_post_mark(&roots, budget, |_| {});
3508 assert_ne!(outcome, StepOutcome::SkippedStopped);
3509 assert_ne!(heap.gc_state(), GcState::Pause);
3510 }
3511
3512 #[test]
3513 fn run_until_state_stops_before_next_phase_work() {
3514 let heap = Heap::new();
3515 heap.unpause();
3516 let head = build_chain(&heap, 8);
3517 let roots = OneRoot(Some(head));
3518 let atomic_calls = Cell::new(0);
3519
3520 let outcome = heap.incremental_run_until_state_with_post_mark(
3521 &roots,
3522 GcState::Atomic,
3523 1024,
3524 |_| atomic_calls.set(atomic_calls.get() + 1),
3525 );
3526 assert_eq!(outcome, StepOutcome::InProgress);
3527 assert_eq!(heap.gc_state(), GcState::Atomic);
3528 assert_eq!(atomic_calls.get(), 0, "atomic hook must not run before inspection");
3529
3530 let outcome = heap.incremental_run_until_state_with_post_mark(
3531 &roots,
3532 GcState::SweepAllGc,
3533 1024,
3534 |_| atomic_calls.set(atomic_calls.get() + 1),
3535 );
3536 assert_eq!(outcome, StepOutcome::InProgress);
3537 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3538 assert_eq!(atomic_calls.get(), 1, "entering sweep must run the atomic hook once");
3539 }
3540
3541 #[test]
3542 fn larger_budget_drains_more_gray_than_smaller() {
3543 let small_heap = Heap::new();
3544 small_heap.unpause();
3545 let h1 = build_chain(&small_heap, 64);
3546 let r1 = OneRoot(Some(h1));
3547 let mut small_calls = 0;
3548 loop {
3549 small_calls += 1;
3550 let outcome = small_heap.incremental_step_with_post_mark(
3551 &r1,
3552 StepBudget::from_work(2),
3553 |_| {},
3554 );
3555 if outcome == StepOutcome::Paused {
3556 break;
3557 }
3558 assert!(small_calls < 10_000, "did not converge");
3559 }
3560
3561 let big_heap = Heap::new();
3562 big_heap.unpause();
3563 let h2 = build_chain(&big_heap, 64);
3564 let r2 = OneRoot(Some(h2));
3565 let mut big_calls = 0;
3566 loop {
3567 big_calls += 1;
3568 let outcome = big_heap.incremental_step_with_post_mark(
3569 &r2,
3570 StepBudget::from_work(64),
3571 |_| {},
3572 );
3573 if outcome == StepOutcome::Paused {
3574 break;
3575 }
3576 assert!(big_calls < 10_000, "did not converge");
3577 }
3578
3579 assert!(
3580 big_calls < small_calls,
3581 "expected big_calls={} < small_calls={}",
3582 big_calls,
3583 small_calls
3584 );
3585 }
3586
3587 #[test]
3588 fn sweep_can_pause_and_resume() {
3589 let heap = Heap::new();
3590 heap.unpause();
3591 for _ in 0..16 {
3592 let _ = heap.allocate(Cell0 {
3593 next: Cell::new(None),
3594 marker_calls: Cell::new(0),
3595 });
3596 }
3597 let roots = OneRoot(None);
3598 let bytes_before = heap.bytes_used();
3599 assert!(bytes_before > 0);
3600 let mut step_count = 0;
3601 let mut saw_in_progress_during_sweep = false;
3602 loop {
3603 step_count += 1;
3604 let outcome = heap.incremental_step_with_post_mark(
3605 &roots,
3606 StepBudget::from_work(2),
3607 |_| {},
3608 );
3609 if heap.gc_state().is_sweep() && outcome == StepOutcome::InProgress {
3610 saw_in_progress_during_sweep = true;
3611 }
3612 if outcome == StepOutcome::Paused {
3613 break;
3614 }
3615 assert!(step_count < 10_000, "did not converge");
3616 }
3617 assert!(saw_in_progress_during_sweep, "sweep never paused mid-list");
3618 assert_eq!(heap.bytes_used(), 0);
3619 }
3620
3621 #[test]
3622 fn post_mark_runs_once_per_atomic() {
3623 let heap = Heap::new();
3624 heap.unpause();
3625 for _ in 0..32 {
3626 let _ = heap.allocate(Cell0 {
3627 next: Cell::new(None),
3628 marker_calls: Cell::new(0),
3629 });
3630 }
3631 let roots = OneRoot(None);
3632 let call_count = std::cell::Cell::new(0);
3633 let mut step_count = 0;
3634 loop {
3635 step_count += 1;
3636 let outcome = heap.incremental_step_with_post_mark(
3637 &roots,
3638 StepBudget::from_work(2),
3639 |_| {
3640 call_count.set(call_count.get() + 1);
3641 },
3642 );
3643 if outcome == StepOutcome::Paused {
3644 break;
3645 }
3646 assert!(step_count < 10_000, "did not converge");
3647 }
3648 assert_eq!(call_count.get(), 1, "post_mark must run exactly once per cycle");
3649 }
3650
3651 #[test]
3652 fn full_collect_equivalent_to_incremental_to_pause() {
3653 let h1 = Heap::new();
3654 h1.unpause();
3655 let head1 = h1.allocate(Cell0 {
3656 next: Cell::new(None),
3657 marker_calls: Cell::new(0),
3658 });
3659 let _orphan1 = h1.allocate(Cell0 {
3660 next: Cell::new(None),
3661 marker_calls: Cell::new(0),
3662 });
3663 let _orphan2 = h1.allocate(Cell0 {
3664 next: Cell::new(None),
3665 marker_calls: Cell::new(0),
3666 });
3667 let roots1 = OneRoot(Some(head1));
3668 h1.full_collect(&roots1);
3669 let bytes_full = h1.bytes_used();
3670
3671 let h2 = Heap::new();
3672 h2.unpause();
3673 let head2 = h2.allocate(Cell0 {
3674 next: Cell::new(None),
3675 marker_calls: Cell::new(0),
3676 });
3677 let _orphan3 = h2.allocate(Cell0 {
3678 next: Cell::new(None),
3679 marker_calls: Cell::new(0),
3680 });
3681 let _orphan4 = h2.allocate(Cell0 {
3682 next: Cell::new(None),
3683 marker_calls: Cell::new(0),
3684 });
3685 let roots2 = OneRoot(Some(head2));
3686 loop {
3687 let outcome = h2.incremental_step_with_post_mark(
3688 &roots2,
3689 StepBudget::from_work(1),
3690 |_| {},
3691 );
3692 if outcome == StepOutcome::Paused {
3693 break;
3694 }
3695 }
3696 assert_eq!(h2.bytes_used(), bytes_full);
3697 }
3698}
3699
3700