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