1use std::cell::{Cell, RefCell};
44use std::collections::{HashMap, HashSet};
45use std::hash::{BuildHasherDefault, Hasher};
46use std::marker::PhantomData;
47use std::ptr::NonNull;
48
49#[derive(Default)]
50struct IdentityHasher {
51 value: u64,
52}
53
54impl Hasher for IdentityHasher {
55 #[inline]
56 fn write(&mut self, bytes: &[u8]) {
57 const PRIME: u64 = 0x0000_0100_0000_01b3;
58 for &byte in bytes {
59 self.value ^= u64::from(byte);
60 self.value = self.value.wrapping_mul(PRIME);
61 }
62 }
63
64 #[inline]
65 fn write_usize(&mut self, i: usize) {
66 self.value = i as u64;
67 }
68
69 #[inline]
70 fn write_u64(&mut self, i: u64) {
71 self.value = i;
72 }
73
74 #[inline]
75 fn finish(&self) -> u64 {
76 let mut x = self.value;
77 x ^= x >> 30;
78 x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9);
79 x ^= x >> 27;
80 x = x.wrapping_mul(0x94d0_49bb_1331_11eb);
81 x ^ (x >> 31)
82 }
83}
84
85type IdentityBuildHasher = BuildHasherDefault<IdentityHasher>;
86type IdentityHashSet = HashSet<usize, IdentityBuildHasher>;
87type IdentityHashMap<V> = HashMap<usize, V, IdentityBuildHasher>;
88
89thread_local! {
103 static CURRENT_HEAP_STACK: RefCell<Vec<std::rc::Rc<Heap>>> = const { RefCell::new(Vec::new()) };
104}
105
106pub struct HeapGuard {
121 heap: std::rc::Rc<Heap>,
122}
123
124impl HeapGuard {
125 pub fn push(heap: &std::rc::Rc<Heap>) -> Self {
127 CURRENT_HEAP_STACK.with(|stack| stack.borrow_mut().push(std::rc::Rc::clone(heap)));
128 HeapGuard {
129 heap: std::rc::Rc::clone(heap),
130 }
131 }
132}
133
134impl Drop for HeapGuard {
135 fn drop(&mut self) {
136 CURRENT_HEAP_STACK.with(|stack| {
137 let popped = stack.borrow_mut().pop();
138 debug_assert!(
139 popped
140 .as_ref()
141 .is_some_and(|top| std::rc::Rc::ptr_eq(top, &self.heap)),
142 "HeapGuard::drop popped a frame it did not push — guards must \
143 drop in reverse push order on the thread that created them"
144 );
145 });
146 }
147}
148
149pub struct BootstrapScope {
161 depth: std::rc::Rc<Cell<usize>>,
162}
163
164impl Drop for BootstrapScope {
165 fn drop(&mut self) {
166 let depth = self.depth.get();
167 debug_assert!(depth > 0, "BootstrapScope dropped with zero depth");
168 self.depth.set(depth.saturating_sub(1));
169 }
170}
171
172thread_local! {
173 static DETACHED_ALLOCATIONS: Cell<usize> = const { Cell::new(0) };
174}
175
176pub fn detached_allocations() -> usize {
183 DETACHED_ALLOCATIONS.with(|c| c.get())
184}
185
186pub fn with_current_heap<R>(f: impl for<'a> FnOnce(Option<&'a std::rc::Rc<Heap>>) -> R) -> R {
193 let top = CURRENT_HEAP_STACK.with(|stack| stack.borrow().last().cloned());
194 f(top.as_ref())
195}
196
197#[derive(Clone, Debug)]
203pub struct HeapRef {
204 weak: std::rc::Weak<Heap>,
205}
206
207impl HeapRef {
208 pub fn from_heap(heap: &std::rc::Rc<Heap>) -> Self {
209 HeapRef {
210 weak: std::rc::Rc::downgrade(heap),
211 }
212 }
213
214 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
215 match self.weak.upgrade() {
216 Some(heap) => heap.contains_allocation(identity, token),
217 None => false,
218 }
219 }
220}
221
222#[derive(Copy, Clone, PartialEq, Eq, Debug)]
224pub enum Color {
225 White0,
229 White1,
231 Gray,
233 Black,
235}
236
237impl Color {
238 pub fn is_white(self) -> bool {
239 matches!(self, Color::White0 | Color::White1)
240 }
241
242 fn other_white(self) -> Self {
243 match self {
244 Color::White0 => Color::White1,
245 Color::White1 => Color::White0,
246 Color::Gray | Color::Black => self,
247 }
248 }
249}
250
251#[derive(Copy, Clone, PartialEq, Eq, Debug)]
255pub enum GcAge {
256 New,
257 Survival,
258 Old0,
259 Old1,
260 Old,
261 Touched1,
262 Touched2,
263}
264
265impl GcAge {
266 pub fn is_old(self) -> bool {
267 !matches!(self, GcAge::New | GcAge::Survival)
268 }
269
270 fn next_after_minor(self) -> Self {
271 match self {
272 GcAge::New => GcAge::Survival,
273 GcAge::Survival | GcAge::Old0 => GcAge::Old1,
274 GcAge::Old1 | GcAge::Old | GcAge::Touched2 => GcAge::Old,
275 GcAge::Touched1 => GcAge::Touched2,
276 }
277 }
278}
279
280pub trait Udata51Probe: std::any::Any {
296 fn is_alive(&self) -> bool;
300
301 fn as_any(&self) -> &dyn std::any::Any;
303}
304
305pub trait FinalizerEntry: Clone {
308 fn identity(&self) -> usize;
309 fn heap_ptr(&self) -> Option<NonNull<GcBox<dyn Trace>>> {
310 None
311 }
312 fn age(&self) -> GcAge;
313 fn is_finalized(&self) -> bool;
314 fn set_finalized(&self, finalized: bool);
315}
316
317pub trait WeakEntry: Clone {
319 type Strong: Clone;
320
321 fn identity(&self) -> usize;
322 fn list_kind(&self) -> WeakListKind;
323 fn upgrade(&self) -> Option<Self::Strong>;
324}
325
326#[derive(Copy, Clone, Debug, PartialEq, Eq)]
327pub enum WeakListKind {
328 WeakValues,
329 Ephemeron,
330 AllWeak,
331}
332
333#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
334pub struct WeakRegistryStats {
335 pub tracked: usize,
336 pub snapshot_live: usize,
337 pub snapshot_dead: usize,
338 pub retained: usize,
339 pub weak_values: usize,
340 pub ephemeron: usize,
341 pub all_weak: usize,
342}
343
344#[derive(Clone, Debug)]
345pub struct WeakRegistry<T: WeakEntry> {
346 weak_values: Vec<T>,
347 ephemeron: Vec<T>,
348 all_weak: Vec<T>,
349 last_stats: WeakRegistryStats,
350}
351
352#[derive(Clone, Debug, PartialEq, Eq)]
353pub struct WeakRegistrySnapshot<T> {
354 pub weak_values: Vec<T>,
355 pub ephemeron: Vec<T>,
356 pub all_weak: Vec<T>,
357}
358
359impl<T> Default for WeakRegistrySnapshot<T> {
360 fn default() -> Self {
361 Self {
362 weak_values: Vec::new(),
363 ephemeron: Vec::new(),
364 all_weak: Vec::new(),
365 }
366 }
367}
368
369impl<T> WeakRegistrySnapshot<T> {
370 pub fn len(&self) -> usize {
371 self.weak_values
372 .len()
373 .saturating_add(self.ephemeron.len())
374 .saturating_add(self.all_weak.len())
375 }
376
377 pub fn into_flat(self) -> Vec<T> {
378 self.weak_values
379 .into_iter()
380 .chain(self.ephemeron)
381 .chain(self.all_weak)
382 .collect()
383 }
384}
385
386impl<T: WeakEntry> Default for WeakRegistry<T> {
387 fn default() -> Self {
388 Self {
389 weak_values: Vec::new(),
390 ephemeron: Vec::new(),
391 all_weak: Vec::new(),
392 last_stats: WeakRegistryStats::default(),
393 }
394 }
395}
396
397impl<T: WeakEntry> WeakRegistry<T> {
398 pub fn len(&self) -> usize {
399 self.weak_values
400 .len()
401 .saturating_add(self.ephemeron.len())
402 .saturating_add(self.all_weak.len())
403 }
404
405 pub fn stats(&self) -> WeakRegistryStats {
406 self.last_stats
407 }
408
409 fn list_mut(&mut self, kind: WeakListKind) -> &mut Vec<T> {
410 match kind {
411 WeakListKind::WeakValues => &mut self.weak_values,
412 WeakListKind::Ephemeron => &mut self.ephemeron,
413 WeakListKind::AllWeak => &mut self.all_weak,
414 }
415 }
416
417 pub fn remove_identity(&mut self, id: usize) {
418 self.weak_values.retain(|entry| entry.identity() != id);
419 self.ephemeron.retain(|entry| entry.identity() != id);
420 self.all_weak.retain(|entry| entry.identity() != id);
421 self.last_stats.tracked = self.len();
422 self.last_stats.retained = self.len();
423 self.update_cohort_stats();
424 }
425
426 fn update_cohort_stats(&mut self) {
427 self.last_stats.weak_values = self.weak_values.len();
428 self.last_stats.ephemeron = self.ephemeron.len();
429 self.last_stats.all_weak = self.all_weak.len();
430 }
431
432 pub fn push_unique(&mut self, entry: T) {
433 let id = entry.identity();
434 self.remove_identity(id);
435 self.list_mut(entry.list_kind()).push(entry);
436 self.last_stats.tracked = self.len();
437 self.last_stats.retained = self.len();
438 self.update_cohort_stats();
439 }
440
441 pub fn live_snapshot_by_kind(&mut self) -> WeakRegistrySnapshot<T::Strong> {
442 let tracked_before = self.len();
443 let weak_values_capacity = self.weak_values.len();
444 let ephemeron_capacity = self.ephemeron.len();
445 let all_weak_capacity = self.all_weak.len();
446 let mut seen = std::collections::HashSet::<usize>::with_capacity(tracked_before);
447 let mut live = WeakRegistrySnapshot {
448 weak_values: Vec::with_capacity(weak_values_capacity),
449 ephemeron: Vec::with_capacity(ephemeron_capacity),
450 all_weak: Vec::with_capacity(all_weak_capacity),
451 };
452 let mut dead = 0usize;
453
454 let entries = std::mem::take(&mut self.weak_values)
455 .into_iter()
456 .chain(std::mem::take(&mut self.ephemeron))
457 .chain(std::mem::take(&mut self.all_weak));
458 for entry in entries {
459 if !seen.insert(entry.identity()) {
460 continue;
461 }
462 match entry.upgrade() {
463 Some(strong) => {
464 match entry.list_kind() {
465 WeakListKind::WeakValues => live.weak_values.push(strong),
466 WeakListKind::Ephemeron => live.ephemeron.push(strong),
467 WeakListKind::AllWeak => live.all_weak.push(strong),
468 }
469 self.list_mut(entry.list_kind()).push(entry);
470 }
471 None => dead += 1,
472 }
473 }
474
475 self.last_stats = WeakRegistryStats {
476 tracked: tracked_before,
477 snapshot_live: live.len(),
478 snapshot_dead: dead,
479 retained: self.len(),
480 weak_values: self.weak_values.len(),
481 ephemeron: self.ephemeron.len(),
482 all_weak: self.all_weak.len(),
483 };
484 live
485 }
486
487 pub fn live_snapshot(&mut self) -> Vec<T::Strong> {
488 self.live_snapshot_by_kind().into_flat()
489 }
490
491 pub fn retain_identities(&mut self, ids: &std::collections::HashSet<usize>) {
492 self.weak_values
493 .retain(|entry| ids.contains(&entry.identity()));
494 self.ephemeron
495 .retain(|entry| ids.contains(&entry.identity()));
496 self.all_weak
497 .retain(|entry| ids.contains(&entry.identity()));
498 self.last_stats.retained = self.len();
499 self.last_stats.tracked = self.len();
500 self.update_cohort_stats();
501 }
502}
503
504#[derive(Clone, Debug)]
505pub struct FinalizerRegistry<T: FinalizerEntry> {
506 pending: Vec<T>,
507 to_be_finalized: Vec<T>,
508 pending_reallyold: usize,
509 pending_old1: usize,
510 pending_survival: usize,
511}
512
513#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
514pub struct FinalizerRegistryStats {
515 pub pending_young: usize,
516 pub pending_old: usize,
517 pub to_be_finalized_young: usize,
518 pub to_be_finalized_old: usize,
519 pub finobj_new: usize,
520 pub finobj_survival: usize,
521 pub finobj_old1: usize,
522 pub finobj_reallyold: usize,
523 pub finobj_minor_scan: usize,
524}
525
526impl<T: FinalizerEntry> Default for FinalizerRegistry<T> {
527 fn default() -> Self {
528 Self {
529 pending: Vec::new(),
530 to_be_finalized: Vec::new(),
531 pending_reallyold: 0,
532 pending_old1: 0,
533 pending_survival: 0,
534 }
535 }
536}
537
538impl<T: FinalizerEntry> FinalizerRegistry<T> {
539 fn pending_new_len(&self) -> usize {
540 self.pending.len().saturating_sub(
541 self.pending_reallyold
542 .saturating_add(self.pending_old1)
543 .saturating_add(self.pending_survival),
544 )
545 }
546
547 fn minor_scan_start(&self) -> usize {
548 self.pending_reallyold.saturating_add(self.pending_old1)
549 }
550
551 fn debug_assert_pending_cohorts(&self) {
552 debug_assert!(
553 self.pending_reallyold
554 .saturating_add(self.pending_old1)
555 .saturating_add(self.pending_survival)
556 <= self.pending.len()
557 );
558 }
559
560 pub fn pending(&self) -> &[T] {
561 &self.pending
562 }
563
564 pub fn pending_snapshot(&self) -> Vec<T> {
565 self.pending.clone()
566 }
567
568 pub fn pending_minor_snapshot(&self) -> Vec<T> {
569 self.pending[self.minor_scan_start().min(self.pending.len())..].to_vec()
570 }
571
572 pub fn to_be_finalized(&self) -> &[T] {
573 &self.to_be_finalized
574 }
575
576 pub fn pending_len(&self) -> usize {
577 self.pending.len()
578 }
579
580 pub fn to_be_finalized_len(&self) -> usize {
581 self.to_be_finalized.len()
582 }
583
584 pub fn has_to_be_finalized(&self) -> bool {
585 !self.to_be_finalized.is_empty()
586 }
587
588 pub fn stats(&self) -> FinalizerRegistryStats {
589 fn count_by_age<T: FinalizerEntry>(objects: &[T]) -> (usize, usize) {
590 objects
591 .iter()
592 .fold((0usize, 0usize), |(young, old), object| {
593 if object.age().is_old() {
594 (young, old + 1)
595 } else {
596 (young + 1, old)
597 }
598 })
599 }
600 let (pending_young, pending_old) = count_by_age(&self.pending);
601 let (to_be_finalized_young, to_be_finalized_old) = count_by_age(&self.to_be_finalized);
602 FinalizerRegistryStats {
603 pending_young,
604 pending_old,
605 to_be_finalized_young,
606 to_be_finalized_old,
607 finobj_new: self.pending_new_len(),
608 finobj_survival: self.pending_survival,
609 finobj_old1: self.pending_old1,
610 finobj_reallyold: self.pending_reallyold,
611 finobj_minor_scan: self.pending.len().saturating_sub(self.minor_scan_start()),
612 }
613 }
614
615 pub fn push_pending_unique(&mut self, object: T) -> bool {
616 if object.is_finalized() {
617 return false;
618 }
619 let id = object.identity();
620 if !self.pending.iter().any(|o| o.identity() == id) {
621 object.set_finalized(true);
622 self.pending.push(object);
623 self.debug_assert_pending_cohorts();
624 true
625 } else {
626 false
627 }
628 }
629
630 pub fn take_pending(&mut self) -> Vec<T> {
631 self.pending_reallyold = 0;
632 self.pending_old1 = 0;
633 self.pending_survival = 0;
634 std::mem::take(&mut self.pending)
635 }
636
637 fn retain_pending_not_in(&mut self, ids: &std::collections::HashSet<usize>) {
638 if ids.is_empty() {
639 return;
640 }
641 let original_reallyold = self.pending_reallyold;
642 let original_old1 = self.pending_old1;
643 let original_survival = self.pending_survival;
644 let mut retained_reallyold = original_reallyold;
645 let mut retained_old1 = original_old1;
646 let mut retained_survival = original_survival;
647 let mut retained = Vec::with_capacity(self.pending.len());
648 for (index, object) in std::mem::take(&mut self.pending).into_iter().enumerate() {
649 if ids.contains(&object.identity()) {
650 if index < original_reallyold {
651 retained_reallyold -= 1;
652 } else if index < original_reallyold + original_old1 {
653 retained_old1 -= 1;
654 } else if index < original_reallyold + original_old1 + original_survival {
655 retained_survival -= 1;
656 }
657 } else {
658 retained.push(object);
659 }
660 }
661 self.pending = retained;
662 self.pending_reallyold = retained_reallyold;
663 self.pending_old1 = retained_old1;
664 self.pending_survival = retained_survival;
665 self.debug_assert_pending_cohorts();
666 }
667
668 pub fn push_to_be_finalized(&mut self, object: T) {
669 object.set_finalized(true);
670 self.to_be_finalized.push(object);
671 }
672
673 fn extend_to_be_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
674 let drain_order: Vec<T> = objects.into_iter().rev().collect();
675 for object in drain_order.iter().cloned() {
676 self.push_to_be_finalized(object);
677 }
678 drain_order
679 }
680
681 pub fn promote_pending_to_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
682 if objects.is_empty() {
683 return Vec::new();
684 }
685 let mut ids: std::collections::HashSet<usize> =
686 std::collections::HashSet::with_capacity(objects.len());
687 ids.extend(objects.iter().map(|object| object.identity()));
688 self.retain_pending_not_in(&ids);
689 self.extend_to_be_finalized(objects)
690 }
691
692 pub fn promote_all_pending_to_old(&mut self) {
693 self.pending_reallyold = self.pending.len();
694 self.pending_old1 = 0;
695 self.pending_survival = 0;
696 }
697
698 pub fn reset_generation_boundaries(&mut self) {
699 self.pending_reallyold = 0;
700 self.pending_old1 = 0;
701 self.pending_survival = 0;
702 }
703
704 pub fn finish_minor_collection(&mut self) {
705 let new = self.pending_new_len();
706 self.pending_reallyold = self.pending_reallyold.saturating_add(self.pending_old1);
707 self.pending_old1 = self.pending_survival;
708 self.pending_survival = new;
709 self.debug_assert_pending_cohorts();
710 }
711
712 pub fn pop_to_be_finalized(&mut self) -> Option<T> {
713 let object = if self.to_be_finalized.is_empty() {
714 None
715 } else {
716 Some(self.to_be_finalized.remove(0))
717 };
718 if let Some(ref object) = object {
719 object.set_finalized(false);
720 }
721 object
722 }
723}
724
725#[repr(C)]
727pub struct GcHeader {
728 color: Cell<Color>,
739 age: Cell<GcAge>,
740 flags: Cell<u8>,
748 size: Cell<u32>,
755 next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
757}
758
759const HDR_FINALIZED: u8 = 1;
760const HDR_COLLECTED: u8 = 2;
761const HDR_GRAY_LISTED: u8 = 4;
762const HDR_HEAP_OWNED: u8 = 16;
769const HDR_FREED: u8 = 8;
774
775impl GcHeader {
776 fn new_white(size: usize, color: Color, flags: u8) -> Self {
777 Self {
778 color: Cell::new(color),
779 age: Cell::new(GcAge::New),
780 flags: Cell::new(flags),
781 size: Cell::new(size.min(u32::MAX as usize) as u32),
782 next: Cell::new(None),
783 }
784 }
785
786 fn flag(&self, bit: u8) -> bool {
787 self.flags.get() & bit != 0
788 }
789
790 fn set_flag(&self, bit: u8, on: bool) {
791 let f = self.flags.get();
792 self.flags.set(if on { f | bit } else { f & !bit });
793 }
794
795 pub fn finalized(&self) -> bool {
796 self.flag(HDR_FINALIZED)
797 }
798
799 pub fn set_finalized(&self, finalized: bool) {
800 self.set_flag(HDR_FINALIZED, finalized);
801 }
802
803 pub fn collected(&self) -> bool {
804 self.flag(HDR_COLLECTED)
805 }
806
807 pub fn gray_listed(&self) -> bool {
808 self.flag(HDR_GRAY_LISTED)
809 }
810
811 pub fn set_gray_listed(&self, listed: bool) {
812 self.set_flag(HDR_GRAY_LISTED, listed);
813 }
814
815 pub fn size(&self) -> usize {
816 self.size.get() as usize
817 }
818
819 pub fn set_size(&self, size: usize) {
820 self.size.set(size.min(u32::MAX as usize) as u32);
821 }
822}
823
824#[cfg(target_pointer_width = "64")]
831const _: () = {
832 assert!(std::mem::size_of::<GcHeader>() == 24);
833 assert!(std::mem::align_of::<GcHeader>() == 8);
834};
835#[cfg(target_pointer_width = "32")]
836const _: () = {
837 assert!(std::mem::size_of::<GcHeader>() == 16);
838};
839
840#[cfg(target_pointer_width = "64")]
845const _: () = assert!(std::mem::size_of::<GcBox<u64>>() == 32);
846#[cfg(target_pointer_width = "32")]
847const _: () = assert!(std::mem::size_of::<GcBox<u64>>() == 24);
848
849#[repr(C)]
851pub struct GcBox<T: ?Sized> {
852 header: GcHeader,
853 value: T,
854}
855
856impl<T: ?Sized> GcBox<T> {
857 pub fn header(&self) -> &GcHeader {
858 &self.header
859 }
860 pub fn value(&self) -> &T {
861 &self.value
862 }
863}
864
865pub struct Gc<T: ?Sized> {
867 ptr: NonNull<GcBox<T>>,
868 _marker: PhantomData<T>,
870}
871
872impl<T: ?Sized> Copy for Gc<T> {}
876impl<T: ?Sized> Clone for Gc<T> {
877 fn clone(&self) -> Self {
878 *self
879 }
880}
881
882impl<T: ?Sized> PartialEq for Gc<T> {
883 fn eq(&self, other: &Self) -> bool {
884 std::ptr::addr_eq(self.ptr.as_ptr(), other.ptr.as_ptr())
885 }
886}
887impl<T: ?Sized> Eq for Gc<T> {}
888
889impl<T: ?Sized> std::hash::Hash for Gc<T> {
890 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
891 self.ptr.as_ptr().hash(state)
892 }
893}
894
895impl<T: ?Sized> std::fmt::Debug for Gc<T> {
896 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
897 write!(f, "Gc({:p})", self.ptr.as_ptr())
898 }
899}
900
901impl<T: Trace + 'static> Gc<T> {
902 pub fn new_uncollected(value: T) -> Self {
908 DETACHED_ALLOCATIONS.with(|c| c.set(c.get() + 1));
909 let size = std::mem::size_of::<T>();
910 let boxed = Box::new(GcBox {
911 header: GcHeader::new_white(size, Color::White0, 0),
912 value,
913 });
914 Gc {
915 ptr: NonNull::new(Box::into_raw(boxed)).expect("Box::into_raw is non-null"),
916 _marker: PhantomData,
917 }
918 }
919
920 pub fn as_trace_ptr(self) -> NonNull<GcBox<dyn Trace>> {
922 self.ptr
923 }
924}
925
926impl<T: ?Sized> Gc<T> {
927 pub fn ptr_eq(a: Self, b: Self) -> bool {
929 std::ptr::addr_eq(a.ptr.as_ptr(), b.ptr.as_ptr())
930 }
931
932 pub fn identity(self) -> usize {
935 self.ptr.as_ptr() as *const () as usize
936 }
937
938 fn as_box(&self) -> &GcBox<T> {
942 let bx = unsafe { self.ptr.as_ref() };
948 debug_assert!(
949 !bx.header.flag(HDR_FREED),
950 "use-after-sweep: Gc<{}> dereferenced after the collector swept it \
951 (caught by LUA_RS_GC_QUARANTINE; this is a rooting bug — the object \
952 was reachable by execution but not by the root trace)",
953 std::any::type_name::<T>()
954 );
955 bx
956 }
957
958 fn header(&self) -> &GcHeader {
959 &self.as_box().header
960 }
961
962 pub fn is_heap_tracked(self) -> bool {
967 self.header().collected()
968 }
969
970 pub fn is_heap_owned(self) -> bool {
978 self.header().flag(HDR_HEAP_OWNED)
979 }
980
981 pub fn color(self) -> Color {
982 self.header().color.get()
983 }
984
985 pub fn set_color(self, color: Color) {
986 self.header().color.set(color);
987 }
988
989 pub fn age(self) -> GcAge {
990 self.header().age.get()
991 }
992
993 pub fn set_age(self, age: GcAge) {
994 self.header().age.set(age);
995 }
996
997 pub fn is_finalized(self) -> bool {
998 self.header().finalized()
999 }
1000
1001 pub fn set_finalized(self, finalized: bool) {
1002 self.header().set_finalized(finalized);
1003 }
1004
1005 pub fn account_buffer(&self, heap: &Heap, delta: isize) {
1015 if delta == 0 {
1016 return;
1017 }
1018 let header = self.header();
1019 if !header.collected() {
1020 return;
1021 }
1022 if delta >= 0 {
1023 header.set_size(header.size().saturating_add(delta as usize));
1024 } else {
1025 header.set_size(header.size().saturating_sub((-delta) as usize));
1026 }
1027 heap.adjust_bytes(delta);
1028 }
1029}
1030
1031impl<T: ?Sized> std::ops::Deref for Gc<T> {
1032 type Target = T;
1033 fn deref(&self) -> &T {
1034 &self.as_box().value
1035 }
1036}
1037
1038impl<T: ?Sized> AsRef<T> for Gc<T> {
1039 fn as_ref(&self) -> &T {
1040 &self.as_box().value
1041 }
1042}
1043
1044pub trait Trace {
1061 fn trace(&self, m: &mut Marker);
1062
1063 fn type_name(&self) -> &'static str {
1069 "unknown"
1070 }
1071}
1072
1073impl<T: Trace> Trace for Vec<T> {
1075 fn trace(&self, m: &mut Marker) {
1076 for item in self.iter() {
1077 item.trace(m);
1078 }
1079 }
1080}
1081
1082impl<T: Trace> Trace for Option<T> {
1083 fn trace(&self, m: &mut Marker) {
1084 if let Some(v) = self {
1085 v.trace(m);
1086 }
1087 }
1088}
1089
1090impl<T: Trace + ?Sized> Trace for Box<T> {
1091 fn trace(&self, m: &mut Marker) {
1092 (**self).trace(m);
1093 }
1094}
1095
1096impl<T: Trace + ?Sized> Trace for std::rc::Rc<T> {
1097 fn trace(&self, m: &mut Marker) {
1098 (**self).trace(m);
1099 }
1100}
1101
1102impl<T: Trace> Trace for RefCell<T> {
1103 fn trace(&self, m: &mut Marker) {
1104 self.borrow().trace(m);
1105 }
1106}
1107
1108impl<T: Trace + 'static> Trace for Gc<T> {
1110 fn trace(&self, m: &mut Marker) {
1111 m.mark(*self);
1112 }
1113}
1114
1115macro_rules! trace_noop {
1117 ($($t:ty),*) => {
1118 $(impl Trace for $t {
1119 fn trace(&self, _m: &mut Marker) {}
1120 })*
1121 };
1122}
1123trace_noop!(
1124 bool, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, char, String,
1125 str
1126);
1127
1128impl<T> Trace for std::marker::PhantomData<T> {
1129 fn trace(&self, _m: &mut Marker) {}
1130}
1131
1132#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1137pub struct MarkerStats {
1138 pub marked: usize,
1139 pub marked_young: usize,
1140 pub marked_old: usize,
1141 pub traced: usize,
1142 pub traced_young: usize,
1143 pub traced_old: usize,
1144}
1145
1146#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1148pub struct SweepStats {
1149 pub visited: usize,
1150 pub visited_young: usize,
1151 pub visited_old: usize,
1152 pub revisit: usize,
1153 pub freed: usize,
1154 pub freed_bytes: usize,
1155}
1156
1157impl SweepStats {
1158 fn record_visit(&mut self, age: GcAge) {
1159 self.visited += 1;
1160 if age.is_old() {
1161 self.visited_old += 1;
1162 } else {
1163 self.visited_young += 1;
1164 }
1165 }
1166
1167 fn record_free(&mut self, bytes: usize) {
1168 self.freed += 1;
1169 self.freed_bytes += bytes;
1170 }
1171
1172 fn add(&mut self, other: Self) {
1173 self.visited += other.visited;
1174 self.visited_young += other.visited_young;
1175 self.visited_old += other.visited_old;
1176 self.revisit += other.revisit;
1177 self.freed += other.freed;
1178 self.freed_bytes += other.freed_bytes;
1179 }
1180}
1181
1182struct OldRevisitTracker {
1183 old_revisit_ids: Vec<usize>,
1184 processed_ids: Vec<usize>,
1185}
1186
1187impl OldRevisitTracker {
1188 fn new(old_revisit: &[NonNull<GcBox<dyn Trace>>]) -> Option<Self> {
1189 if old_revisit.is_empty() {
1190 return None;
1191 }
1192 let mut old_revisit_ids: Vec<usize> = old_revisit
1193 .iter()
1194 .map(|ptr| ptr.as_ptr() as *const () as usize)
1195 .collect();
1196 old_revisit_ids.sort_unstable();
1197 old_revisit_ids.dedup();
1198 Some(Self {
1199 old_revisit_ids,
1200 processed_ids: Vec::new(),
1201 })
1202 }
1203
1204 #[inline(always)]
1205 fn record_processed(&mut self, id: usize) {
1206 if self.old_revisit_ids.binary_search(&id).is_ok() {
1207 self.processed_ids.push(id);
1208 }
1209 }
1210
1211 fn finish(&mut self) {
1212 self.processed_ids.sort_unstable();
1213 self.processed_ids.dedup();
1214 }
1215
1216 #[inline(always)]
1217 fn was_processed(&self, id: usize) -> bool {
1218 self.processed_ids.binary_search(&id).is_ok()
1219 }
1220}
1221
1222#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1224pub struct AllGcCohortStats {
1225 pub new: usize,
1226 pub survival: usize,
1227 pub old1: usize,
1228 pub old: usize,
1229}
1230
1231#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1232enum MarkerMode {
1233 Full,
1234 Minor,
1235}
1236
1237pub struct Marker {
1239 gray_queue: Vec<NonNull<GcBox<dyn Trace>>>,
1240 visited: IdentityHashSet,
1241 stats: MarkerStats,
1242 mode: MarkerMode,
1243}
1244
1245impl Marker {
1246 fn new_with_capacity(mode: MarkerMode, capacity: usize) -> Self {
1247 Self {
1248 gray_queue: Vec::with_capacity(256),
1249 visited: IdentityHashSet::with_capacity_and_hasher(
1250 capacity,
1251 IdentityBuildHasher::default(),
1252 ),
1253 stats: MarkerStats::default(),
1254 mode,
1255 }
1256 }
1257
1258 fn should_trace_age(&self, age: GcAge) -> bool {
1259 match self.mode {
1260 MarkerMode::Full => true,
1261 MarkerMode::Minor => !matches!(age, GcAge::Old),
1262 }
1263 }
1264
1265 pub fn mark<T: Trace + 'static>(&mut self, gc: Gc<T>) {
1278 let ptr: NonNull<GcBox<dyn Trace>> = gc.ptr;
1279 self.mark_box(ptr, gc.header(), gc.identity());
1280 }
1281
1282 fn mark_box(&mut self, ptr: NonNull<GcBox<dyn Trace>>, header: &GcHeader, id: usize) {
1283 debug_assert!(
1284 !header.flag(HDR_FREED),
1285 "GC marker reached a quarantined (swept) object at {id:#x} — a root \
1286 traced a stale GcRef (caught by LUA_RS_GC_QUARANTINE; bug-B class: \
1287 garbage slot fed into the marker)"
1288 );
1289 if self.visited.insert(id) {
1290 let age = header.age.get();
1291 self.stats.marked += 1;
1292 if age.is_old() {
1293 self.stats.marked_old += 1;
1294 } else {
1295 self.stats.marked_young += 1;
1296 }
1297 if self.should_trace_age(age) {
1298 header.color.set(Color::Gray);
1299 self.gray_queue.push(ptr);
1300 }
1301 }
1302 }
1303
1304 pub fn is_visited(&self, id: usize) -> bool {
1309 self.visited.contains(&id)
1310 }
1311
1312 pub fn is_marked_or_old<T: Trace + 'static>(&self, gc: Gc<T>) -> bool {
1315 self.is_visited(gc.identity())
1316 || (matches!(self.mode, MarkerMode::Minor) && gc.age().is_old())
1317 }
1318
1319 pub fn visited_count(&self) -> usize {
1323 self.visited.len()
1324 }
1325
1326 pub fn stats(&self) -> MarkerStats {
1328 self.stats
1329 }
1330
1331 pub fn drain_gray_queue(&mut self) {
1340 while let Some(gray_ptr) = self.gray_queue.pop() {
1341 unsafe {
1342 let bx = gray_ptr.as_ref();
1343 self.stats.traced += 1;
1344 if bx.header.age.get().is_old() {
1345 self.stats.traced_old += 1;
1346 } else {
1347 self.stats.traced_young += 1;
1348 }
1349 bx.header.color.set(Color::Black);
1350 bx.value.trace(self);
1351 }
1352 }
1353 }
1354}
1355
1356#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1375pub enum GcState {
1376 Pause,
1377 Propagate,
1378 EnterAtomic,
1379 Atomic,
1380 SweepAllGc,
1381 SweepFinObj,
1382 SweepToBeFnz,
1383 SweepEnd,
1384 CallFin,
1385}
1386
1387impl GcState {
1388 pub fn is_pause(self) -> bool {
1389 matches!(self, GcState::Pause)
1390 }
1391 pub fn is_propagate(self) -> bool {
1392 matches!(self, GcState::Propagate)
1393 }
1394 pub fn is_invariant(self) -> bool {
1395 matches!(
1396 self,
1397 GcState::Propagate | GcState::EnterAtomic | GcState::Atomic
1398 )
1399 }
1400 pub fn is_sweep(self) -> bool {
1401 matches!(
1402 self,
1403 GcState::SweepAllGc | GcState::SweepFinObj | GcState::SweepToBeFnz | GcState::SweepEnd
1404 )
1405 }
1406}
1407
1408#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1410pub enum StepOutcome {
1411 Paused,
1413 InProgress,
1416 SkippedStopped,
1419}
1420
1421#[derive(Copy, Clone, Debug)]
1429pub struct StepBudget {
1430 pub remaining_work: isize,
1431 pub max_credit: isize,
1432}
1433
1434impl StepBudget {
1435 pub fn from_work(work: isize) -> Self {
1437 Self {
1438 remaining_work: work.max(1),
1439 max_credit: work.max(1),
1440 }
1441 }
1442}
1443
1444const GC_MIN_THRESHOLD: usize = 1024 * 1024;
1459
1460pub struct Heap {
1461 head: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1464 finobj: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1466 tobefnz: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1468 survival: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1471 old1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1474 reallyold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1477 firstold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1480 finobjsur: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1482 finobjold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1484 finobjrold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1486 bytes: Cell<usize>,
1488 objects: Cell<usize>,
1490 current_white: Cell<Color>,
1492 allocation_tokens: RefCell<IdentityHashMap<usize>>,
1496 next_allocation_token: Cell<usize>,
1498 threshold: Cell<usize>,
1500 stress: bool,
1507 quarantine: bool,
1518 quarantined: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1522 uncollected: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1530 closed: Cell<bool>,
1539 tearing_down: Cell<bool>,
1547 bootstrap_depth: std::rc::Rc<Cell<usize>>,
1560 pause_multiplier: Cell<usize>,
1562 state: Cell<GcState>,
1564 paused: Cell<bool>,
1567 collections: Cell<usize>,
1569 minor_collections: Cell<usize>,
1571 full_collections: Cell<usize>,
1573 last_mark_stats: Cell<MarkerStats>,
1575 last_sweep_stats: Cell<SweepStats>,
1577 grayagain: RefCell<Vec<NonNull<GcBox<dyn Trace>>>>,
1586 grayagain_scratch: RefCell<Vec<NonNull<GcBox<dyn Trace>>>>,
1593 marker: RefCell<Option<Marker>>,
1596 marker_pool: RefCell<Option<(Vec<NonNull<GcBox<dyn Trace>>>, IdentityHashSet)>>,
1602 sweep_prev_next: Cell<Option<NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>>>,
1607 v51_udata_roster: RefCell<Vec<std::rc::Rc<dyn Udata51Probe>>>,
1614}
1615
1616impl Heap {
1617 pub fn new() -> std::rc::Rc<Self> {
1626 Self::build(
1627 std::env::var_os("LUA_RS_GC_STRESS").is_some_and(|v| v == "1"),
1628 std::env::var_os("LUA_RS_GC_QUARANTINE").is_some_and(|v| v == "1"),
1629 )
1630 }
1631
1632 #[doc(hidden)]
1640 pub fn new_quarantined() -> std::rc::Rc<Self> {
1641 Self::build(false, true)
1642 }
1643
1644 fn build(stress: bool, quarantine: bool) -> std::rc::Rc<Self> {
1645 std::rc::Rc::new(Self {
1646 head: Cell::new(None),
1647 finobj: Cell::new(None),
1648 tobefnz: Cell::new(None),
1649 survival: Cell::new(None),
1650 old1: Cell::new(None),
1651 reallyold: Cell::new(None),
1652 firstold1: Cell::new(None),
1653 finobjsur: Cell::new(None),
1654 finobjold1: Cell::new(None),
1655 finobjrold: Cell::new(None),
1656 bytes: Cell::new(0),
1657 objects: Cell::new(0),
1658 current_white: Cell::new(Color::White0),
1659 allocation_tokens: RefCell::new(IdentityHashMap::default()),
1660 next_allocation_token: Cell::new(1),
1661 threshold: Cell::new(64 * 1024), stress,
1663 quarantine,
1664 quarantined: Cell::new(None),
1665 uncollected: Cell::new(None),
1666 closed: Cell::new(false),
1667 tearing_down: Cell::new(false),
1668 bootstrap_depth: std::rc::Rc::new(Cell::new(0)),
1669 pause_multiplier: Cell::new(200), state: Cell::new(GcState::Pause),
1671 paused: Cell::new(true), collections: Cell::new(0),
1673 minor_collections: Cell::new(0),
1674 full_collections: Cell::new(0),
1675 last_mark_stats: Cell::new(MarkerStats::default()),
1676 last_sweep_stats: Cell::new(SweepStats::default()),
1677 grayagain: RefCell::new(Vec::new()),
1678 grayagain_scratch: RefCell::new(Vec::new()),
1679 marker: RefCell::new(None),
1680 marker_pool: RefCell::new(None),
1681 sweep_prev_next: Cell::new(None),
1682 v51_udata_roster: RefCell::new(Vec::new()),
1683 })
1684 }
1685
1686 pub fn unpause(&self) {
1689 self.paused.set(false);
1690 }
1691
1692 pub fn is_paused(&self) -> bool {
1693 self.paused.get()
1694 }
1695
1696 pub fn is_closed(&self) -> bool {
1704 self.closed.get()
1705 }
1706
1707 pub fn begin_bootstrap(&self) {
1715 self.bootstrap_depth.set(
1716 self.bootstrap_depth
1717 .get()
1718 .checked_add(1)
1719 .expect("Heap bootstrap depth overflow"),
1720 );
1721 }
1722
1723 pub fn end_bootstrap(&self) {
1726 let depth = self.bootstrap_depth.get();
1727 debug_assert!(depth > 0, "Heap::end_bootstrap without begin_bootstrap");
1728 self.bootstrap_depth.set(depth.saturating_sub(1));
1729 }
1730
1731 pub fn is_bootstrapping(&self) -> bool {
1732 self.bootstrap_depth.get() != 0
1733 }
1734
1735 pub fn bootstrap_scope(&self) -> BootstrapScope {
1742 self.begin_bootstrap();
1743 BootstrapScope {
1744 depth: std::rc::Rc::clone(&self.bootstrap_depth),
1745 }
1746 }
1747
1748 fn assert_open(&self) {
1755 if self.closed.get() && !self.tearing_down.get() {
1756 panic!("allocation into a closed heap — a HeapGuard outlived close()");
1757 }
1758 }
1759
1760 pub fn allocate<T: Trace + 'static>(&self, value: T) -> Gc<T> {
1764 self.assert_open();
1765 if self.is_bootstrapping() {
1766 return self.allocate_uncollected(value);
1767 }
1768 let size = std::mem::size_of::<GcBox<T>>();
1769 let boxed = Box::new(GcBox {
1770 header: GcHeader::new_white(
1771 size,
1772 self.current_white.get(),
1773 HDR_COLLECTED | HDR_HEAP_OWNED,
1774 ),
1775 value,
1776 });
1777 boxed.header.next.set(self.head.get());
1778 let raw: *mut GcBox<T> = Box::into_raw(boxed);
1779 let ptr: NonNull<GcBox<T>> = NonNull::new(raw).expect("Box::into_raw is non-null");
1780 let dyn_ptr: NonNull<GcBox<dyn Trace>> = ptr;
1781 self.head.set(Some(dyn_ptr));
1782 self.bytes.set(self.bytes.get() + size);
1783 self.objects.set(self.objects.get() + 1);
1784 Gc {
1785 ptr,
1786 _marker: PhantomData,
1787 }
1788 }
1789
1790 pub fn allocate_uncollected<T: Trace + 'static>(&self, value: T) -> Gc<T> {
1801 self.assert_open();
1802 let size = std::mem::size_of::<GcBox<T>>();
1803 let boxed = Box::new(GcBox {
1804 header: GcHeader::new_white(size, self.current_white.get(), HDR_HEAP_OWNED),
1805 value,
1806 });
1807 boxed.header.next.set(self.uncollected.get());
1808 let raw: *mut GcBox<T> = Box::into_raw(boxed);
1809 let ptr: NonNull<GcBox<T>> = NonNull::new(raw).expect("Box::into_raw is non-null");
1810 let dyn_ptr: NonNull<GcBox<dyn Trace>> = ptr;
1811 self.uncollected.set(Some(dyn_ptr));
1812 Gc {
1813 ptr,
1814 _marker: PhantomData,
1815 }
1816 }
1817
1818 pub fn bytes_used(&self) -> usize {
1820 self.bytes.get()
1821 }
1822
1823 pub fn adjust_bytes(&self, delta: isize) {
1828 if delta >= 0 {
1829 self.bytes
1830 .set(self.bytes.get().saturating_add(delta as usize));
1831 } else {
1832 self.bytes
1833 .set(self.bytes.get().saturating_sub((-delta) as usize));
1834 }
1835 }
1836
1837 pub fn threshold_bytes(&self) -> usize {
1842 self.threshold.get()
1843 }
1844
1845 pub fn set_threshold_bytes(&self, threshold: usize) {
1851 self.threshold.set(threshold.max(1));
1852 }
1853
1854 pub fn would_collect(&self) -> bool {
1859 if self.collection_inert() {
1860 return false;
1861 }
1862 if self.stress {
1863 return true;
1864 }
1865 self.bytes.get() >= self.threshold.get()
1866 }
1867
1868 fn collection_inert(&self) -> bool {
1878 self.paused.get() || self.closed.get()
1879 }
1880
1881 pub fn collections(&self) -> usize {
1882 self.collections.get()
1883 }
1884
1885 pub fn minor_collections(&self) -> usize {
1886 self.minor_collections.get()
1887 }
1888
1889 pub fn full_collections(&self) -> usize {
1890 self.full_collections.get()
1891 }
1892
1893 pub fn last_mark_stats(&self) -> MarkerStats {
1894 self.last_mark_stats.get()
1895 }
1896
1897 pub fn last_sweep_stats(&self) -> SweepStats {
1898 self.last_sweep_stats.get()
1899 }
1900
1901 pub fn allgc_cohort_stats(&self) -> AllGcCohortStats {
1902 let survival = self.survival.get();
1903 let old1 = self.old1.get();
1904 let reallyold = self.reallyold.get();
1905 let mut stats = AllGcCohortStats::default();
1906 let mut cursor = self.head.get();
1907 let mut seen = IdentityHashSet::default();
1908 let mut cohort = 0u8;
1909 while let Some(ptr) = cursor {
1910 let id = ptr.as_ptr() as *const () as usize;
1911 if !seen.insert(id) {
1912 break;
1913 }
1914 if Some(ptr) == reallyold {
1915 cohort = 3;
1916 } else if Some(ptr) == old1 {
1917 cohort = 2;
1918 } else if Some(ptr) == survival {
1919 cohort = 1;
1920 }
1921 match cohort {
1922 0 => stats.new += 1,
1923 1 => stats.survival += 1,
1924 2 => stats.old1 += 1,
1925 _ => stats.old += 1,
1926 }
1927 cursor = self.header_from_ptr(ptr).next.get();
1928 }
1929 stats
1930 }
1931
1932 fn next_token(&self) -> usize {
1933 let token = self.next_allocation_token.get().max(1);
1934 let next = token.checked_add(1).unwrap_or(1).max(1);
1935 self.next_allocation_token.set(next);
1936 token
1937 }
1938
1939 fn current_white(&self) -> Color {
1940 self.current_white.get()
1941 }
1942
1943 fn other_white(&self) -> Color {
1944 self.current_white.get().other_white()
1945 }
1946
1947 fn flip_current_white(&self) {
1948 self.current_white.set(self.other_white());
1949 }
1950
1951 fn for_each_list_header(
1952 &self,
1953 head: Option<NonNull<GcBox<dyn Trace>>>,
1954 f: &mut impl FnMut(&GcHeader),
1955 ) {
1956 let mut cursor = head;
1957 while let Some(ptr) = cursor {
1958 let header = self.header_from_ptr(ptr);
1959 cursor = header.next.get();
1960 f(header);
1961 }
1962 }
1963
1964 fn for_each_header(&self, mut f: impl FnMut(&GcHeader)) {
1965 self.for_each_list_header(self.head.get(), &mut f);
1966 self.for_each_list_header(self.finobj.get(), &mut f);
1967 self.for_each_list_header(self.tobefnz.get(), &mut f);
1968 }
1969
1970 fn header_from_ptr<'a>(&'a self, ptr: NonNull<GcBox<dyn Trace>>) -> &'a GcHeader {
1971 unsafe { &(*ptr.as_ptr()).header }
1972 }
1973
1974 fn release_box(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1991 if self.header_from_ptr(ptr).gray_listed() {
1992 self.unlink_grayagain(ptr);
1993 }
1994 if self.quarantine {
1995 let header = self.header_from_ptr(ptr);
1996 header.set_flag(HDR_FREED, true);
1997 header.next.set(self.quarantined.get());
1998 self.quarantined.set(Some(ptr));
1999 } else {
2000 unsafe {
2004 let _ = Box::from_raw(ptr.as_ptr());
2005 }
2006 }
2007 }
2008
2009 fn clear_generation_cursors(&self) {
2010 self.survival.set(None);
2011 self.old1.set(None);
2012 self.reallyold.set(None);
2013 self.firstold1.set(None);
2014 self.finobjsur.set(None);
2015 self.finobjold1.set(None);
2016 self.finobjrold.set(None);
2017 self.clear_grayagain();
2018 }
2019
2020 fn set_all_cursors_to_head(&self) {
2021 let head = self.head.get();
2022 self.survival.set(head);
2023 self.old1.set(head);
2024 self.reallyold.set(head);
2025 self.firstold1.set(None);
2026 let finobj = self.finobj.get();
2027 self.finobjsur.set(finobj);
2028 self.finobjold1.set(finobj);
2029 self.finobjrold.set(finobj);
2030 self.clear_grayagain();
2031 }
2032
2033 fn correct_generation_pointers(
2045 &self,
2046 removed: NonNull<GcBox<dyn Trace>>,
2047 next: Option<NonNull<GcBox<dyn Trace>>>,
2048 ) {
2049 if self.survival.get() == Some(removed) {
2050 self.survival.set(next);
2051 }
2052 if self.old1.get() == Some(removed) {
2053 self.old1.set(next);
2054 }
2055 if self.reallyold.get() == Some(removed) {
2056 self.reallyold.set(next);
2057 }
2058 if self.firstold1.get() == Some(removed) {
2059 self.firstold1.set(next);
2060 }
2061 if self.finobjsur.get() == Some(removed) {
2062 self.finobjsur.set(next);
2063 }
2064 if self.finobjold1.get() == Some(removed) {
2065 self.finobjold1.set(next);
2066 }
2067 if self.finobjrold.get() == Some(removed) {
2068 self.finobjrold.set(next);
2069 }
2070 }
2071
2072 fn unlink_from_list(
2073 &self,
2074 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
2075 ptr: NonNull<GcBox<dyn Trace>>,
2076 ) -> bool {
2077 let mut prev_cell = list;
2078 loop {
2079 let Some(current) = prev_cell.get() else {
2080 return false;
2081 };
2082 let header = self.header_from_ptr(current);
2083 let next = header.next.get();
2084 if std::ptr::addr_eq(current.as_ptr(), ptr.as_ptr()) {
2085 prev_cell.set(next);
2086 let prev_next_ptr = NonNull::from(prev_cell);
2087 let removed_next_ptr = NonNull::from(&self.header_from_ptr(ptr).next);
2088 if self.sweep_prev_next.get() == Some(removed_next_ptr) {
2089 self.sweep_prev_next.set(Some(prev_next_ptr));
2090 }
2091 self.correct_generation_pointers(ptr, next);
2092 header.next.set(None);
2093 return true;
2094 }
2095 prev_cell = &header.next;
2096 }
2097 }
2098
2099 fn link_to_head(
2100 &self,
2101 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
2102 ptr: NonNull<GcBox<dyn Trace>>,
2103 ) {
2104 let header = self.header_from_ptr(ptr);
2105 header.next.set(list.get());
2106 list.set(Some(ptr));
2107 }
2108
2109 fn link_to_tail(
2110 &self,
2111 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
2112 ptr: NonNull<GcBox<dyn Trace>>,
2113 ) {
2114 let mut last_cell = list;
2115 loop {
2116 let Some(current) = last_cell.get() else {
2117 let header = self.header_from_ptr(ptr);
2118 header.next.set(None);
2119 last_cell.set(Some(ptr));
2120 return;
2121 };
2122 last_cell = &self.header_from_ptr(current).next;
2123 }
2124 }
2125
2126 pub fn move_allgc_to_finobj(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
2127 let header = self.header_from_ptr(ptr);
2128 if !header.collected() {
2129 return false;
2130 }
2131 if !self.unlink_from_list(&self.head, ptr) {
2132 return false;
2133 }
2134 if self.state.get().is_sweep() {
2135 header.color.set(self.current_white());
2136 }
2137 self.link_to_head(&self.finobj, ptr);
2138 true
2139 }
2140
2141 pub fn move_finobj_to_tobefnz(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
2142 if !self.unlink_from_list(&self.finobj, ptr) {
2143 return false;
2144 }
2145 self.link_to_tail(&self.tobefnz, ptr);
2146 true
2147 }
2148
2149 pub fn move_tobefnz_to_allgc(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
2150 let header = self.header_from_ptr(ptr);
2151 if !self.unlink_from_list(&self.tobefnz, ptr) {
2152 return false;
2153 }
2154 if self.state.get().is_sweep() {
2155 header.color.set(self.current_white());
2156 }
2157 self.link_to_head(&self.head, ptr);
2158 if header.age.get() == GcAge::Old1 {
2159 self.firstold1.set(Some(ptr));
2160 }
2161 true
2162 }
2163
2164 fn remember_minor_revisit(&self, ptr: NonNull<GcBox<dyn Trace>>) {
2181 if self.closed.get() {
2182 return;
2183 }
2184 let header = self.header_from_ptr(ptr);
2185 if header.gray_listed() {
2186 return;
2187 }
2188 header.set_gray_listed(true);
2189 self.grayagain.borrow_mut().push(ptr);
2190 }
2191
2192 fn mark_minor_revisit_objects(&self, marker: &mut Marker) {
2197 let grayagain = self.grayagain.borrow();
2198 for &ptr in grayagain.iter() {
2199 let header = self.header_from_ptr(ptr);
2200 let id = ptr.as_ptr() as *const () as usize;
2201 marker.mark_box(ptr, header, id);
2202 }
2203 }
2204
2205 fn clear_grayagain(&self) {
2208 let mut grayagain = self.grayagain.borrow_mut();
2209 for &ptr in grayagain.iter() {
2210 self.header_from_ptr(ptr).set_gray_listed(false);
2211 }
2212 grayagain.clear();
2213 }
2214
2215 fn take_grayagain(&self) -> Vec<NonNull<GcBox<dyn Trace>>> {
2220 let objects = std::mem::take(&mut *self.grayagain.borrow_mut());
2221 for &ptr in &objects {
2222 self.header_from_ptr(ptr).set_gray_listed(false);
2223 }
2224 objects
2225 }
2226
2227 fn replace_grayagain(&self, objects: Vec<NonNull<GcBox<dyn Trace>>>) {
2232 self.clear_grayagain();
2233 for &ptr in &objects {
2234 self.header_from_ptr(ptr).set_gray_listed(true);
2235 }
2236 *self.grayagain.borrow_mut() = objects;
2237 }
2238
2239 fn unlink_grayagain(&self, removed: NonNull<GcBox<dyn Trace>>) {
2246 self.header_from_ptr(removed).set_gray_listed(false);
2247 self.grayagain
2248 .borrow_mut()
2249 .retain(|ptr| !std::ptr::addr_eq(ptr.as_ptr(), removed.as_ptr()));
2250 }
2251
2252 pub fn grayagain_count(&self) -> usize {
2256 self.grayagain.borrow().len()
2257 }
2258
2259 pub fn register_v51_udata(&self, probe: std::rc::Rc<dyn Udata51Probe>) {
2266 self.v51_udata_roster.borrow_mut().push(probe);
2267 }
2268
2269 pub fn scan_v51_finalizable(&self) -> Vec<std::rc::Rc<dyn Udata51Probe>> {
2279 let mut roster = self.v51_udata_roster.borrow_mut();
2280 roster.retain(|probe| probe.is_alive());
2281 roster.clone()
2282 }
2283
2284 pub fn allocation_token(&self, identity: usize) -> Option<usize> {
2292 self.allocation_tokens.borrow().get(&identity).copied()
2293 }
2294
2295 pub fn register_allocation_token(&self, identity: usize) -> usize {
2319 if self.closed.get() {
2320 return 0;
2321 }
2322 let mut tokens = self.allocation_tokens.borrow_mut();
2323 if let Some(token) = tokens.get(&identity) {
2324 return *token;
2325 }
2326 let token = self.next_token();
2327 tokens.insert(identity, token);
2328 token
2329 }
2330
2331 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
2338 if self.closed.get() {
2339 return false;
2340 }
2341 self.allocation_token(identity) == Some(token)
2342 }
2343
2344 pub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2354 where
2355 P: Trace + 'static,
2356 C: Trace + 'static,
2357 {
2358 if self.paused.get() || self.state.get().is_pause() {
2359 return;
2360 }
2361 if parent.header().color.get() != Color::Black {
2362 return;
2363 }
2364 if !child.header().color.get().is_white() {
2365 return;
2366 }
2367 child.header().color.set(Color::Gray);
2368 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
2369 if let Some(m) = m_opt.as_mut() {
2370 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
2371 m.gray_queue.push(ptr);
2372 m.visited.insert(child.identity());
2373 }
2374 }
2375 }
2376
2377 pub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2380 where
2381 P: Trace + 'static,
2382 C: Trace + 'static,
2383 {
2384 if self.paused.get() || self.state.get().is_pause() {
2385 return;
2386 }
2387 if parent.header().color.get() != Color::Black {
2388 return;
2389 }
2390 if !child.header().color.get().is_white() {
2391 return;
2392 }
2393 parent.header().color.set(Color::Gray);
2394 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
2395 if let Some(m) = m_opt.as_mut() {
2396 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
2397 m.gray_queue.push(ptr);
2398 m.visited.insert(parent.identity());
2399 }
2400 }
2401 }
2402
2403 pub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2408 where
2409 P: Trace + 'static,
2410 C: Trace + 'static,
2411 {
2412 if parent.age().is_old() && !child.age().is_old() {
2413 child.set_age(GcAge::Old0);
2414 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
2415 self.remember_minor_revisit(ptr);
2416 }
2417 self.barrier(parent, child);
2418 }
2419
2420 pub fn generational_backward_barrier<P>(&self, parent: Gc<P>)
2424 where
2425 P: Trace + 'static,
2426 {
2427 if parent.age().is_old() {
2428 parent.set_color(Color::Gray);
2429 parent.set_age(GcAge::Touched1);
2430 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
2431 self.remember_minor_revisit(ptr);
2432 }
2433 }
2434
2435 pub fn step(&self, roots: &dyn Trace) {
2439 self.step_with_post_mark(roots, |_: &mut Marker| {});
2440 }
2441
2442 pub fn step_with_post_mark<F: FnMut(&mut Marker)>(&self, roots: &dyn Trace, post_mark: F) {
2447 if self.collection_inert() {
2448 return;
2449 }
2450 if !self.stress && self.bytes.get() < self.threshold.get() {
2451 return;
2452 }
2453 self.full_collect_with_post_mark(roots, post_mark);
2454 }
2455
2456 pub fn full_collect(&self, roots: &dyn Trace) {
2459 self.full_collect_with_post_mark(roots, |_: &mut Marker| {});
2460 }
2461
2462 pub fn mark_only_with_post_mark<F: FnMut(&mut Marker)>(
2467 &self,
2468 roots: &dyn Trace,
2469 mut post_mark: F,
2470 ) {
2471 if self.collection_inert() {
2472 return;
2473 }
2474 let mut marker = self.marker_from_pool(MarkerMode::Full);
2475 roots.trace(&mut marker);
2476 marker.drain_gray_queue();
2477 post_mark(&mut marker);
2478 marker.drain_gray_queue();
2479 self.last_mark_stats.set(marker.stats());
2480 self.recycle_marker(marker);
2481 }
2482
2483 pub fn promote_all_to_old(&self) {
2486 self.for_each_header(|header| {
2487 header.age.set(GcAge::Old);
2488 header.color.set(Color::Black);
2489 });
2490 self.set_all_cursors_to_head();
2491 }
2492
2493 pub fn reset_all_ages(&self) {
2496 let current_white = self.current_white();
2497 self.for_each_header(|header| {
2498 header.age.set(GcAge::New);
2499 header.color.set(current_white);
2500 });
2501 self.clear_generation_cursors();
2502 }
2503
2504 pub fn minor_collect_with_post_mark<F: FnMut(&mut Marker)>(
2511 &self,
2512 roots: &dyn Trace,
2513 mut post_mark: F,
2514 ) {
2515 if self.collection_inert() {
2516 return;
2517 }
2518 if !self.state.get().is_pause() {
2519 self.abort_cycle();
2520 }
2521
2522 self.state.set(GcState::Propagate);
2523 let mut marker = self.marker_from_pool(MarkerMode::Minor);
2524 self.last_sweep_stats.set(SweepStats::default());
2525 self.mark_minor_revisit_objects(&mut marker);
2526 roots.trace(&mut marker);
2527 marker.drain_gray_queue();
2528
2529 self.state.set(GcState::EnterAtomic);
2530 self.state.set(GcState::Atomic);
2531 post_mark(&mut marker);
2532 marker.drain_gray_queue();
2533 self.last_mark_stats.set(marker.stats());
2534 self.recycle_marker(marker);
2535
2536 self.state.set(GcState::SweepAllGc);
2537 self.sweep_young();
2538 self.recycle_marker_cell();
2539 self.sweep_prev_next.set(None);
2540 self.state.set(GcState::Pause);
2541 self.collections.set(self.collections.get() + 1);
2542 self.minor_collections.set(self.minor_collections.get() + 1);
2543 }
2544
2545 pub fn full_collect_with_post_mark<F: FnMut(&mut Marker)>(
2552 &self,
2553 roots: &dyn Trace,
2554 mut post_mark: F,
2555 ) {
2556 if self.collection_inert() {
2557 return;
2558 }
2559 if !self.state.get().is_pause() {
2560 self.abort_cycle();
2561 }
2562 self.full_collections.set(self.full_collections.get() + 1);
2563 let unlimited = StepBudget {
2564 remaining_work: isize::MAX,
2565 max_credit: isize::MAX,
2566 };
2567 loop {
2568 let outcome = self.incremental_step_with_post_mark(roots, unlimited, &mut post_mark);
2569 if matches!(outcome, StepOutcome::Paused | StepOutcome::SkippedStopped) {
2570 break;
2571 }
2572 }
2573 }
2574
2575 pub fn incremental_step_with_post_mark<F: FnMut(&mut Marker)>(
2590 &self,
2591 roots: &dyn Trace,
2592 mut budget: StepBudget,
2593 mut post_mark: F,
2594 ) -> StepOutcome {
2595 if self.collection_inert() {
2596 return StepOutcome::SkippedStopped;
2597 }
2598 self.run_budgeted(roots, &mut budget, &mut post_mark);
2599 if self.state.get().is_pause() {
2600 StepOutcome::Paused
2601 } else {
2602 StepOutcome::InProgress
2603 }
2604 }
2605
2606 fn run_budgeted(
2607 &self,
2608 roots: &dyn Trace,
2609 budget: &mut StepBudget,
2610 post_mark: &mut dyn FnMut(&mut Marker),
2611 ) -> bool {
2612 self.run_budgeted_until(roots, budget, post_mark, None)
2613 }
2614
2615 fn run_budgeted_until(
2616 &self,
2617 roots: &dyn Trace,
2618 budget: &mut StepBudget,
2619 post_mark: &mut dyn FnMut(&mut Marker),
2620 stop_at: Option<GcState>,
2621 ) -> bool {
2622 let mut did_work = false;
2623 loop {
2624 if stop_at == Some(self.state.get()) {
2625 return did_work;
2626 }
2627 if budget.remaining_work <= -budget.max_credit {
2628 return did_work;
2629 }
2630 match self.state.get() {
2631 GcState::Pause => {
2632 self.start_cycle(roots);
2633 self.state.set(GcState::Propagate);
2634 budget.remaining_work -= 1;
2635 did_work = true;
2636 if stop_at == Some(GcState::Propagate) {
2637 return did_work;
2638 }
2639 }
2640 GcState::Propagate => {
2641 let work = self.drain_gray_budgeted(budget.remaining_work.max(1));
2642 budget.remaining_work -= work as isize;
2643 did_work = did_work || work > 0;
2644 let empty = {
2645 let m = self.marker.borrow();
2646 m.as_ref().map(|m| m.gray_queue.is_empty()).unwrap_or(true)
2647 };
2648 if empty {
2649 self.state.set(GcState::EnterAtomic);
2650 if stop_at == Some(GcState::EnterAtomic) {
2651 return did_work;
2652 }
2653 } else if budget.remaining_work <= 0 {
2654 return did_work;
2655 }
2656 }
2657 GcState::EnterAtomic => {
2658 self.state.set(GcState::Atomic);
2659 budget.remaining_work -= 1;
2660 did_work = true;
2661 if stop_at == Some(GcState::Atomic) || budget.remaining_work <= 0 {
2662 return did_work;
2663 }
2664 }
2665 GcState::Atomic => {
2666 self.run_atomic(post_mark);
2667 self.state.set(GcState::SweepAllGc);
2668 budget.remaining_work -= 1;
2669 did_work = true;
2670 if stop_at == Some(GcState::SweepAllGc) {
2671 return did_work;
2672 }
2673 }
2674 GcState::SweepAllGc => {
2675 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2676 budget.remaining_work -= work as isize;
2677 did_work = did_work || work > 0;
2678 if self.sweep_prev_next.get().is_none() {
2679 self.state.set(GcState::SweepFinObj);
2680 self.sweep_prev_next.set(Some(NonNull::from(&self.finobj)));
2681 if stop_at == Some(GcState::SweepFinObj) {
2682 return did_work;
2683 }
2684 } else if budget.remaining_work <= 0 {
2685 return did_work;
2686 }
2687 }
2688 GcState::SweepFinObj => {
2689 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2690 budget.remaining_work -= work as isize;
2691 did_work = did_work || work > 0;
2692 if self.sweep_prev_next.get().is_none() {
2693 self.state.set(GcState::SweepToBeFnz);
2694 self.sweep_prev_next.set(Some(NonNull::from(&self.tobefnz)));
2695 if stop_at == Some(GcState::SweepToBeFnz) {
2696 return did_work;
2697 }
2698 } else if budget.remaining_work <= 0 {
2699 return did_work;
2700 }
2701 }
2702 GcState::SweepToBeFnz => {
2703 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2704 budget.remaining_work -= work as isize;
2705 did_work = did_work || work > 0;
2706 if self.sweep_prev_next.get().is_none() {
2707 self.state.set(GcState::SweepEnd);
2708 if stop_at == Some(GcState::SweepEnd) {
2709 return did_work;
2710 }
2711 } else if budget.remaining_work <= 0 {
2712 return did_work;
2713 }
2714 }
2715 GcState::SweepEnd => {
2716 self.state.set(GcState::CallFin);
2717 budget.remaining_work -= 1;
2718 did_work = true;
2719 if stop_at == Some(GcState::CallFin) || budget.remaining_work <= 0 {
2720 return did_work;
2721 }
2722 }
2723 GcState::CallFin => {
2724 self.finish_cycle();
2725 self.state.set(GcState::Pause);
2726 if stop_at == Some(GcState::Pause) {
2727 return did_work;
2728 }
2729 return did_work;
2730 }
2731 }
2732 }
2733 }
2734
2735 pub fn incremental_run_until_state_with_post_mark<F: FnMut(&mut Marker)>(
2740 &self,
2741 roots: &dyn Trace,
2742 target: GcState,
2743 max_work: isize,
2744 mut post_mark: F,
2745 ) -> StepOutcome {
2746 if self.collection_inert() {
2747 return StepOutcome::SkippedStopped;
2748 }
2749 let work = max_work.max(1);
2750 let mut budget = StepBudget {
2751 remaining_work: work,
2752 max_credit: work,
2753 };
2754 self.run_budgeted_until(roots, &mut budget, &mut post_mark, Some(target));
2755 if self.state.get().is_pause() {
2756 StepOutcome::Paused
2757 } else {
2758 StepOutcome::InProgress
2759 }
2760 }
2761
2762 fn marker_from_pool(&self, mode: MarkerMode) -> Marker {
2765 match self.marker_pool.borrow_mut().take() {
2766 Some((gray_queue, visited)) => Marker {
2767 gray_queue,
2768 visited,
2769 stats: MarkerStats::default(),
2770 mode,
2771 },
2772 None => Marker::new_with_capacity(mode, self.objects.get()),
2773 }
2774 }
2775
2776 fn recycle_marker(&self, marker: Marker) {
2777 let Marker {
2778 mut gray_queue,
2779 mut visited,
2780 ..
2781 } = marker;
2782 gray_queue.clear();
2783 visited.clear();
2784 *self.marker_pool.borrow_mut() = Some((gray_queue, visited));
2785 }
2786
2787 fn recycle_marker_cell(&self) {
2788 if let Some(marker) = self.marker.borrow_mut().take() {
2789 self.recycle_marker(marker);
2790 }
2791 }
2792
2793 fn start_cycle(&self, roots: &dyn Trace) {
2794 self.flip_current_white();
2795 let dead_white = self.other_white();
2796 self.for_each_header(|header| {
2797 header.color.set(dead_white);
2798 });
2799 let mut marker = self.marker_from_pool(MarkerMode::Full);
2800 roots.trace(&mut marker);
2801 *self.marker.borrow_mut() = Some(marker);
2802 self.sweep_prev_next.set(None);
2803 }
2804
2805 fn drain_gray_budgeted(&self, max_units: isize) -> usize {
2806 let mut m_opt = self.marker.borrow_mut();
2807 let marker = match m_opt.as_mut() {
2808 Some(m) => m,
2809 None => return 0,
2810 };
2811 let mut work = 0usize;
2812 let mut budget = max_units;
2813 while budget > 0 {
2814 let next = match marker.gray_queue.pop() {
2815 Some(p) => p,
2816 None => break,
2817 };
2818 unsafe {
2819 let bx = next.as_ref();
2820 marker.stats.traced += 1;
2821 if bx.header.age.get().is_old() {
2822 marker.stats.traced_old += 1;
2823 } else {
2824 marker.stats.traced_young += 1;
2825 }
2826 bx.header.color.set(Color::Black);
2827 bx.value.trace(marker);
2828 }
2829 work += 1;
2830 budget -= 1;
2831 }
2832 work
2833 }
2834
2835 fn run_atomic(&self, post_mark: &mut dyn FnMut(&mut Marker)) {
2836 let mut m_opt = self.marker.borrow_mut();
2837 if let Some(marker) = m_opt.as_mut() {
2838 marker.drain_gray_queue();
2839 post_mark(marker);
2840 marker.drain_gray_queue();
2841 }
2842 self.sweep_prev_next.set(Some(NonNull::from(&self.head)));
2843 self.last_sweep_stats.set(SweepStats::default());
2844 }
2845
2846 fn sweep_budgeted(&self, max_units: isize) -> usize {
2847 let mut work = 0usize;
2848 let mut budget = max_units;
2849 let mut freed_bytes = 0usize;
2850 let mut stats = SweepStats::default();
2851 let current_white = self.current_white();
2852 let dead_white = self.other_white();
2853 let mut prev_next_ptr = match self.sweep_prev_next.get() {
2854 Some(p) => p,
2855 None => return 0,
2856 };
2857 while budget > 0 {
2858 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2859 let cursor = prev_cell.get();
2860 let ptr = match cursor {
2861 Some(p) => p,
2862 None => {
2863 self.sweep_prev_next.set(None);
2864 break;
2865 }
2866 };
2867 let header = self.header_from_ptr(ptr);
2868 let next = header.next.get();
2869 let age = header.age.get();
2870 stats.record_visit(age);
2871 let color = header.color.get();
2872 if color == dead_white {
2873 prev_cell.set(next);
2874 let size = header.size();
2875 freed_bytes += size;
2876 stats.record_free(size);
2877 self.correct_generation_pointers(ptr, next);
2878 self.allocation_tokens
2879 .borrow_mut()
2880 .remove(&(ptr.as_ptr() as *const () as usize));
2881 self.objects.set(self.objects.get().saturating_sub(1));
2882 self.release_box(ptr);
2883 } else {
2884 if matches!(color, Color::Black | Color::Gray) {
2885 header.color.set(current_white);
2886 }
2887 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2888 self.sweep_prev_next.set(Some(prev_next_ptr));
2889 }
2890 work += 1;
2891 budget -= 1;
2892 }
2893 if freed_bytes > 0 {
2894 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2895 }
2896 if stats.visited > 0 {
2897 let mut total = self.last_sweep_stats.get();
2898 total.add(stats);
2899 self.last_sweep_stats.set(total);
2900 }
2901 work
2902 }
2903
2904 fn push_next_revisit(
2905 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2906 seen: &mut IdentityHashSet,
2907 ptr: NonNull<GcBox<dyn Trace>>,
2908 age: GcAge,
2909 ) {
2910 if matches!(
2911 age,
2912 GcAge::Old0 | GcAge::Old1 | GcAge::Touched1 | GcAge::Touched2
2913 ) {
2914 let id = ptr.as_ptr() as *const () as usize;
2915 if seen.insert(id) {
2916 next_revisit.push(ptr);
2917 }
2918 }
2919 }
2920
2921 fn sweep_young_range(
2922 &self,
2923 mut prev_next_ptr: NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2924 limit: Option<NonNull<GcBox<dyn Trace>>>,
2925 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2926 next_revisit_seen: &mut IdentityHashSet,
2927 processed: &mut Option<OldRevisitTracker>,
2928 firstold1: &mut Option<NonNull<GcBox<dyn Trace>>>,
2929 freed_bytes: &mut usize,
2930 stats: &mut SweepStats,
2931 ) -> (
2932 NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2933 Option<NonNull<GcBox<dyn Trace>>>,
2934 ) {
2935 let current_white = self.current_white();
2936 loop {
2937 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2938 let Some(ptr) = prev_cell.get() else {
2939 return (prev_next_ptr, None);
2940 };
2941 if Some(ptr) == limit {
2942 return (prev_next_ptr, Some(ptr));
2943 }
2944 let header = self.header_from_ptr(ptr);
2945 let next = header.next.get();
2946 let age = header.age.get();
2947 stats.record_visit(age);
2948 if let Some(processed) = processed.as_mut() {
2949 processed.record_processed(ptr.as_ptr() as *const () as usize);
2950 }
2951 if header.color.get().is_white() && !age.is_old() {
2952 prev_cell.set(next);
2953 let size = header.size();
2954 *freed_bytes += size;
2955 stats.record_free(size);
2956 self.correct_generation_pointers(ptr, next);
2957 self.allocation_tokens
2958 .borrow_mut()
2959 .remove(&(ptr.as_ptr() as *const () as usize));
2960 self.objects.set(self.objects.get().saturating_sub(1));
2961 self.release_box(ptr);
2962 continue;
2963 }
2964
2965 if !header.color.get().is_white() {
2966 let next_age = age.next_after_minor();
2967 header.age.set(next_age);
2968 if next_age == GcAge::Old1 && firstold1.is_none() {
2969 *firstold1 = Some(ptr);
2970 }
2971 match age {
2972 GcAge::New => header.color.set(current_white),
2973 GcAge::Touched1 | GcAge::Touched2 => header.color.set(Color::Black),
2974 _ => {}
2975 }
2976 Self::push_next_revisit(next_revisit, next_revisit_seen, ptr, next_age);
2977 }
2978 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2979 }
2980 }
2981
2982 fn sweep_young(&self) {
2983 let mut freed_bytes = 0usize;
2984 let mut next_revisit = std::mem::take(&mut *self.grayagain_scratch.borrow_mut());
2985 debug_assert!(next_revisit.is_empty(), "grayagain scratch must be parked empty");
2986 let mut next_revisit_seen = IdentityHashSet::default();
2987 let mut firstold1 = None;
2988 let mut stats = SweepStats::default();
2989 let old_revisit = self.take_grayagain();
2990 let mut processed = OldRevisitTracker::new(&old_revisit);
2991 let survival = self.survival.get();
2992 let old1 = self.old1.get();
2993
2994 let (psurvival, new_old1) = self.sweep_young_range(
2995 NonNull::from(&self.head),
2996 survival,
2997 &mut next_revisit,
2998 &mut next_revisit_seen,
2999 &mut processed,
3000 &mut firstold1,
3001 &mut freed_bytes,
3002 &mut stats,
3003 );
3004 self.sweep_young_range(
3005 psurvival,
3006 old1,
3007 &mut next_revisit,
3008 &mut next_revisit_seen,
3009 &mut processed,
3010 &mut firstold1,
3011 &mut freed_bytes,
3012 &mut stats,
3013 );
3014
3015 let finobjsur = self.finobjsur.get();
3016 let finobjold1 = self.finobjold1.get();
3017 let mut dummy_firstold1 = None;
3018 let (pfinobjsur, new_finobjold1) = self.sweep_young_range(
3019 NonNull::from(&self.finobj),
3020 finobjsur,
3021 &mut next_revisit,
3022 &mut next_revisit_seen,
3023 &mut processed,
3024 &mut dummy_firstold1,
3025 &mut freed_bytes,
3026 &mut stats,
3027 );
3028 self.sweep_young_range(
3029 pfinobjsur,
3030 finobjold1,
3031 &mut next_revisit,
3032 &mut next_revisit_seen,
3033 &mut processed,
3034 &mut dummy_firstold1,
3035 &mut freed_bytes,
3036 &mut stats,
3037 );
3038 self.sweep_young_range(
3039 NonNull::from(&self.tobefnz),
3040 None,
3041 &mut next_revisit,
3042 &mut next_revisit_seen,
3043 &mut processed,
3044 &mut dummy_firstold1,
3045 &mut freed_bytes,
3046 &mut stats,
3047 );
3048
3049 if let Some(processed) = processed.as_mut() {
3050 processed.finish();
3051 }
3052
3053 for &ptr in &old_revisit {
3054 let id = ptr.as_ptr() as *const () as usize;
3055 if processed
3056 .as_ref()
3057 .is_some_and(|processed| processed.was_processed(id))
3058 {
3059 continue;
3060 }
3061 stats.revisit += 1;
3062 let header = self.header_from_ptr(ptr);
3063 if header.color.get().is_white() {
3064 continue;
3065 }
3066 let age = header.age.get();
3067 let next_age = age.next_after_minor();
3068 header.age.set(next_age);
3069 if next_age == GcAge::Old1 && firstold1.is_none() {
3070 firstold1 = Some(ptr);
3071 }
3072 if matches!(age, GcAge::Touched1 | GcAge::Touched2) {
3073 header.color.set(Color::Black);
3074 }
3075 Self::push_next_revisit(&mut next_revisit, &mut next_revisit_seen, ptr, next_age);
3076 }
3077
3078 if freed_bytes > 0 {
3079 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
3080 }
3081 let mut recycled = old_revisit;
3082 recycled.clear();
3083 *self.grayagain_scratch.borrow_mut() = recycled;
3084 self.replace_grayagain(next_revisit);
3085 self.reallyold.set(old1);
3086 self.old1.set(new_old1);
3087 self.survival.set(self.head.get());
3088 self.firstold1.set(firstold1);
3089 self.finobjrold.set(finobjold1);
3090 self.finobjold1.set(new_finobjold1);
3091 self.finobjsur.set(self.finobj.get());
3092 self.last_sweep_stats.set(stats);
3093 }
3094
3095 fn finish_cycle(&self) {
3096 let stats = self
3097 .marker
3098 .borrow()
3099 .as_ref()
3100 .map(|marker| marker.stats())
3101 .unwrap_or_default();
3102 self.last_mark_stats.set(stats);
3103 self.recycle_marker_cell();
3104 self.sweep_prev_next.set(None);
3105 let next = self.bytes.get().saturating_mul(self.pause_multiplier.get()) / 100;
3106 self.threshold.set(next.max(GC_MIN_THRESHOLD));
3107 self.collections.set(self.collections.get() + 1);
3108 }
3109
3110 pub fn finish_callfin_phase(&self) -> bool {
3113 if self.state.get() != GcState::CallFin {
3114 return false;
3115 }
3116 self.finish_cycle();
3117 self.state.set(GcState::Pause);
3118 true
3119 }
3120
3121 fn abort_cycle(&self) {
3122 if !self.state.get().is_pause() {
3123 self.recycle_marker_cell();
3124 self.sweep_prev_next.set(None);
3125 let current_white = self.current_white();
3126 self.for_each_header(|header| {
3127 header.color.set(current_white);
3128 });
3129 self.state.set(GcState::Pause);
3130 }
3131 }
3132
3133 pub fn gc_state(&self) -> GcState {
3135 self.state.get()
3136 }
3137
3138 pub fn allgc_count(&self) -> usize {
3140 self.objects.get()
3141 }
3142
3143 pub fn type_name_count(&self, mut predicate: impl FnMut(&'static str) -> bool) -> usize {
3147 let mut count = 0usize;
3148 for head in [self.head.get(), self.finobj.get(), self.tobefnz.get()] {
3149 let mut cursor = head;
3150 while let Some(ptr) = cursor {
3151 let bx = unsafe { ptr.as_ref() };
3152 cursor = bx.header.next.get();
3153 if predicate(bx.value().type_name()) {
3154 count += 1;
3155 }
3156 }
3157 }
3158 count
3159 }
3160
3161 pub fn drop_all(&self) {
3221 struct TearingDownReset<'a> {
3226 flag: &'a Cell<bool>,
3227 armed: bool,
3228 }
3229 impl Drop for TearingDownReset<'_> {
3230 fn drop(&mut self) {
3231 if self.armed {
3232 self.flag.set(false);
3233 }
3234 }
3235 }
3236
3237 self.closed.set(true);
3238 let _reset = TearingDownReset {
3239 armed: !self.tearing_down.replace(true),
3240 flag: &self.tearing_down,
3241 };
3242 self.recycle_marker_cell();
3243 self.sweep_prev_next.set(None);
3244 self.clear_generation_cursors();
3245 self.state.set(GcState::Pause);
3246 self.allocation_tokens.borrow_mut().clear();
3247
3248 let mut passes = 0usize;
3249 loop {
3250 let mut freed_any = false;
3251 freed_any |= self.drop_list(&self.head);
3252 freed_any |= self.drop_list(&self.finobj);
3253 freed_any |= self.drop_list(&self.tobefnz);
3254 freed_any |= self.drop_list(&self.quarantined);
3255 freed_any |= self.drop_list(&self.uncollected);
3256 if !freed_any {
3257 break;
3258 }
3259 passes = passes.saturating_add(1);
3260 if passes >= 10_000 {
3261 panic!(
3262 "Heap::drop_all did not converge after 10000 passes — a \
3263 nonconvergent destructor is allocating a new GC box on \
3264 every teardown pass"
3265 );
3266 }
3267 }
3268
3269 self.allocation_tokens.borrow_mut().clear();
3270 self.bytes.set(0);
3271 self.objects.set(0);
3272 }
3273
3274 fn drop_list(&self, list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>) -> bool {
3280 let mut cursor = list.get();
3281 list.set(None);
3282 let mut freed = false;
3283 while let Some(ptr) = cursor {
3284 freed = true;
3285 let next = unsafe {
3287 let next = (*ptr.as_ptr()).header.next.get();
3288 let _ = Box::from_raw(ptr.as_ptr());
3289 next
3290 };
3291 cursor = next;
3292 }
3293 freed
3294 }
3295}
3296
3297impl Drop for Heap {
3308 fn drop(&mut self) {
3309 self.drop_all();
3310 }
3311}
3312
3313#[cfg(test)]
3318mod tests {
3319 use super::*;
3320
3321 struct Cell0 {
3323 next: Cell<Option<Gc<Cell0>>>,
3324 marker_calls: Cell<usize>,
3325 }
3326
3327 impl Trace for Cell0 {
3328 fn trace(&self, m: &mut Marker) {
3329 self.marker_calls.set(self.marker_calls.get() + 1);
3330 if let Some(n) = self.next.get() {
3331 m.mark(n);
3332 }
3333 }
3334 }
3335
3336 struct OneRoot(Option<Gc<Cell0>>);
3338 impl Trace for OneRoot {
3339 fn trace(&self, m: &mut Marker) {
3340 if let Some(g) = self.0 {
3341 m.mark(g);
3342 }
3343 }
3344 }
3345
3346 struct TwoRoots {
3347 first: Option<Gc<Cell0>>,
3348 second: Option<Gc<Cell0>>,
3349 }
3350
3351 impl Trace for TwoRoots {
3352 fn trace(&self, m: &mut Marker) {
3353 if let Some(g) = self.first {
3354 m.mark(g);
3355 }
3356 if let Some(g) = self.second {
3357 m.mark(g);
3358 }
3359 }
3360 }
3361
3362 #[derive(Clone)]
3363 struct FinalizerCell {
3364 id: usize,
3365 age: GcAge,
3366 finalized: std::rc::Rc<Cell<bool>>,
3367 }
3368
3369 impl FinalizerCell {
3370 fn new(id: usize) -> Self {
3371 Self {
3372 id,
3373 age: GcAge::New,
3374 finalized: std::rc::Rc::new(Cell::new(false)),
3375 }
3376 }
3377 }
3378
3379 impl FinalizerEntry for FinalizerCell {
3380 fn identity(&self) -> usize {
3381 self.id
3382 }
3383
3384 fn age(&self) -> GcAge {
3385 self.age
3386 }
3387
3388 fn is_finalized(&self) -> bool {
3389 self.finalized.get()
3390 }
3391
3392 fn set_finalized(&self, finalized: bool) {
3393 self.finalized.set(finalized);
3394 }
3395 }
3396
3397 fn finalizer_ids(objects: &[FinalizerCell]) -> Vec<usize> {
3398 objects.iter().map(|object| object.id).collect()
3399 }
3400
3401 #[derive(Clone)]
3402 struct WeakCell {
3403 id: usize,
3404 live: bool,
3405 kind: WeakListKind,
3406 }
3407
3408 impl WeakEntry for WeakCell {
3409 type Strong = usize;
3410
3411 fn identity(&self) -> usize {
3412 self.id
3413 }
3414
3415 fn list_kind(&self) -> WeakListKind {
3416 self.kind
3417 }
3418
3419 fn upgrade(&self) -> Option<Self::Strong> {
3420 self.live.then_some(self.id)
3421 }
3422 }
3423
3424 #[test]
3425 fn weak_registry_dedups_snapshots_and_retains_live_ids() {
3426 let mut registry = WeakRegistry::default();
3427 registry.push_unique(WeakCell {
3428 id: 1,
3429 live: true,
3430 kind: WeakListKind::WeakValues,
3431 });
3432 registry.push_unique(WeakCell {
3433 id: 1,
3434 live: true,
3435 kind: WeakListKind::Ephemeron,
3436 });
3437 registry.push_unique(WeakCell {
3438 id: 2,
3439 live: false,
3440 kind: WeakListKind::AllWeak,
3441 });
3442 registry.push_unique(WeakCell {
3443 id: 3,
3444 live: true,
3445 kind: WeakListKind::WeakValues,
3446 });
3447
3448 let stats = registry.stats();
3449 assert_eq!(stats.weak_values, 1);
3450 assert_eq!(stats.ephemeron, 1);
3451 assert_eq!(stats.all_weak, 1);
3452
3453 let snapshot = registry.live_snapshot();
3454 assert_eq!(snapshot, vec![3, 1]);
3455 assert_eq!(
3456 registry.stats(),
3457 WeakRegistryStats {
3458 tracked: 3,
3459 snapshot_live: 2,
3460 snapshot_dead: 1,
3461 retained: 2,
3462 weak_values: 1,
3463 ephemeron: 1,
3464 all_weak: 0,
3465 }
3466 );
3467
3468 let keep: std::collections::HashSet<usize> = [3usize].into_iter().collect();
3469 registry.retain_identities(&keep);
3470 assert_eq!(registry.len(), 1);
3471 assert_eq!(registry.stats().retained, 1);
3472 assert_eq!(registry.stats().weak_values, 1);
3473 registry.remove_identity(3);
3474 assert_eq!(registry.len(), 0);
3475 }
3476
3477 #[test]
3478 fn finalizer_registry_tracks_generational_cohorts() {
3479 let mut registry = FinalizerRegistry::default();
3480 registry.push_pending_unique(FinalizerCell::new(1));
3481 registry.push_pending_unique(FinalizerCell::new(2));
3482
3483 let stats = registry.stats();
3484 assert_eq!(stats.finobj_new, 2);
3485 assert_eq!(stats.finobj_survival, 0);
3486 assert_eq!(stats.finobj_old1, 0);
3487 assert_eq!(stats.finobj_reallyold, 0);
3488 assert_eq!(stats.finobj_minor_scan, 2);
3489
3490 registry.finish_minor_collection();
3491 let stats = registry.stats();
3492 assert_eq!(stats.finobj_new, 0);
3493 assert_eq!(stats.finobj_survival, 2);
3494 assert_eq!(stats.finobj_old1, 0);
3495 assert_eq!(stats.finobj_reallyold, 0);
3496 assert_eq!(stats.finobj_minor_scan, 2);
3497
3498 registry.push_pending_unique(FinalizerCell::new(3));
3499 registry.finish_minor_collection();
3500 let stats = registry.stats();
3501 assert_eq!(stats.finobj_new, 0);
3502 assert_eq!(stats.finobj_survival, 1);
3503 assert_eq!(stats.finobj_old1, 2);
3504 assert_eq!(stats.finobj_reallyold, 0);
3505 assert_eq!(stats.finobj_minor_scan, 1);
3506
3507 registry.finish_minor_collection();
3508 let stats = registry.stats();
3509 assert_eq!(stats.finobj_new, 0);
3510 assert_eq!(stats.finobj_survival, 0);
3511 assert_eq!(stats.finobj_old1, 1);
3512 assert_eq!(stats.finobj_reallyold, 2);
3513 assert_eq!(stats.finobj_minor_scan, 0);
3514 }
3515
3516 #[test]
3517 fn finalizer_registry_minor_snapshot_uses_cohort_boundaries() {
3518 let mut registry = FinalizerRegistry::default();
3519 registry.push_pending_unique(FinalizerCell::new(1));
3520 registry.push_pending_unique(FinalizerCell::new(2));
3521 registry.push_pending_unique(FinalizerCell::new(3));
3522 registry.finish_minor_collection();
3523 registry.finish_minor_collection();
3524 registry.push_pending_unique(FinalizerCell::new(4));
3525 registry.push_pending_unique(FinalizerCell::new(5));
3526
3527 assert_eq!(
3528 finalizer_ids(®istry.pending_minor_snapshot()),
3529 vec![4, 5],
3530 "minor finalizer scan must skip the old1/reallyold prefix"
3531 );
3532
3533 registry.push_to_be_finalized(FinalizerCell::new(99));
3534 registry.promote_pending_to_finalized(vec![
3535 FinalizerCell::new(1),
3536 FinalizerCell::new(2),
3537 FinalizerCell::new(4),
3538 ]);
3539
3540 let stats = registry.stats();
3541 assert_eq!(stats.finobj_old1, 1);
3542 assert_eq!(stats.finobj_new, 1);
3543 assert_eq!(stats.finobj_minor_scan, 1);
3544 assert_eq!(finalizer_ids(registry.pending()), vec![3, 5]);
3545 assert_eq!(
3546 finalizer_ids(registry.to_be_finalized()),
3547 vec![99, 4, 2, 1],
3548 "new to-be-finalized batches append behind older queued finalizers"
3549 );
3550 }
3551
3552 #[test]
3553 fn finalizer_registry_marks_and_clears_finalized_bit() {
3554 let mut registry = FinalizerRegistry::default();
3555 let object = FinalizerCell::new(1);
3556
3557 assert!(!object.is_finalized());
3558 registry.push_pending_unique(object.clone());
3559 assert!(object.is_finalized());
3560
3561 registry.push_pending_unique(object.clone());
3562 assert_eq!(registry.pending_len(), 1);
3563
3564 registry.promote_pending_to_finalized(vec![object.clone()]);
3565 assert!(object.is_finalized());
3566 assert_eq!(registry.pending_len(), 0);
3567 assert_eq!(registry.to_be_finalized_len(), 1);
3568
3569 let popped = registry.pop_to_be_finalized().unwrap();
3570 assert_eq!(popped.id, 1);
3571 assert!(!object.is_finalized());
3572
3573 registry.push_pending_unique(object.clone());
3574 assert!(object.is_finalized());
3575 assert_eq!(registry.pending_len(), 1);
3576 }
3577
3578 #[test]
3579 fn alloc_and_drop_all() {
3580 let heap = Heap::new();
3581 heap.unpause();
3582 let _a = heap.allocate(Cell0 {
3583 next: Cell::new(None),
3584 marker_calls: Cell::new(0),
3585 });
3586 let _b = heap.allocate(Cell0 {
3587 next: Cell::new(None),
3588 marker_calls: Cell::new(0),
3589 });
3590 assert!(heap.bytes_used() > 0);
3591 heap.drop_all();
3592 assert_eq!(heap.bytes_used(), 0);
3593 }
3594
3595 fn list_len(heap: &Heap, mut cursor: Option<NonNull<GcBox<dyn Trace>>>) -> usize {
3596 let mut count = 0usize;
3597 while let Some(ptr) = cursor {
3598 count += 1;
3599 cursor = heap.header_from_ptr(ptr).next.get();
3600 }
3601 count
3602 }
3603
3604 #[test]
3605 fn finalizer_intrusive_lists_sweep_and_drop() {
3606 let heap = Heap::new();
3607 heap.unpause();
3608 let _normal = heap.allocate(Cell0 {
3609 next: Cell::new(None),
3610 marker_calls: Cell::new(0),
3611 });
3612 let finobj = heap.allocate(Cell0 {
3613 next: Cell::new(None),
3614 marker_calls: Cell::new(0),
3615 });
3616 let tobefnz = heap.allocate(Cell0 {
3617 next: Cell::new(None),
3618 marker_calls: Cell::new(0),
3619 });
3620
3621 assert!(heap.move_allgc_to_finobj(finobj.as_trace_ptr()));
3622 assert!(heap.move_allgc_to_finobj(tobefnz.as_trace_ptr()));
3623 assert!(heap.move_finobj_to_tobefnz(tobefnz.as_trace_ptr()));
3624 assert_eq!(list_len(&heap, heap.head.get()), 1);
3625 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3626 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3627 assert_eq!(heap.allgc_count(), 3);
3628
3629 heap.full_collect(&TwoRoots {
3630 first: Some(finobj),
3631 second: Some(tobefnz),
3632 });
3633 assert_eq!(list_len(&heap, heap.head.get()), 0);
3634 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3635 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3636 assert_eq!(heap.allgc_count(), 2);
3637
3638 assert!(heap.move_tobefnz_to_allgc(tobefnz.as_trace_ptr()));
3639 heap.full_collect(&OneRoot(Some(tobefnz)));
3640 assert_eq!(list_len(&heap, heap.head.get()), 1);
3641 assert_eq!(list_len(&heap, heap.finobj.get()), 0);
3642 assert_eq!(list_len(&heap, heap.tobefnz.get()), 0);
3643 assert_eq!(heap.allgc_count(), 1);
3644
3645 heap.drop_all();
3646 assert_eq!(heap.bytes_used(), 0);
3647 }
3648
3649 #[test]
3650 fn account_buffer_refunded_on_sweep() {
3651 let heap = Heap::new();
3652 heap.unpause();
3653 let baseline = heap.bytes_used();
3654 {
3655 let a = heap.allocate(Cell0 {
3656 next: Cell::new(None),
3657 marker_calls: Cell::new(0),
3658 });
3659 assert!(heap.bytes_used() > baseline);
3660 a.account_buffer(&heap, 4096);
3661 assert_eq!(
3662 a.header().size(),
3663 std::mem::size_of::<GcBox<Cell0>>() + 4096
3664 );
3665 }
3666 heap.full_collect(&OneRoot(None));
3669 assert_eq!(
3670 heap.bytes_used(),
3671 baseline,
3672 "account_buffer charge must be fully refunded by sweep"
3673 );
3674 }
3675
3676 #[test]
3677 fn account_buffer_noop_on_uncollected() {
3678 let heap = Heap::new();
3679 heap.unpause();
3680 let g = Gc::new_uncollected(Cell0 {
3681 next: Cell::new(None),
3682 marker_calls: Cell::new(0),
3683 });
3684 let before = heap.bytes_used();
3685 g.account_buffer(&heap, 8192);
3686 assert_eq!(
3687 heap.bytes_used(),
3688 before,
3689 "uncollected box must not charge the pacer"
3690 );
3691 }
3692
3693 #[test]
3694 fn collect_unreachable_frees_bytes() {
3695 let heap = Heap::new();
3696 heap.unpause();
3697 let _a = heap.allocate(Cell0 {
3698 next: Cell::new(None),
3699 marker_calls: Cell::new(0),
3700 });
3701 let bytes_before = heap.bytes_used();
3702 assert!(bytes_before > 0);
3703 heap.full_collect(&OneRoot(None));
3705 assert_eq!(heap.bytes_used(), 0);
3706 assert_eq!(heap.collections(), 1);
3707 }
3708
3709 #[test]
3710 fn collect_keeps_reachable() {
3711 let heap = Heap::new();
3712 heap.unpause();
3713 let root = heap.allocate(Cell0 {
3714 next: Cell::new(None),
3715 marker_calls: Cell::new(0),
3716 });
3717 let bytes_before = heap.bytes_used();
3718 heap.full_collect(&OneRoot(Some(root)));
3719 assert_eq!(heap.bytes_used(), bytes_before);
3720 assert_eq!(root.marker_calls.get(), 1);
3721 }
3722
3723 #[test]
3724 fn allocations_start_new_and_white() {
3725 let heap = Heap::new();
3726 heap.unpause();
3727 let obj = heap.allocate(Cell0 {
3728 next: Cell::new(None),
3729 marker_calls: Cell::new(0),
3730 });
3731 assert_eq!(obj.age(), GcAge::New);
3732 assert!(obj.color().is_white());
3733 }
3734
3735 #[test]
3736 fn allocation_tokens_track_exact_live_box() {
3737 let heap = Heap::new();
3738 heap.unpause();
3739 let obj = heap.allocate(Cell0 {
3740 next: Cell::new(None),
3741 marker_calls: Cell::new(0),
3742 });
3743 let id = obj.identity();
3744
3745 assert_eq!(
3746 heap.allocation_token(id),
3747 None,
3748 "lazy registration: a freshly allocated box has no token until it is downgraded"
3749 );
3750
3751 let token = heap.register_allocation_token(id);
3752 assert_eq!(
3753 heap.register_allocation_token(id),
3754 token,
3755 "registration is get-or-insert: re-registering a live identity returns the same token"
3756 );
3757 assert_eq!(heap.allocation_token(id), Some(token));
3758 assert!(heap.contains_allocation(id, token));
3759 assert!(!heap.contains_allocation(id, token + 1));
3760
3761 heap.full_collect(&OneRoot(None));
3762 assert_eq!(heap.allocation_token(id), None);
3763 assert!(!heap.contains_allocation(id, token));
3764 }
3765
3766 #[test]
3767 fn registering_after_sweep_yields_a_distinct_token_on_address_reuse() {
3768 let heap = Heap::new();
3769 heap.unpause();
3770 let first = heap.allocate(Cell0 {
3771 next: Cell::new(None),
3772 marker_calls: Cell::new(0),
3773 });
3774 let id = first.identity();
3775 let first_token = heap.register_allocation_token(id);
3776
3777 heap.full_collect(&OneRoot(None));
3778 assert_eq!(
3779 heap.allocation_token(id),
3780 None,
3781 "sweep removes the token for the dead identity"
3782 );
3783
3784 let second_token = heap.register_allocation_token(id);
3785 assert_ne!(
3786 first_token, second_token,
3787 "monotonic tokens keep address reuse from resurrecting a stale weak handle"
3788 );
3789 assert!(!heap.contains_allocation(id, first_token));
3790 assert!(heap.contains_allocation(id, second_token));
3791 }
3792
3793 #[test]
3794 fn allocation_during_incremental_sweep_survives_current_cycle() {
3795 let heap = Heap::new();
3796 heap.unpause();
3797 let old_dead = heap.allocate(Cell0 {
3798 next: Cell::new(None),
3799 marker_calls: Cell::new(0),
3800 });
3801 let old_id = old_dead.identity();
3802 heap.register_allocation_token(old_id);
3803
3804 let outcome = heap.incremental_run_until_state_with_post_mark(
3805 &OneRoot(None),
3806 GcState::SweepAllGc,
3807 1024,
3808 |_| {},
3809 );
3810 assert_eq!(outcome, StepOutcome::InProgress);
3811 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3812
3813 let new_during_sweep = heap.allocate(Cell0 {
3814 next: Cell::new(None),
3815 marker_calls: Cell::new(0),
3816 });
3817 let new_id = new_during_sweep.identity();
3818 heap.register_allocation_token(new_id);
3819
3820 loop {
3821 let outcome = heap.incremental_step_with_post_mark(
3822 &OneRoot(None),
3823 StepBudget::from_work(64),
3824 |_| {},
3825 );
3826 if matches!(outcome, StepOutcome::Paused) {
3827 break;
3828 }
3829 }
3830
3831 assert_eq!(heap.allocation_token(old_id), None);
3832 assert!(heap.allocation_token(new_id).is_some());
3833 assert_eq!(heap.allgc_count(), 1);
3834
3835 heap.full_collect(&OneRoot(None));
3836 assert_eq!(heap.allocation_token(new_id), None);
3837 assert_eq!(heap.allgc_count(), 0);
3838 }
3839
3840 #[test]
3841 fn promote_and_reset_all_ages() {
3842 let heap = Heap::new();
3843 heap.unpause();
3844 let a = heap.allocate(Cell0 {
3845 next: Cell::new(None),
3846 marker_calls: Cell::new(0),
3847 });
3848 let b = heap.allocate(Cell0 {
3849 next: Cell::new(None),
3850 marker_calls: Cell::new(0),
3851 });
3852
3853 heap.promote_all_to_old();
3854 assert_eq!(a.age(), GcAge::Old);
3855 assert_eq!(b.age(), GcAge::Old);
3856 assert_eq!(a.color(), Color::Black);
3857 assert_eq!(b.color(), Color::Black);
3858
3859 heap.reset_all_ages();
3860 assert_eq!(a.age(), GcAge::New);
3861 assert_eq!(b.age(), GcAge::New);
3862 assert!(a.color().is_white());
3863 assert!(b.color().is_white());
3864 }
3865
3866 #[test]
3867 fn generational_barriers_update_ages() {
3868 let heap = Heap::new();
3869 heap.unpause();
3870 let parent = heap.allocate(Cell0 {
3871 next: Cell::new(None),
3872 marker_calls: Cell::new(0),
3873 });
3874 let child = heap.allocate(Cell0 {
3875 next: Cell::new(None),
3876 marker_calls: Cell::new(0),
3877 });
3878
3879 parent.set_age(GcAge::Old);
3880 parent.set_color(Color::Black);
3881 heap.generational_backward_barrier(parent);
3882 assert_eq!(parent.age(), GcAge::Touched1);
3883 assert_eq!(parent.color(), Color::Gray);
3884
3885 heap.generational_forward_barrier(parent, child);
3886 assert_eq!(child.age(), GcAge::Old0);
3887 }
3888
3889 #[test]
3890 fn minor_collect_frees_young_and_keeps_old() {
3891 let heap = Heap::new();
3892 heap.unpause();
3893 let old_unreachable = heap.allocate(Cell0 {
3894 next: Cell::new(None),
3895 marker_calls: Cell::new(0),
3896 });
3897 old_unreachable.set_age(GcAge::Old);
3898 old_unreachable.set_color(Color::Black);
3899 let _young_unreachable = heap.allocate(Cell0 {
3900 next: Cell::new(None),
3901 marker_calls: Cell::new(0),
3902 });
3903 let young_survivor = heap.allocate(Cell0 {
3904 next: Cell::new(None),
3905 marker_calls: Cell::new(0),
3906 });
3907
3908 heap.minor_collect_with_post_mark(&OneRoot(Some(young_survivor)), |_| {});
3909
3910 assert_eq!(heap.allgc_count(), 2);
3911 assert_eq!(old_unreachable.age(), GcAge::Old);
3912 assert_eq!(young_survivor.age(), GcAge::Survival);
3913 assert!(young_survivor.color().is_white());
3914 }
3915
3916 #[test]
3917 fn minor_collect_skips_untouched_old_root_scan_work() {
3918 let heap = Heap::new();
3919 heap.unpause();
3920 let old_root = heap.allocate(Cell0 {
3921 next: Cell::new(None),
3922 marker_calls: Cell::new(0),
3923 });
3924 old_root.set_age(GcAge::Old);
3925 old_root.set_color(Color::Black);
3926 let young_root = heap.allocate(Cell0 {
3927 next: Cell::new(None),
3928 marker_calls: Cell::new(0),
3929 });
3930
3931 heap.minor_collect_with_post_mark(
3932 &TwoRoots {
3933 first: Some(old_root),
3934 second: Some(young_root),
3935 },
3936 |_| {},
3937 );
3938
3939 let stats = heap.last_mark_stats();
3940 assert_eq!(stats.marked, 2);
3941 assert_eq!(stats.marked_old, 1);
3942 assert_eq!(stats.marked_young, 1);
3943 assert_eq!(stats.traced, 1);
3944 assert_eq!(stats.traced_old, 0);
3945 assert_eq!(stats.traced_young, 1);
3946 assert_eq!(old_root.marker_calls.get(), 0);
3947 assert_eq!(young_root.marker_calls.get(), 1);
3948 assert_eq!(old_root.age(), GcAge::Old);
3949 assert_eq!(young_root.age(), GcAge::Survival);
3950 }
3951
3952 #[test]
3953 fn minor_collect_traces_touched_old_parent() {
3954 let heap = Heap::new();
3955 heap.unpause();
3956 let old_root = heap.allocate(Cell0 {
3957 next: Cell::new(None),
3958 marker_calls: Cell::new(0),
3959 });
3960 old_root.set_age(GcAge::Old);
3961 old_root.set_color(Color::Black);
3962 let young_child = heap.allocate(Cell0 {
3963 next: Cell::new(None),
3964 marker_calls: Cell::new(0),
3965 });
3966 old_root.next.set(Some(young_child));
3967 heap.generational_backward_barrier(old_root);
3968
3969 heap.minor_collect_with_post_mark(&OneRoot(Some(old_root)), |_| {});
3970
3971 let stats = heap.last_mark_stats();
3972 assert_eq!(stats.marked, 2);
3973 assert_eq!(stats.marked_old, 1);
3974 assert_eq!(stats.marked_young, 1);
3975 assert_eq!(stats.traced, 2);
3976 assert_eq!(stats.traced_old, 1);
3977 assert_eq!(stats.traced_young, 1);
3978 assert_eq!(old_root.marker_calls.get(), 1);
3979 assert_eq!(young_child.marker_calls.get(), 1);
3980 assert_eq!(old_root.age(), GcAge::Touched2);
3981 assert_eq!(young_child.age(), GcAge::Survival);
3982 }
3983
3984 #[test]
3985 fn minor_sweep_uses_generation_cursors_to_skip_old_tail() {
3986 let heap = Heap::new();
3987 heap.unpause();
3988 let mut old_objects = Vec::new();
3989 for _ in 0..64 {
3990 old_objects.push(heap.allocate(Cell0 {
3991 next: Cell::new(None),
3992 marker_calls: Cell::new(0),
3993 }));
3994 }
3995 heap.promote_all_to_old();
3996 let young_root = heap.allocate(Cell0 {
3997 next: Cell::new(None),
3998 marker_calls: Cell::new(0),
3999 });
4000
4001 heap.minor_collect_with_post_mark(&OneRoot(Some(young_root)), |_| {});
4002
4003 let stats = heap.last_sweep_stats();
4004 assert_eq!(stats.visited, 1);
4005 assert_eq!(stats.visited_young, 1);
4006 assert_eq!(stats.visited_old, 0);
4007 assert_eq!(heap.allgc_count(), old_objects.len() + 1);
4008 assert_eq!(young_root.age(), GcAge::Survival);
4009 }
4010
4011 #[test]
4012 fn full_sweep_corrects_generation_cursors_when_cursor_object_is_freed() {
4013 let heap = Heap::new();
4014 heap.unpause();
4015 let _old = heap.allocate(Cell0 {
4016 next: Cell::new(None),
4017 marker_calls: Cell::new(0),
4018 });
4019 heap.promote_all_to_old();
4020 assert!(heap.survival.get().is_some());
4021 assert!(heap.old1.get().is_some());
4022 assert!(heap.reallyold.get().is_some());
4023
4024 heap.full_collect(&OneRoot(None));
4025
4026 assert_eq!(heap.allgc_count(), 0);
4027 assert_eq!(heap.survival.get(), None);
4028 assert_eq!(heap.old1.get(), None);
4029 assert_eq!(heap.reallyold.get(), None);
4030 assert_eq!(heap.firstold1.get(), None);
4031 assert_eq!(heap.last_sweep_stats().freed, 1);
4032 }
4033
4034 #[test]
4044 fn cross_list_move_preserves_grayagain_entry() {
4045 let heap = Heap::new();
4046 heap.unpause();
4047 let obj = heap.allocate(Cell0 {
4048 next: Cell::new(None),
4049 marker_calls: Cell::new(0),
4050 });
4051 obj.set_age(GcAge::Old1);
4052 obj.set_color(Color::Gray);
4053 heap.remember_minor_revisit(obj.as_trace_ptr());
4054 assert_eq!(heap.grayagain_count(), 1);
4055 assert!(heap.header_from_ptr(obj.as_trace_ptr()).gray_listed());
4056 assert!(heap.move_allgc_to_finobj(obj.as_trace_ptr()));
4057 assert_eq!(
4058 heap.grayagain_count(),
4059 1,
4060 "── issue #263: allgc->finobj move preserves the grayagain entry"
4061 );
4062 assert!(heap.header_from_ptr(obj.as_trace_ptr()).gray_listed());
4063
4064 let heap = Heap::new();
4065 heap.unpause();
4066 let obj = heap.allocate(Cell0 {
4067 next: Cell::new(None),
4068 marker_calls: Cell::new(0),
4069 });
4070 assert!(heap.move_allgc_to_finobj(obj.as_trace_ptr()));
4071 assert!(heap.move_finobj_to_tobefnz(obj.as_trace_ptr()));
4072 obj.set_age(GcAge::Old1);
4073 obj.set_color(Color::Gray);
4074 heap.remember_minor_revisit(obj.as_trace_ptr());
4075 assert_eq!(heap.grayagain_count(), 1);
4076 assert!(heap.move_tobefnz_to_allgc(obj.as_trace_ptr()));
4077 assert_eq!(
4078 heap.grayagain_count(),
4079 1,
4080 "── issue #263: tobefnz->allgc move preserves the grayagain entry"
4081 );
4082 assert!(heap.header_from_ptr(obj.as_trace_ptr()).gray_listed());
4083 }
4084
4085 #[test]
4107 fn g3_moved_transitional_keeps_young_child_reachable() {
4108 let heap = Heap::new();
4109 heap.unpause();
4110 let child = heap.allocate(Cell0 {
4111 next: Cell::new(None),
4112 marker_calls: Cell::new(0),
4113 });
4114 let transitional = heap.allocate(Cell0 {
4115 next: Cell::new(Some(child)),
4116 marker_calls: Cell::new(0),
4117 });
4118 let parent = heap.allocate(Cell0 {
4119 next: Cell::new(Some(transitional)),
4120 marker_calls: Cell::new(0),
4121 });
4122 parent.set_age(GcAge::Old);
4123 parent.set_color(Color::Black);
4124 transitional.set_age(GcAge::Old1);
4125 transitional.set_color(Color::Gray);
4126 heap.remember_minor_revisit(transitional.as_trace_ptr());
4127 assert_eq!(heap.grayagain_count(), 1);
4128 assert_eq!(heap.allgc_count(), 3);
4129
4130 assert!(heap.move_allgc_to_finobj(transitional.as_trace_ptr()));
4131 assert_eq!(
4132 heap.grayagain_count(),
4133 1,
4134 "── issue #263: cross-list move preserves the grayagain entry"
4135 );
4136
4137 heap.minor_collect_with_post_mark(&OneRoot(Some(parent)), |_| {});
4138
4139 assert_eq!(
4140 heap.allgc_count(),
4141 3,
4142 "reachable young child survives the minor because the moved \
4143 transitional kept its grayagain revisit entry"
4144 );
4145 }
4146
4147 #[test]
4171 fn g3_transitional_remaining_on_tobefnz_reconciles_across_minors() {
4172 let heap = Heap::new();
4173 heap.unpause();
4174 let child = heap.allocate(Cell0 {
4175 next: Cell::new(None),
4176 marker_calls: Cell::new(0),
4177 });
4178 let transitional = heap.allocate(Cell0 {
4179 next: Cell::new(Some(child)),
4180 marker_calls: Cell::new(0),
4181 });
4182 let parent = heap.allocate(Cell0 {
4183 next: Cell::new(Some(transitional)),
4184 marker_calls: Cell::new(0),
4185 });
4186 parent.set_age(GcAge::Old);
4187 parent.set_color(Color::Black);
4188 transitional.set_age(GcAge::Touched1);
4189 transitional.set_color(Color::Black);
4190
4191 assert!(heap.move_allgc_to_finobj(transitional.as_trace_ptr()));
4192 heap.remember_minor_revisit(transitional.as_trace_ptr());
4193 assert_eq!(heap.grayagain_count(), 1);
4194
4195 assert!(heap.move_finobj_to_tobefnz(transitional.as_trace_ptr()));
4196 assert_eq!(
4197 heap.grayagain_count(),
4198 1,
4199 "── issue #263: finobj->tobefnz move preserves the grayagain entry"
4200 );
4201 assert!(heap
4202 .header_from_ptr(transitional.as_trace_ptr())
4203 .gray_listed());
4204
4205 heap.minor_collect_with_post_mark(&OneRoot(Some(parent)), |_| {});
4206 assert_eq!(child.age(), GcAge::Survival, "child New -> Survival");
4207 assert_eq!(
4208 transitional.age(),
4209 GcAge::Touched2,
4210 "transitional Touched1 -> Touched2 while on tobefnz"
4211 );
4212 assert_eq!(heap.grayagain_count(), 1, "still tracked");
4213 assert_eq!(heap.allgc_count(), 3, "child reachable after minor 1");
4214
4215 heap.minor_collect_with_post_mark(&OneRoot(Some(parent)), |_| {});
4216 assert_eq!(child.age(), GcAge::Old1, "child Survival -> Old1");
4217 assert_eq!(
4218 transitional.age(),
4219 GcAge::Old,
4220 "transitional Touched2 -> Old while on tobefnz"
4221 );
4222 assert_eq!(
4223 heap.grayagain_count(),
4224 1,
4225 "transitional retires at Old, but the child it kept young is now \
4226 itself an Old1 tracked survivor — the obligation follows the \
4227 live subgraph, not just the finalizable object"
4228 );
4229 assert_eq!(heap.allgc_count(), 3, "child reachable after minor 2");
4230
4231 heap.minor_collect_with_post_mark(&OneRoot(Some(parent)), |_| {});
4232 assert_eq!(child.age(), GcAge::Old, "child Old1 -> Old");
4233 assert_eq!(
4234 heap.grayagain_count(),
4235 0,
4236 "once the whole subgraph reaches Old the revisit obligation is \
4237 fully retired (C correctgraylist)"
4238 );
4239 assert_eq!(heap.allgc_count(), 3, "child reachable throughout — the #263 guarantee");
4240 }
4241
4242 #[test]
4243 fn g3_moved_transitional_via_tobefnz_keeps_young_child_reachable() {
4244 let heap = Heap::new();
4245 heap.unpause();
4246 let child = heap.allocate(Cell0 {
4247 next: Cell::new(None),
4248 marker_calls: Cell::new(0),
4249 });
4250 let transitional = heap.allocate(Cell0 {
4251 next: Cell::new(Some(child)),
4252 marker_calls: Cell::new(0),
4253 });
4254 let parent = heap.allocate(Cell0 {
4255 next: Cell::new(Some(transitional)),
4256 marker_calls: Cell::new(0),
4257 });
4258 parent.set_age(GcAge::Old);
4259 parent.set_color(Color::Black);
4260 transitional.set_age(GcAge::Old1);
4261 transitional.set_color(Color::Gray);
4262
4263 assert!(heap.move_allgc_to_finobj(transitional.as_trace_ptr()));
4264 assert!(heap.move_finobj_to_tobefnz(transitional.as_trace_ptr()));
4265 heap.remember_minor_revisit(transitional.as_trace_ptr());
4266 assert_eq!(heap.grayagain_count(), 1);
4267
4268 assert!(heap.move_tobefnz_to_allgc(transitional.as_trace_ptr()));
4269 assert_eq!(
4270 heap.grayagain_count(),
4271 1,
4272 "── issue #263: tobefnz->allgc move preserves the grayagain entry"
4273 );
4274
4275 heap.minor_collect_with_post_mark(&OneRoot(Some(parent)), |_| {});
4276
4277 assert_eq!(
4278 heap.allgc_count(),
4279 3,
4280 "reachable young child survives the minor because the tobefnz->allgc \
4281 move kept the transitional's grayagain revisit entry"
4282 );
4283 }
4284
4285 #[test]
4292 fn drop_all_clears_grayagain_before_freeing() {
4293 let heap = Heap::new();
4294 heap.unpause();
4295 let obj = heap.allocate(Cell0 {
4296 next: Cell::new(None),
4297 marker_calls: Cell::new(0),
4298 });
4299 obj.set_age(GcAge::Old);
4300 heap.generational_backward_barrier(obj);
4301 assert_eq!(heap.grayagain_count(), 1);
4302
4303 heap.drop_all();
4304
4305 assert_eq!(heap.grayagain_count(), 0);
4306 assert_eq!(heap.allgc_count(), 0);
4307 }
4308
4309 #[test]
4318 #[cfg(target_pointer_width = "64")]
4319 fn gcheader_is_24_bytes_after_grayagain_diet() {
4320 assert_eq!(std::mem::size_of::<GcHeader>(), 24);
4321 assert_eq!(std::mem::align_of::<GcHeader>(), 8);
4322 assert_eq!(
4323 std::mem::size_of::<GcBox<u64>>(),
4324 32,
4325 "a GcBox<u64> stays header(24) + value(8) with no growth"
4326 );
4327 }
4328
4329 #[test]
4330 fn full_sweep_unlinks_freed_grayagain_entries() {
4331 let heap = Heap::new();
4332 heap.unpause();
4333 let parent = heap.allocate(Cell0 {
4334 next: Cell::new(None),
4335 marker_calls: Cell::new(0),
4336 });
4337 heap.promote_all_to_old();
4338 heap.generational_backward_barrier(parent);
4339 assert_eq!(heap.grayagain_count(), 1);
4340
4341 heap.full_collect(&OneRoot(None));
4342
4343 assert_eq!(heap.allgc_count(), 0);
4344 assert_eq!(heap.grayagain_count(), 0);
4345
4346 let young = heap.allocate(Cell0 {
4347 next: Cell::new(None),
4348 marker_calls: Cell::new(0),
4349 });
4350 heap.minor_collect_with_post_mark(&OneRoot(Some(young)), |_| {});
4351 assert_eq!(young.age(), GcAge::Survival);
4352 }
4353
4354 #[test]
4355 fn grayagain_links_object_once() {
4356 let heap = Heap::new();
4357 heap.unpause();
4358 let parent = heap.allocate(Cell0 {
4359 next: Cell::new(None),
4360 marker_calls: Cell::new(0),
4361 });
4362 parent.set_age(GcAge::Old);
4363 parent.set_color(Color::Black);
4364
4365 heap.generational_backward_barrier(parent);
4366 heap.generational_backward_barrier(parent);
4367
4368 assert_eq!(heap.grayagain_count(), 1);
4369 }
4370
4371 #[test]
4372 fn grayagain_list_carries_old1_until_old() {
4373 let heap = Heap::new();
4374 heap.unpause();
4375 let survivor = heap.allocate(Cell0 {
4376 next: Cell::new(None),
4377 marker_calls: Cell::new(0),
4378 });
4379
4380 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
4381 assert_eq!(survivor.age(), GcAge::Survival);
4382
4383 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
4384 assert_eq!(survivor.age(), GcAge::Old1);
4385
4386 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
4387 assert_eq!(survivor.age(), GcAge::Old);
4388 assert_eq!(heap.allgc_count(), 1);
4389 }
4390
4391 #[test]
4392 fn grayagain_list_carries_touched2_until_old() {
4393 let heap = Heap::new();
4394 heap.unpause();
4395 let parent = heap.allocate(Cell0 {
4396 next: Cell::new(None),
4397 marker_calls: Cell::new(0),
4398 });
4399 parent.set_age(GcAge::Old);
4400 parent.set_color(Color::Black);
4401 heap.generational_backward_barrier(parent);
4402
4403 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
4404 assert_eq!(parent.age(), GcAge::Touched2);
4405
4406 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
4407 assert_eq!(parent.age(), GcAge::Old);
4408 assert_eq!(parent.marker_calls.get(), 2);
4409 }
4410
4411 #[test]
4412 fn collect_traverses_cycles() {
4413 let heap = Heap::new();
4414 heap.unpause();
4415 let a = heap.allocate(Cell0 {
4416 next: Cell::new(None),
4417 marker_calls: Cell::new(0),
4418 });
4419 let b = heap.allocate(Cell0 {
4420 next: Cell::new(Some(a)),
4421 marker_calls: Cell::new(0),
4422 });
4423 a.next.set(Some(b)); heap.full_collect(&OneRoot(Some(a)));
4426 assert_eq!(a.marker_calls.get(), 1);
4427 assert_eq!(b.marker_calls.get(), 1);
4428 heap.full_collect(&OneRoot(None));
4432 assert_eq!(heap.bytes_used(), 0);
4433 }
4434
4435 #[test]
4436 fn heap_guard_stacks() {
4437 assert!(
4438 with_current_heap(|heap| heap.is_none()),
4439 "no guard initially"
4440 );
4441 let h1 = Heap::new();
4442 h1.unpause();
4443 {
4444 let _g1 = HeapGuard::push(&h1);
4445 assert!(with_current_heap(|heap| heap.is_some()));
4446 let h2 = Heap::new();
4447 h2.unpause();
4448 {
4449 let _g2 = HeapGuard::push(&h2);
4450 with_current_heap(|heap| {
4452 assert!(std::rc::Rc::ptr_eq(heap.unwrap(), &h2));
4453 });
4454 }
4455 with_current_heap(|heap| {
4457 assert!(std::rc::Rc::ptr_eq(heap.unwrap(), &h1));
4458 });
4459 }
4460 assert!(
4461 with_current_heap(|heap| heap.is_none()),
4462 "stack empty after all guards drop"
4463 );
4464 }
4465
4466 #[test]
4467 fn step_threshold_triggers_collect() {
4468 let heap = Heap::new();
4469 heap.unpause();
4470 let mut keeps = Vec::new();
4472 for _ in 0..64 {
4473 keeps.push(heap.allocate(Cell0 {
4477 next: Cell::new(None),
4478 marker_calls: Cell::new(0),
4479 }));
4480 }
4481 struct ManyRoots<'a>(&'a [Gc<Cell0>]);
4483 impl<'a> Trace for ManyRoots<'a> {
4484 fn trace(&self, m: &mut Marker) {
4485 for g in self.0.iter() {
4486 m.mark(*g);
4487 }
4488 }
4489 }
4490 heap.step(&ManyRoots(&keeps));
4491 assert!(heap.bytes_used() > 0);
4493 }
4494
4495 #[test]
4496 fn threshold_floored_after_collecting_tiny_heap() {
4497 let heap = Heap::new();
4498 heap.unpause();
4499 struct NoRoots;
4500 impl Trace for NoRoots {
4501 fn trace(&self, _m: &mut Marker) {}
4502 }
4503 for _ in 0..200 {
4504 heap.allocate(Cell0 {
4505 next: Cell::new(None),
4506 marker_calls: Cell::new(0),
4507 });
4508 }
4509 heap.full_collect(&NoRoots);
4510 assert!(
4511 heap.threshold_bytes() >= GC_MIN_THRESHOLD,
4512 "threshold {} collapsed below floor {}; a churning program would full-collect per allocation",
4513 heap.threshold_bytes(),
4514 GC_MIN_THRESHOLD
4515 );
4516 }
4517
4518 fn build_chain(heap: &Heap, len: usize) -> Gc<Cell0> {
4519 let head = heap.allocate(Cell0 {
4520 next: Cell::new(None),
4521 marker_calls: Cell::new(0),
4522 });
4523 let mut cur = head;
4524 for _ in 1..len {
4525 let n = heap.allocate(Cell0 {
4526 next: Cell::new(None),
4527 marker_calls: Cell::new(0),
4528 });
4529 cur.next.set(Some(n));
4530 cur = n;
4531 }
4532 head
4533 }
4534
4535 #[test]
4536 fn budget_zero_does_some_work() {
4537 let heap = Heap::new();
4538 heap.unpause();
4539 let head = build_chain(&heap, 8);
4540 let roots = OneRoot(Some(head));
4541 let budget = StepBudget::from_work(0);
4542 let outcome = heap.incremental_step_with_post_mark(&roots, budget, |_| {});
4543 assert_ne!(outcome, StepOutcome::SkippedStopped);
4544 assert_ne!(heap.gc_state(), GcState::Pause);
4545 }
4546
4547 #[test]
4548 fn run_until_state_stops_before_next_phase_work() {
4549 let heap = Heap::new();
4550 heap.unpause();
4551 let head = build_chain(&heap, 8);
4552 let roots = OneRoot(Some(head));
4553 let atomic_calls = Cell::new(0);
4554
4555 let outcome =
4556 heap.incremental_run_until_state_with_post_mark(&roots, GcState::Atomic, 1024, |_| {
4557 atomic_calls.set(atomic_calls.get() + 1)
4558 });
4559 assert_eq!(outcome, StepOutcome::InProgress);
4560 assert_eq!(heap.gc_state(), GcState::Atomic);
4561 assert_eq!(
4562 atomic_calls.get(),
4563 0,
4564 "atomic hook must not run before inspection"
4565 );
4566
4567 let outcome = heap.incremental_run_until_state_with_post_mark(
4568 &roots,
4569 GcState::SweepAllGc,
4570 1024,
4571 |_| atomic_calls.set(atomic_calls.get() + 1),
4572 );
4573 assert_eq!(outcome, StepOutcome::InProgress);
4574 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
4575 assert_eq!(
4576 atomic_calls.get(),
4577 1,
4578 "entering sweep must run the atomic hook once"
4579 );
4580 }
4581
4582 #[test]
4583 fn larger_budget_drains_more_gray_than_smaller() {
4584 let small_heap = Heap::new();
4585 small_heap.unpause();
4586 let h1 = build_chain(&small_heap, 64);
4587 let r1 = OneRoot(Some(h1));
4588 let mut small_calls = 0;
4589 loop {
4590 small_calls += 1;
4591 let outcome =
4592 small_heap.incremental_step_with_post_mark(&r1, StepBudget::from_work(2), |_| {});
4593 if outcome == StepOutcome::Paused {
4594 break;
4595 }
4596 assert!(small_calls < 10_000, "did not converge");
4597 }
4598
4599 let big_heap = Heap::new();
4600 big_heap.unpause();
4601 let h2 = build_chain(&big_heap, 64);
4602 let r2 = OneRoot(Some(h2));
4603 let mut big_calls = 0;
4604 loop {
4605 big_calls += 1;
4606 let outcome =
4607 big_heap.incremental_step_with_post_mark(&r2, StepBudget::from_work(64), |_| {});
4608 if outcome == StepOutcome::Paused {
4609 break;
4610 }
4611 assert!(big_calls < 10_000, "did not converge");
4612 }
4613
4614 assert!(
4615 big_calls < small_calls,
4616 "expected big_calls={} < small_calls={}",
4617 big_calls,
4618 small_calls
4619 );
4620 }
4621
4622 #[test]
4623 fn sweep_can_pause_and_resume() {
4624 let heap = Heap::new();
4625 heap.unpause();
4626 for _ in 0..16 {
4627 let _ = heap.allocate(Cell0 {
4628 next: Cell::new(None),
4629 marker_calls: Cell::new(0),
4630 });
4631 }
4632 let roots = OneRoot(None);
4633 let bytes_before = heap.bytes_used();
4634 assert!(bytes_before > 0);
4635 let mut step_count = 0;
4636 let mut saw_in_progress_during_sweep = false;
4637 loop {
4638 step_count += 1;
4639 let outcome =
4640 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {});
4641 if heap.gc_state().is_sweep() && outcome == StepOutcome::InProgress {
4642 saw_in_progress_during_sweep = true;
4643 }
4644 if outcome == StepOutcome::Paused {
4645 break;
4646 }
4647 assert!(step_count < 10_000, "did not converge");
4648 }
4649 assert!(saw_in_progress_during_sweep, "sweep never paused mid-list");
4650 assert_eq!(heap.bytes_used(), 0);
4651 }
4652
4653 #[test]
4654 fn post_mark_runs_once_per_atomic() {
4655 let heap = Heap::new();
4656 heap.unpause();
4657 for _ in 0..32 {
4658 let _ = heap.allocate(Cell0 {
4659 next: Cell::new(None),
4660 marker_calls: Cell::new(0),
4661 });
4662 }
4663 let roots = OneRoot(None);
4664 let call_count = std::cell::Cell::new(0);
4665 let mut step_count = 0;
4666 loop {
4667 step_count += 1;
4668 let outcome =
4669 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {
4670 call_count.set(call_count.get() + 1);
4671 });
4672 if outcome == StepOutcome::Paused {
4673 break;
4674 }
4675 assert!(step_count < 10_000, "did not converge");
4676 }
4677 assert_eq!(
4678 call_count.get(),
4679 1,
4680 "post_mark must run exactly once per cycle"
4681 );
4682 }
4683
4684 #[test]
4685 fn full_collect_equivalent_to_incremental_to_pause() {
4686 let h1 = Heap::new();
4687 h1.unpause();
4688 let head1 = h1.allocate(Cell0 {
4689 next: Cell::new(None),
4690 marker_calls: Cell::new(0),
4691 });
4692 let _orphan1 = h1.allocate(Cell0 {
4693 next: Cell::new(None),
4694 marker_calls: Cell::new(0),
4695 });
4696 let _orphan2 = h1.allocate(Cell0 {
4697 next: Cell::new(None),
4698 marker_calls: Cell::new(0),
4699 });
4700 let roots1 = OneRoot(Some(head1));
4701 h1.full_collect(&roots1);
4702 let bytes_full = h1.bytes_used();
4703
4704 let h2 = Heap::new();
4705 h2.unpause();
4706 let head2 = h2.allocate(Cell0 {
4707 next: Cell::new(None),
4708 marker_calls: Cell::new(0),
4709 });
4710 let _orphan3 = h2.allocate(Cell0 {
4711 next: Cell::new(None),
4712 marker_calls: Cell::new(0),
4713 });
4714 let _orphan4 = h2.allocate(Cell0 {
4715 next: Cell::new(None),
4716 marker_calls: Cell::new(0),
4717 });
4718 let roots2 = OneRoot(Some(head2));
4719 loop {
4720 let outcome =
4721 h2.incremental_step_with_post_mark(&roots2, StepBudget::from_work(1), |_| {});
4722 if outcome == StepOutcome::Paused {
4723 break;
4724 }
4725 }
4726 assert_eq!(h2.bytes_used(), bytes_full);
4727 }
4728
4729 struct DropFlag(std::rc::Rc<Cell<bool>>);
4734 impl Drop for DropFlag {
4735 fn drop(&mut self) {
4736 self.0.set(true);
4737 }
4738 }
4739 struct Tracked(#[allow(dead_code)] DropFlag);
4740 impl Trace for Tracked {
4741 fn trace(&self, _m: &mut Marker) {}
4742 }
4743
4744 #[test]
4745 fn allocate_uncollected_survives_collection_but_is_freed_on_heap_drop() {
4746 let heap = Heap::new();
4747 heap.unpause();
4748 let dropped = std::rc::Rc::new(Cell::new(false));
4749 let _gc = heap.allocate_uncollected(Tracked(DropFlag(dropped.clone())));
4750
4751 heap.full_collect(&OneRoot(None));
4754 assert!(
4755 !dropped.get(),
4756 "allocate_uncollected box must survive a full collection while the heap is alive"
4757 );
4758
4759 drop(heap);
4760 assert!(
4761 dropped.get(),
4762 "allocate_uncollected box must be freed once its heap drops (issue #249)"
4763 );
4764 }
4765
4766 #[test]
4767 fn bootstrapping_routes_allocate_to_the_uncollected_list() {
4768 let heap = Heap::new();
4769 heap.unpause();
4770 assert!(!heap.is_bootstrapping());
4771
4772 heap.begin_bootstrap();
4773 let dropped = std::rc::Rc::new(Cell::new(false));
4774 let _gc = heap.allocate(Tracked(DropFlag(dropped.clone())));
4775
4776 assert_eq!(
4779 heap.allgc_count(),
4780 0,
4781 "a bootstrap allocation must not join the normal collectable list"
4782 );
4783 heap.full_collect(&OneRoot(None));
4784 assert!(
4785 !dropped.get(),
4786 "a bootstrap allocation must survive collection while bootstrapping is active"
4787 );
4788
4789 heap.end_bootstrap();
4790 drop(heap);
4791 assert!(
4792 dropped.get(),
4793 "a bootstrap allocation must still be freed when the heap drops"
4794 );
4795 }
4796
4797 #[test]
4804 fn ownership_flags_distinguish_all_three_allocation_paths() {
4805 let heap = Heap::new();
4806 let tracked = heap.allocate(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4807 assert!(tracked.is_heap_owned());
4808 assert!(tracked.is_heap_tracked());
4809
4810 let bootstrap =
4811 heap.allocate_uncollected(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4812 assert!(
4813 bootstrap.is_heap_owned(),
4814 "bootstrap boxes die in drop_all — strict-guard hazard checks must see them"
4815 );
4816 assert!(!bootstrap.is_heap_tracked());
4817
4818 let detached = Gc::new_uncollected(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4819 assert!(!detached.is_heap_owned());
4820 assert!(!detached.is_heap_tracked());
4821 }
4822
4823 #[test]
4824 fn end_bootstrap_restores_normal_sweepable_allocation() {
4825 let heap = Heap::new();
4826 heap.unpause();
4827 heap.begin_bootstrap();
4828 heap.end_bootstrap();
4829
4830 let dropped = std::rc::Rc::new(Cell::new(false));
4831 let _gc = heap.allocate(Tracked(DropFlag(dropped.clone())));
4832 assert_eq!(heap.allgc_count(), 1);
4833
4834 heap.full_collect(&OneRoot(None));
4835 assert_eq!(
4841 heap.allgc_count(),
4842 0,
4843 "after end_bootstrap, an unreachable allocation must be swept normally"
4844 );
4845 }
4846
4847 struct AllocOnDrop {
4854 flag: std::rc::Rc<Cell<bool>>,
4855 inner: std::rc::Rc<Cell<bool>>,
4856 }
4857 impl Trace for AllocOnDrop {
4858 fn trace(&self, _m: &mut Marker) {}
4859 }
4860 impl Drop for AllocOnDrop {
4861 fn drop(&mut self) {
4862 self.flag.set(true);
4863 with_current_heap(|heap| {
4864 if let Some(heap) = heap {
4865 let _ = heap.allocate(Tracked(DropFlag(self.inner.clone())));
4866 }
4867 });
4868 }
4869 }
4870
4871 #[test]
4872 fn close_frees_objects_while_outer_guard_alive() {
4873 let heap = Heap::new();
4874 heap.unpause();
4875 let guard = HeapGuard::push(&heap);
4876
4877 let dropped = std::rc::Rc::new(Cell::new(false));
4878 let _gc = heap.allocate(Tracked(DropFlag(dropped.clone())));
4879 assert!(!dropped.get());
4880
4881 heap.drop_all();
4882 assert!(
4883 dropped.get(),
4884 "drop_all must run the object's destructor during the close call, \
4885 while the outer guard is still alive"
4886 );
4887 assert!(heap.is_closed());
4888 assert_eq!(heap.bytes_used(), 0);
4889 drop(guard);
4890 }
4891
4892 #[test]
4893 #[should_panic(expected = "closed heap")]
4894 fn allocate_into_closed_heap_panics() {
4895 let heap = Heap::new();
4896 heap.unpause();
4897 let _guard = HeapGuard::push(&heap);
4898 heap.drop_all();
4899 let _ = heap.allocate(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4900 }
4901
4902 #[test]
4903 fn destructor_allocating_during_teardown_does_not_leak() {
4904 let heap = Heap::new();
4905 heap.unpause();
4906 let _guard = HeapGuard::push(&heap);
4907
4908 let outer = std::rc::Rc::new(Cell::new(false));
4909 let inner = std::rc::Rc::new(Cell::new(false));
4910 let _gc = heap.allocate(AllocOnDrop {
4911 flag: outer.clone(),
4912 inner: inner.clone(),
4913 });
4914
4915 heap.drop_all();
4916 assert!(outer.get(), "the destructor itself must have run");
4917 assert!(
4918 inner.get(),
4919 "the box the destructor allocated mid-teardown must be freed by a \
4920 later drain pass, not leaked"
4921 );
4922 assert_eq!(heap.bytes_used(), 0);
4923 }
4924
4925 #[test]
4926 fn weak_handles_dead_immediately_after_close() {
4927 let heap = Heap::new();
4928 heap.unpause();
4929 let _guard = HeapGuard::push(&heap);
4930
4931 let gc = heap.allocate(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4932 let identity = gc.identity();
4933 let token = heap.register_allocation_token(identity);
4934 let weak = HeapRef::from_heap(&heap);
4935 assert!(
4936 weak.contains_allocation(identity, token),
4937 "weak handle upgrades while the box is live"
4938 );
4939
4940 heap.drop_all();
4941
4942 assert!(
4943 !weak.contains_allocation(identity, token),
4944 "weak handle must fail to upgrade the instant close clears the \
4945 allocation tokens, while the heap Rc is still alive"
4946 );
4947 }
4948
4949 struct BarrierOnDrop {
4956 flag: std::rc::Rc<Cell<bool>>,
4957 inner: std::rc::Rc<Cell<bool>>,
4958 }
4959 impl Trace for BarrierOnDrop {
4960 fn trace(&self, _m: &mut Marker) {}
4961 }
4962 impl Drop for BarrierOnDrop {
4963 fn drop(&mut self) {
4964 self.flag.set(true);
4965 with_current_heap(|heap| {
4966 if let Some(heap) = heap {
4967 let fresh = heap.allocate(Tracked(DropFlag(self.inner.clone())));
4968 heap.remember_minor_revisit(fresh.as_trace_ptr());
4969 }
4970 });
4971 }
4972 }
4973
4974 #[test]
4975 fn teardown_barrier_cannot_repopulate_grayagain() {
4976 let heap = Heap::new();
4977 heap.unpause();
4978 let _guard = HeapGuard::push(&heap);
4979
4980 let outer = std::rc::Rc::new(Cell::new(false));
4981 let inner = std::rc::Rc::new(Cell::new(false));
4982 let _gc = heap.allocate(BarrierOnDrop {
4983 flag: outer.clone(),
4984 inner: inner.clone(),
4985 });
4986
4987 heap.drop_all();
4988 assert!(outer.get(), "the destructor itself must have run");
4989 assert!(
4990 inner.get(),
4991 "the box the destructor allocated must still be drained"
4992 );
4993 assert_eq!(
4994 heap.grayagain_count(),
4995 0,
4996 "a barrier fired during teardown must not park a soon-freed \
4997 pointer in grayagain"
4998 );
4999
5000 heap.drop_all();
5001 assert_eq!(heap.grayagain_count(), 0);
5002 assert_eq!(heap.bytes_used(), 0);
5003 }
5004
5005 struct CollectOnDrop {
5013 flag: std::rc::Rc<Cell<bool>>,
5014 inner: std::rc::Rc<Cell<bool>>,
5015 }
5016 struct TrackedRoot(Option<Gc<Tracked>>);
5017 impl Trace for TrackedRoot {
5018 fn trace(&self, m: &mut Marker) {
5019 if let Some(g) = self.0 {
5020 m.mark(g);
5021 }
5022 }
5023 }
5024 impl Trace for CollectOnDrop {
5025 fn trace(&self, _m: &mut Marker) {}
5026 }
5027 impl Drop for CollectOnDrop {
5028 fn drop(&mut self) {
5029 self.flag.set(true);
5030 with_current_heap(|heap| {
5031 if let Some(heap) = heap {
5032 let fresh = heap.allocate(Tracked(DropFlag(self.inner.clone())));
5033 let root = TrackedRoot(Some(fresh));
5034 heap.minor_collect_with_post_mark(&root, |_: &mut Marker| {});
5035 heap.full_collect(&root);
5036 heap.step(&root);
5037 }
5038 });
5039 }
5040 }
5041
5042 #[test]
5043 fn collections_invoked_by_teardown_destructor_are_inert() {
5044 let heap = Heap::new();
5045 heap.unpause();
5046 let _guard = HeapGuard::push(&heap);
5047
5048 let outer = std::rc::Rc::new(Cell::new(false));
5049 let inner = std::rc::Rc::new(Cell::new(false));
5050 let _gc = heap.allocate(CollectOnDrop {
5051 flag: outer.clone(),
5052 inner: inner.clone(),
5053 });
5054
5055 heap.drop_all();
5056 assert!(outer.get(), "the destructor itself must have run");
5057 assert!(
5058 inner.get(),
5059 "the box the destructor allocated must still be drained"
5060 );
5061 assert_eq!(
5062 heap.minor_collections(),
5063 0,
5064 "a minor collection invoked from a teardown destructor must be inert"
5065 );
5066 assert_eq!(
5067 heap.full_collections(),
5068 0,
5069 "a full collection invoked from a teardown destructor must be inert"
5070 );
5071 assert_eq!(heap.grayagain_count(), 0);
5072
5073 heap.drop_all();
5074 assert_eq!(heap.grayagain_count(), 0);
5075 assert_eq!(heap.bytes_used(), 0);
5076 assert!(!heap.would_collect());
5077 }
5078
5079 #[test]
5084 fn post_close_token_minting_is_refused() {
5085 let heap = Heap::new();
5086 heap.unpause();
5087 let _guard = HeapGuard::push(&heap);
5088
5089 let gc = heap.allocate(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
5090 let identity = gc.identity();
5091
5092 heap.drop_all();
5093
5094 let token = heap.register_allocation_token(identity);
5095 assert_eq!(token, 0, "a closed heap must refuse to mint a token");
5096 assert!(
5097 !heap.contains_allocation(identity, token),
5098 "the refused token must never validate"
5099 );
5100 assert!(
5101 heap.allocation_token(identity).is_none(),
5102 "the refusal must not have touched the token map"
5103 );
5104 }
5105}