1use std::cell::{Cell, RefCell};
41use std::collections::{HashMap, HashSet};
42use std::hash::{BuildHasherDefault, Hasher};
43use std::marker::PhantomData;
44use std::ptr::NonNull;
45
46#[derive(Default)]
47struct IdentityHasher {
48 value: u64,
49}
50
51impl Hasher for IdentityHasher {
52 #[inline]
53 fn write(&mut self, bytes: &[u8]) {
54 const PRIME: u64 = 0x0000_0100_0000_01b3;
55 for &byte in bytes {
56 self.value ^= u64::from(byte);
57 self.value = self.value.wrapping_mul(PRIME);
58 }
59 }
60
61 #[inline]
62 fn write_usize(&mut self, i: usize) {
63 self.value = i as u64;
64 }
65
66 #[inline]
67 fn write_u64(&mut self, i: u64) {
68 self.value = i;
69 }
70
71 #[inline]
72 fn finish(&self) -> u64 {
73 let mut x = self.value;
74 x ^= x >> 30;
75 x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9);
76 x ^= x >> 27;
77 x = x.wrapping_mul(0x94d0_49bb_1331_11eb);
78 x ^ (x >> 31)
79 }
80}
81
82type IdentityBuildHasher = BuildHasherDefault<IdentityHasher>;
83type IdentityHashSet = HashSet<usize, IdentityBuildHasher>;
84type IdentityHashMap<V> = HashMap<usize, V, IdentityBuildHasher>;
85
86thread_local! {
100 static CURRENT_HEAP_STACK: RefCell<Vec<std::rc::Rc<Heap>>> = const { RefCell::new(Vec::new()) };
101}
102
103pub struct HeapGuard {
118 heap: std::rc::Rc<Heap>,
119}
120
121impl HeapGuard {
122 pub fn push(heap: &std::rc::Rc<Heap>) -> Self {
124 CURRENT_HEAP_STACK.with(|stack| stack.borrow_mut().push(std::rc::Rc::clone(heap)));
125 HeapGuard {
126 heap: std::rc::Rc::clone(heap),
127 }
128 }
129}
130
131impl Drop for HeapGuard {
132 fn drop(&mut self) {
133 CURRENT_HEAP_STACK.with(|stack| {
134 let popped = stack.borrow_mut().pop();
135 debug_assert!(
136 popped
137 .as_ref()
138 .is_some_and(|top| std::rc::Rc::ptr_eq(top, &self.heap)),
139 "HeapGuard::drop popped a frame it did not push — guards must \
140 drop in reverse push order on the thread that created them"
141 );
142 });
143 }
144}
145
146pub struct BootstrapScope {
158 depth: std::rc::Rc<Cell<usize>>,
159}
160
161impl Drop for BootstrapScope {
162 fn drop(&mut self) {
163 let depth = self.depth.get();
164 debug_assert!(depth > 0, "BootstrapScope dropped with zero depth");
165 self.depth.set(depth.saturating_sub(1));
166 }
167}
168
169thread_local! {
170 static DETACHED_ALLOCATIONS: Cell<usize> = const { Cell::new(0) };
171}
172
173pub fn strict_guard_mode() -> bool {
181 static STRICT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
182 *STRICT.get_or_init(|| std::env::var_os("OMNILUA_GC_STRICT_GUARD").is_some_and(|v| v == "1"))
183}
184
185pub fn detached_allocations() -> usize {
192 DETACHED_ALLOCATIONS.with(|c| c.get())
193}
194
195pub fn with_current_heap<R>(f: impl for<'a> FnOnce(Option<&'a std::rc::Rc<Heap>>) -> R) -> R {
202 let top = CURRENT_HEAP_STACK.with(|stack| stack.borrow().last().cloned());
203 f(top.as_ref())
204}
205
206#[derive(Clone, Debug)]
212pub struct HeapRef {
213 weak: std::rc::Weak<Heap>,
214}
215
216impl HeapRef {
217 pub fn from_heap(heap: &std::rc::Rc<Heap>) -> Self {
218 HeapRef {
219 weak: std::rc::Rc::downgrade(heap),
220 }
221 }
222
223 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
224 match self.weak.upgrade() {
225 Some(heap) => heap.contains_allocation(identity, token),
226 None => false,
227 }
228 }
229}
230
231#[derive(Copy, Clone, PartialEq, Eq, Debug)]
233pub enum Color {
234 White0,
238 White1,
240 Gray,
242 Black,
244}
245
246impl Color {
247 pub fn is_white(self) -> bool {
248 matches!(self, Color::White0 | Color::White1)
249 }
250
251 fn other_white(self) -> Self {
252 match self {
253 Color::White0 => Color::White1,
254 Color::White1 => Color::White0,
255 Color::Gray | Color::Black => self,
256 }
257 }
258}
259
260#[derive(Copy, Clone, PartialEq, Eq, Debug)]
264pub enum GcAge {
265 New,
266 Survival,
267 Old0,
268 Old1,
269 Old,
270 Touched1,
271 Touched2,
272}
273
274impl GcAge {
275 pub fn is_old(self) -> bool {
276 !matches!(self, GcAge::New | GcAge::Survival)
277 }
278
279 fn next_after_minor(self) -> Self {
280 match self {
281 GcAge::New => GcAge::Survival,
282 GcAge::Survival | GcAge::Old0 => GcAge::Old1,
283 GcAge::Old1 | GcAge::Old | GcAge::Touched2 => GcAge::Old,
284 GcAge::Touched1 => GcAge::Touched2,
285 }
286 }
287}
288
289pub trait Udata51Probe: std::any::Any {
305 fn is_alive(&self) -> bool;
309
310 fn as_any(&self) -> &dyn std::any::Any;
312}
313
314pub trait FinalizerEntry: Clone {
317 fn identity(&self) -> usize;
318 fn heap_ptr(&self) -> Option<NonNull<GcBox<dyn Trace>>> {
319 None
320 }
321 fn age(&self) -> GcAge;
322 fn is_finalized(&self) -> bool;
323 fn set_finalized(&self, finalized: bool);
324}
325
326pub trait WeakEntry: Clone {
328 type Strong: Clone;
329
330 fn identity(&self) -> usize;
331 fn list_kind(&self) -> WeakListKind;
332 fn upgrade(&self) -> Option<Self::Strong>;
333}
334
335#[derive(Copy, Clone, Debug, PartialEq, Eq)]
336pub enum WeakListKind {
337 WeakValues,
338 Ephemeron,
339 AllWeak,
340}
341
342#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
343pub struct WeakRegistryStats {
344 pub tracked: usize,
345 pub snapshot_live: usize,
346 pub snapshot_dead: usize,
347 pub retained: usize,
348 pub weak_values: usize,
349 pub ephemeron: usize,
350 pub all_weak: usize,
351}
352
353#[derive(Clone, Debug)]
354pub struct WeakRegistry<T: WeakEntry> {
355 weak_values: Vec<T>,
356 ephemeron: Vec<T>,
357 all_weak: Vec<T>,
358 last_stats: WeakRegistryStats,
359}
360
361#[derive(Clone, Debug, PartialEq, Eq)]
362pub struct WeakRegistrySnapshot<T> {
363 pub weak_values: Vec<T>,
364 pub ephemeron: Vec<T>,
365 pub all_weak: Vec<T>,
366}
367
368impl<T> Default for WeakRegistrySnapshot<T> {
369 fn default() -> Self {
370 Self {
371 weak_values: Vec::new(),
372 ephemeron: Vec::new(),
373 all_weak: Vec::new(),
374 }
375 }
376}
377
378impl<T> WeakRegistrySnapshot<T> {
379 pub fn len(&self) -> usize {
380 self.weak_values
381 .len()
382 .saturating_add(self.ephemeron.len())
383 .saturating_add(self.all_weak.len())
384 }
385
386 pub fn into_flat(self) -> Vec<T> {
387 self.weak_values
388 .into_iter()
389 .chain(self.ephemeron)
390 .chain(self.all_weak)
391 .collect()
392 }
393}
394
395impl<T: WeakEntry> Default for WeakRegistry<T> {
396 fn default() -> Self {
397 Self {
398 weak_values: Vec::new(),
399 ephemeron: Vec::new(),
400 all_weak: Vec::new(),
401 last_stats: WeakRegistryStats::default(),
402 }
403 }
404}
405
406impl<T: WeakEntry> WeakRegistry<T> {
407 pub fn len(&self) -> usize {
408 self.weak_values
409 .len()
410 .saturating_add(self.ephemeron.len())
411 .saturating_add(self.all_weak.len())
412 }
413
414 pub fn stats(&self) -> WeakRegistryStats {
415 self.last_stats
416 }
417
418 fn list_mut(&mut self, kind: WeakListKind) -> &mut Vec<T> {
419 match kind {
420 WeakListKind::WeakValues => &mut self.weak_values,
421 WeakListKind::Ephemeron => &mut self.ephemeron,
422 WeakListKind::AllWeak => &mut self.all_weak,
423 }
424 }
425
426 pub fn remove_identity(&mut self, id: usize) {
427 self.weak_values.retain(|entry| entry.identity() != id);
428 self.ephemeron.retain(|entry| entry.identity() != id);
429 self.all_weak.retain(|entry| entry.identity() != id);
430 self.last_stats.tracked = self.len();
431 self.last_stats.retained = self.len();
432 self.update_cohort_stats();
433 }
434
435 fn update_cohort_stats(&mut self) {
436 self.last_stats.weak_values = self.weak_values.len();
437 self.last_stats.ephemeron = self.ephemeron.len();
438 self.last_stats.all_weak = self.all_weak.len();
439 }
440
441 pub fn push_unique(&mut self, entry: T) {
442 let id = entry.identity();
443 self.remove_identity(id);
444 self.list_mut(entry.list_kind()).push(entry);
445 self.last_stats.tracked = self.len();
446 self.last_stats.retained = self.len();
447 self.update_cohort_stats();
448 }
449
450 pub fn live_snapshot_by_kind(&mut self) -> WeakRegistrySnapshot<T::Strong> {
451 let tracked_before = self.len();
452 let weak_values_capacity = self.weak_values.len();
453 let ephemeron_capacity = self.ephemeron.len();
454 let all_weak_capacity = self.all_weak.len();
455 let mut seen = std::collections::HashSet::<usize>::with_capacity(tracked_before);
456 let mut live = WeakRegistrySnapshot {
457 weak_values: Vec::with_capacity(weak_values_capacity),
458 ephemeron: Vec::with_capacity(ephemeron_capacity),
459 all_weak: Vec::with_capacity(all_weak_capacity),
460 };
461 let mut dead = 0usize;
462
463 let entries = std::mem::take(&mut self.weak_values)
464 .into_iter()
465 .chain(std::mem::take(&mut self.ephemeron))
466 .chain(std::mem::take(&mut self.all_weak));
467 for entry in entries {
468 if !seen.insert(entry.identity()) {
469 continue;
470 }
471 match entry.upgrade() {
472 Some(strong) => {
473 match entry.list_kind() {
474 WeakListKind::WeakValues => live.weak_values.push(strong),
475 WeakListKind::Ephemeron => live.ephemeron.push(strong),
476 WeakListKind::AllWeak => live.all_weak.push(strong),
477 }
478 self.list_mut(entry.list_kind()).push(entry);
479 }
480 None => dead += 1,
481 }
482 }
483
484 self.last_stats = WeakRegistryStats {
485 tracked: tracked_before,
486 snapshot_live: live.len(),
487 snapshot_dead: dead,
488 retained: self.len(),
489 weak_values: self.weak_values.len(),
490 ephemeron: self.ephemeron.len(),
491 all_weak: self.all_weak.len(),
492 };
493 live
494 }
495
496 pub fn live_snapshot(&mut self) -> Vec<T::Strong> {
497 self.live_snapshot_by_kind().into_flat()
498 }
499
500 pub fn retain_identities(&mut self, ids: &std::collections::HashSet<usize>) {
501 self.weak_values
502 .retain(|entry| ids.contains(&entry.identity()));
503 self.ephemeron
504 .retain(|entry| ids.contains(&entry.identity()));
505 self.all_weak
506 .retain(|entry| ids.contains(&entry.identity()));
507 self.last_stats.retained = self.len();
508 self.last_stats.tracked = self.len();
509 self.update_cohort_stats();
510 }
511}
512
513#[derive(Clone, Debug)]
514pub struct FinalizerRegistry<T: FinalizerEntry> {
515 pending: Vec<T>,
516 to_be_finalized: Vec<T>,
517 pending_reallyold: usize,
518 pending_old1: usize,
519 pending_survival: usize,
520}
521
522#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
523pub struct FinalizerRegistryStats {
524 pub pending_young: usize,
525 pub pending_old: usize,
526 pub to_be_finalized_young: usize,
527 pub to_be_finalized_old: usize,
528 pub finobj_new: usize,
529 pub finobj_survival: usize,
530 pub finobj_old1: usize,
531 pub finobj_reallyold: usize,
532 pub finobj_minor_scan: usize,
533}
534
535impl<T: FinalizerEntry> Default for FinalizerRegistry<T> {
536 fn default() -> Self {
537 Self {
538 pending: Vec::new(),
539 to_be_finalized: Vec::new(),
540 pending_reallyold: 0,
541 pending_old1: 0,
542 pending_survival: 0,
543 }
544 }
545}
546
547impl<T: FinalizerEntry> FinalizerRegistry<T> {
548 fn pending_new_len(&self) -> usize {
549 self.pending.len().saturating_sub(
550 self.pending_reallyold
551 .saturating_add(self.pending_old1)
552 .saturating_add(self.pending_survival),
553 )
554 }
555
556 fn minor_scan_start(&self) -> usize {
557 self.pending_reallyold.saturating_add(self.pending_old1)
558 }
559
560 fn debug_assert_pending_cohorts(&self) {
561 debug_assert!(
562 self.pending_reallyold
563 .saturating_add(self.pending_old1)
564 .saturating_add(self.pending_survival)
565 <= self.pending.len()
566 );
567 }
568
569 pub fn pending(&self) -> &[T] {
570 &self.pending
571 }
572
573 pub fn pending_snapshot(&self) -> Vec<T> {
574 self.pending.clone()
575 }
576
577 pub fn pending_minor_snapshot(&self) -> Vec<T> {
578 self.pending[self.minor_scan_start().min(self.pending.len())..].to_vec()
579 }
580
581 pub fn to_be_finalized(&self) -> &[T] {
582 &self.to_be_finalized
583 }
584
585 pub fn pending_len(&self) -> usize {
586 self.pending.len()
587 }
588
589 pub fn to_be_finalized_len(&self) -> usize {
590 self.to_be_finalized.len()
591 }
592
593 pub fn has_to_be_finalized(&self) -> bool {
594 !self.to_be_finalized.is_empty()
595 }
596
597 pub fn stats(&self) -> FinalizerRegistryStats {
598 fn count_by_age<T: FinalizerEntry>(objects: &[T]) -> (usize, usize) {
599 objects
600 .iter()
601 .fold((0usize, 0usize), |(young, old), object| {
602 if object.age().is_old() {
603 (young, old + 1)
604 } else {
605 (young + 1, old)
606 }
607 })
608 }
609 let (pending_young, pending_old) = count_by_age(&self.pending);
610 let (to_be_finalized_young, to_be_finalized_old) = count_by_age(&self.to_be_finalized);
611 FinalizerRegistryStats {
612 pending_young,
613 pending_old,
614 to_be_finalized_young,
615 to_be_finalized_old,
616 finobj_new: self.pending_new_len(),
617 finobj_survival: self.pending_survival,
618 finobj_old1: self.pending_old1,
619 finobj_reallyold: self.pending_reallyold,
620 finobj_minor_scan: self.pending.len().saturating_sub(self.minor_scan_start()),
621 }
622 }
623
624 pub fn push_pending_unique(&mut self, object: T) -> bool {
625 if object.is_finalized() {
626 return false;
627 }
628 let id = object.identity();
629 if !self.pending.iter().any(|o| o.identity() == id) {
630 object.set_finalized(true);
631 self.pending.push(object);
632 self.debug_assert_pending_cohorts();
633 true
634 } else {
635 false
636 }
637 }
638
639 pub fn take_pending(&mut self) -> Vec<T> {
640 self.pending_reallyold = 0;
641 self.pending_old1 = 0;
642 self.pending_survival = 0;
643 std::mem::take(&mut self.pending)
644 }
645
646 fn retain_pending_not_in(&mut self, ids: &std::collections::HashSet<usize>) {
647 if ids.is_empty() {
648 return;
649 }
650 let original_reallyold = self.pending_reallyold;
651 let original_old1 = self.pending_old1;
652 let original_survival = self.pending_survival;
653 let mut retained_reallyold = original_reallyold;
654 let mut retained_old1 = original_old1;
655 let mut retained_survival = original_survival;
656 let mut retained = Vec::with_capacity(self.pending.len());
657 for (index, object) in std::mem::take(&mut self.pending).into_iter().enumerate() {
658 if ids.contains(&object.identity()) {
659 if index < original_reallyold {
660 retained_reallyold -= 1;
661 } else if index < original_reallyold + original_old1 {
662 retained_old1 -= 1;
663 } else if index < original_reallyold + original_old1 + original_survival {
664 retained_survival -= 1;
665 }
666 } else {
667 retained.push(object);
668 }
669 }
670 self.pending = retained;
671 self.pending_reallyold = retained_reallyold;
672 self.pending_old1 = retained_old1;
673 self.pending_survival = retained_survival;
674 self.debug_assert_pending_cohorts();
675 }
676
677 pub fn push_to_be_finalized(&mut self, object: T) {
678 object.set_finalized(true);
679 self.to_be_finalized.push(object);
680 }
681
682 fn extend_to_be_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
683 let drain_order: Vec<T> = objects.into_iter().rev().collect();
684 for object in drain_order.iter().cloned() {
685 self.push_to_be_finalized(object);
686 }
687 drain_order
688 }
689
690 pub fn promote_pending_to_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
691 if objects.is_empty() {
692 return Vec::new();
693 }
694 let mut ids: std::collections::HashSet<usize> =
695 std::collections::HashSet::with_capacity(objects.len());
696 ids.extend(objects.iter().map(|object| object.identity()));
697 self.retain_pending_not_in(&ids);
698 self.extend_to_be_finalized(objects)
699 }
700
701 pub fn promote_all_pending_to_old(&mut self) {
702 self.pending_reallyold = self.pending.len();
703 self.pending_old1 = 0;
704 self.pending_survival = 0;
705 }
706
707 pub fn reset_generation_boundaries(&mut self) {
708 self.pending_reallyold = 0;
709 self.pending_old1 = 0;
710 self.pending_survival = 0;
711 }
712
713 pub fn finish_minor_collection(&mut self) {
714 let new = self.pending_new_len();
715 self.pending_reallyold = self.pending_reallyold.saturating_add(self.pending_old1);
716 self.pending_old1 = self.pending_survival;
717 self.pending_survival = new;
718 self.debug_assert_pending_cohorts();
719 }
720
721 pub fn pop_to_be_finalized(&mut self) -> Option<T> {
722 let object = if self.to_be_finalized.is_empty() {
723 None
724 } else {
725 Some(self.to_be_finalized.remove(0))
726 };
727 if let Some(ref object) = object {
728 object.set_finalized(false);
729 }
730 object
731 }
732}
733
734#[repr(C)]
736pub struct GcHeader {
737 color: Cell<Color>,
744 age: Cell<GcAge>,
745 flags: Cell<u8>,
753 size: Cell<u32>,
760 next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
762 gray_next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
764}
765
766const HDR_FINALIZED: u8 = 1;
767const HDR_COLLECTED: u8 = 2;
768const HDR_GRAY_LISTED: u8 = 4;
769const HDR_HEAP_OWNED: u8 = 16;
776const HDR_FREED: u8 = 8;
781
782impl GcHeader {
783 fn new_white(size: usize, color: Color, flags: u8) -> Self {
784 Self {
785 color: Cell::new(color),
786 age: Cell::new(GcAge::New),
787 flags: Cell::new(flags),
788 size: Cell::new(size.min(u32::MAX as usize) as u32),
789 next: Cell::new(None),
790 gray_next: Cell::new(None),
791 }
792 }
793
794 fn flag(&self, bit: u8) -> bool {
795 self.flags.get() & bit != 0
796 }
797
798 fn set_flag(&self, bit: u8, on: bool) {
799 let f = self.flags.get();
800 self.flags.set(if on { f | bit } else { f & !bit });
801 }
802
803 pub fn finalized(&self) -> bool {
804 self.flag(HDR_FINALIZED)
805 }
806
807 pub fn set_finalized(&self, finalized: bool) {
808 self.set_flag(HDR_FINALIZED, finalized);
809 }
810
811 pub fn collected(&self) -> bool {
812 self.flag(HDR_COLLECTED)
813 }
814
815 pub fn gray_listed(&self) -> bool {
816 self.flag(HDR_GRAY_LISTED)
817 }
818
819 pub fn set_gray_listed(&self, listed: bool) {
820 self.set_flag(HDR_GRAY_LISTED, listed);
821 }
822
823 pub fn size(&self) -> usize {
824 self.size.get() as usize
825 }
826
827 pub fn set_size(&self, size: usize) {
828 self.size.set(size.min(u32::MAX as usize) as u32);
829 }
830}
831
832#[repr(C)]
834pub struct GcBox<T: ?Sized> {
835 header: GcHeader,
836 value: T,
837}
838
839impl<T: ?Sized> GcBox<T> {
840 pub fn header(&self) -> &GcHeader {
841 &self.header
842 }
843 pub fn value(&self) -> &T {
844 &self.value
845 }
846}
847
848pub struct Gc<T: ?Sized> {
850 ptr: NonNull<GcBox<T>>,
851 _marker: PhantomData<T>,
853}
854
855impl<T: ?Sized> Copy for Gc<T> {}
859impl<T: ?Sized> Clone for Gc<T> {
860 fn clone(&self) -> Self {
861 *self
862 }
863}
864
865impl<T: ?Sized> PartialEq for Gc<T> {
866 fn eq(&self, other: &Self) -> bool {
867 std::ptr::addr_eq(self.ptr.as_ptr(), other.ptr.as_ptr())
868 }
869}
870impl<T: ?Sized> Eq for Gc<T> {}
871
872impl<T: ?Sized> std::hash::Hash for Gc<T> {
873 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
874 self.ptr.as_ptr().hash(state)
875 }
876}
877
878impl<T: ?Sized> std::fmt::Debug for Gc<T> {
879 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
880 write!(f, "Gc({:p})", self.ptr.as_ptr())
881 }
882}
883
884impl<T: Trace + 'static> Gc<T> {
885 pub fn new_uncollected(value: T) -> Self {
891 DETACHED_ALLOCATIONS.with(|c| c.set(c.get() + 1));
892 let size = std::mem::size_of::<T>();
893 let boxed = Box::new(GcBox {
894 header: GcHeader::new_white(size, Color::White0, 0),
895 value,
896 });
897 Gc {
898 ptr: NonNull::new(Box::into_raw(boxed)).expect("Box::into_raw is non-null"),
899 _marker: PhantomData,
900 }
901 }
902
903 pub fn as_trace_ptr(self) -> NonNull<GcBox<dyn Trace>> {
905 self.ptr
906 }
907}
908
909impl<T: ?Sized> Gc<T> {
910 pub fn ptr_eq(a: Self, b: Self) -> bool {
912 std::ptr::addr_eq(a.ptr.as_ptr(), b.ptr.as_ptr())
913 }
914
915 pub fn identity(self) -> usize {
918 self.ptr.as_ptr() as *const () as usize
919 }
920
921 fn as_box(&self) -> &GcBox<T> {
925 let bx = unsafe { self.ptr.as_ref() };
931 debug_assert!(
932 !bx.header.flag(HDR_FREED),
933 "use-after-sweep: Gc<{}> dereferenced after the collector swept it \
934 (caught by LUA_RS_GC_QUARANTINE; this is a rooting bug — the object \
935 was reachable by execution but not by the root trace)",
936 std::any::type_name::<T>()
937 );
938 bx
939 }
940
941 fn header(&self) -> &GcHeader {
942 &self.as_box().header
943 }
944
945 pub fn is_heap_tracked(self) -> bool {
950 self.header().collected()
951 }
952
953 pub fn is_heap_owned(self) -> bool {
961 self.header().flag(HDR_HEAP_OWNED)
962 }
963
964 pub fn color(self) -> Color {
965 self.header().color.get()
966 }
967
968 pub fn set_color(self, color: Color) {
969 self.header().color.set(color);
970 }
971
972 pub fn age(self) -> GcAge {
973 self.header().age.get()
974 }
975
976 pub fn set_age(self, age: GcAge) {
977 self.header().age.set(age);
978 }
979
980 pub fn is_finalized(self) -> bool {
981 self.header().finalized()
982 }
983
984 pub fn set_finalized(self, finalized: bool) {
985 self.header().set_finalized(finalized);
986 }
987
988 pub fn account_buffer(&self, heap: &Heap, delta: isize) {
998 if delta == 0 {
999 return;
1000 }
1001 let header = self.header();
1002 if !header.collected() {
1003 return;
1004 }
1005 if delta >= 0 {
1006 header.set_size(header.size().saturating_add(delta as usize));
1007 } else {
1008 header.set_size(header.size().saturating_sub((-delta) as usize));
1009 }
1010 heap.adjust_bytes(delta);
1011 }
1012}
1013
1014impl<T: ?Sized> std::ops::Deref for Gc<T> {
1015 type Target = T;
1016 fn deref(&self) -> &T {
1017 &self.as_box().value
1018 }
1019}
1020
1021impl<T: ?Sized> AsRef<T> for Gc<T> {
1022 fn as_ref(&self) -> &T {
1023 &self.as_box().value
1024 }
1025}
1026
1027pub trait Trace {
1044 fn trace(&self, m: &mut Marker);
1045
1046 fn type_name(&self) -> &'static str {
1052 "unknown"
1053 }
1054}
1055
1056impl<T: Trace> Trace for Vec<T> {
1058 fn trace(&self, m: &mut Marker) {
1059 for item in self.iter() {
1060 item.trace(m);
1061 }
1062 }
1063}
1064
1065impl<T: Trace> Trace for Option<T> {
1066 fn trace(&self, m: &mut Marker) {
1067 if let Some(v) = self {
1068 v.trace(m);
1069 }
1070 }
1071}
1072
1073impl<T: Trace + ?Sized> Trace for Box<T> {
1074 fn trace(&self, m: &mut Marker) {
1075 (**self).trace(m);
1076 }
1077}
1078
1079impl<T: Trace + ?Sized> Trace for std::rc::Rc<T> {
1080 fn trace(&self, m: &mut Marker) {
1081 (**self).trace(m);
1082 }
1083}
1084
1085impl<T: Trace> Trace for RefCell<T> {
1086 fn trace(&self, m: &mut Marker) {
1087 self.borrow().trace(m);
1088 }
1089}
1090
1091impl<T: Trace + 'static> Trace for Gc<T> {
1093 fn trace(&self, m: &mut Marker) {
1094 m.mark(*self);
1095 }
1096}
1097
1098macro_rules! trace_noop {
1100 ($($t:ty),*) => {
1101 $(impl Trace for $t {
1102 fn trace(&self, _m: &mut Marker) {}
1103 })*
1104 };
1105}
1106trace_noop!(
1107 bool, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, char, String,
1108 str
1109);
1110
1111impl<T> Trace for std::marker::PhantomData<T> {
1112 fn trace(&self, _m: &mut Marker) {}
1113}
1114
1115#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1120pub struct MarkerStats {
1121 pub marked: usize,
1122 pub marked_young: usize,
1123 pub marked_old: usize,
1124 pub traced: usize,
1125 pub traced_young: usize,
1126 pub traced_old: usize,
1127}
1128
1129#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1131pub struct SweepStats {
1132 pub visited: usize,
1133 pub visited_young: usize,
1134 pub visited_old: usize,
1135 pub revisit: usize,
1136 pub freed: usize,
1137 pub freed_bytes: usize,
1138}
1139
1140impl SweepStats {
1141 fn record_visit(&mut self, age: GcAge) {
1142 self.visited += 1;
1143 if age.is_old() {
1144 self.visited_old += 1;
1145 } else {
1146 self.visited_young += 1;
1147 }
1148 }
1149
1150 fn record_free(&mut self, bytes: usize) {
1151 self.freed += 1;
1152 self.freed_bytes += bytes;
1153 }
1154
1155 fn add(&mut self, other: Self) {
1156 self.visited += other.visited;
1157 self.visited_young += other.visited_young;
1158 self.visited_old += other.visited_old;
1159 self.revisit += other.revisit;
1160 self.freed += other.freed;
1161 self.freed_bytes += other.freed_bytes;
1162 }
1163}
1164
1165struct OldRevisitTracker {
1166 old_revisit_ids: Vec<usize>,
1167 processed_ids: Vec<usize>,
1168}
1169
1170impl OldRevisitTracker {
1171 fn new(old_revisit: &[NonNull<GcBox<dyn Trace>>]) -> Option<Self> {
1172 if old_revisit.is_empty() {
1173 return None;
1174 }
1175 let mut old_revisit_ids: Vec<usize> = old_revisit
1176 .iter()
1177 .map(|ptr| ptr.as_ptr() as *const () as usize)
1178 .collect();
1179 old_revisit_ids.sort_unstable();
1180 old_revisit_ids.dedup();
1181 Some(Self {
1182 old_revisit_ids,
1183 processed_ids: Vec::new(),
1184 })
1185 }
1186
1187 #[inline(always)]
1188 fn record_processed(&mut self, id: usize) {
1189 if self.old_revisit_ids.binary_search(&id).is_ok() {
1190 self.processed_ids.push(id);
1191 }
1192 }
1193
1194 fn finish(&mut self) {
1195 self.processed_ids.sort_unstable();
1196 self.processed_ids.dedup();
1197 }
1198
1199 #[inline(always)]
1200 fn was_processed(&self, id: usize) -> bool {
1201 self.processed_ids.binary_search(&id).is_ok()
1202 }
1203}
1204
1205#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1207pub struct AllGcCohortStats {
1208 pub new: usize,
1209 pub survival: usize,
1210 pub old1: usize,
1211 pub old: usize,
1212}
1213
1214#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1215enum MarkerMode {
1216 Full,
1217 Minor,
1218}
1219
1220pub struct Marker {
1222 gray_queue: Vec<NonNull<GcBox<dyn Trace>>>,
1223 visited: IdentityHashSet,
1224 stats: MarkerStats,
1225 mode: MarkerMode,
1226}
1227
1228impl Marker {
1229 fn new_with_capacity(mode: MarkerMode, capacity: usize) -> Self {
1230 Self {
1231 gray_queue: Vec::with_capacity(256),
1232 visited: IdentityHashSet::with_capacity_and_hasher(
1233 capacity,
1234 IdentityBuildHasher::default(),
1235 ),
1236 stats: MarkerStats::default(),
1237 mode,
1238 }
1239 }
1240
1241 fn should_trace_age(&self, age: GcAge) -> bool {
1242 match self.mode {
1243 MarkerMode::Full => true,
1244 MarkerMode::Minor => !matches!(age, GcAge::Old),
1245 }
1246 }
1247
1248 pub fn mark<T: Trace + 'static>(&mut self, gc: Gc<T>) {
1261 let ptr: NonNull<GcBox<dyn Trace>> = gc.ptr;
1262 self.mark_box(ptr, gc.header(), gc.identity());
1263 }
1264
1265 fn mark_box(&mut self, ptr: NonNull<GcBox<dyn Trace>>, header: &GcHeader, id: usize) {
1266 debug_assert!(
1267 !header.flag(HDR_FREED),
1268 "GC marker reached a quarantined (swept) object at {id:#x} — a root \
1269 traced a stale GcRef (caught by LUA_RS_GC_QUARANTINE; bug-B class: \
1270 garbage slot fed into the marker)"
1271 );
1272 if self.visited.insert(id) {
1273 let age = header.age.get();
1274 self.stats.marked += 1;
1275 if age.is_old() {
1276 self.stats.marked_old += 1;
1277 } else {
1278 self.stats.marked_young += 1;
1279 }
1280 if self.should_trace_age(age) {
1281 header.color.set(Color::Gray);
1282 self.gray_queue.push(ptr);
1283 }
1284 }
1285 }
1286
1287 pub fn try_visit(&mut self, addr: usize) -> bool {
1292 self.visited.insert(addr)
1293 }
1294
1295 pub fn is_visited(&self, id: usize) -> bool {
1300 self.visited.contains(&id)
1301 }
1302
1303 pub fn is_marked_or_old<T: Trace + 'static>(&self, gc: Gc<T>) -> bool {
1306 self.is_visited(gc.identity())
1307 || (matches!(self.mode, MarkerMode::Minor) && gc.age().is_old())
1308 }
1309
1310 pub fn visited_count(&self) -> usize {
1314 self.visited.len()
1315 }
1316
1317 pub fn stats(&self) -> MarkerStats {
1319 self.stats
1320 }
1321
1322 pub fn drain_gray_queue(&mut self) {
1331 while let Some(gray_ptr) = self.gray_queue.pop() {
1332 unsafe {
1333 let bx = gray_ptr.as_ref();
1334 self.stats.traced += 1;
1335 if bx.header.age.get().is_old() {
1336 self.stats.traced_old += 1;
1337 } else {
1338 self.stats.traced_young += 1;
1339 }
1340 bx.header.color.set(Color::Black);
1341 bx.value.trace(self);
1342 }
1343 }
1344 }
1345}
1346
1347#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1366pub enum GcState {
1367 Pause,
1368 Propagate,
1369 EnterAtomic,
1370 Atomic,
1371 SweepAllGc,
1372 SweepFinObj,
1373 SweepToBeFnz,
1374 SweepEnd,
1375 CallFin,
1376}
1377
1378impl GcState {
1379 pub fn is_pause(self) -> bool {
1380 matches!(self, GcState::Pause)
1381 }
1382 pub fn is_propagate(self) -> bool {
1383 matches!(self, GcState::Propagate)
1384 }
1385 pub fn is_invariant(self) -> bool {
1386 matches!(
1387 self,
1388 GcState::Propagate | GcState::EnterAtomic | GcState::Atomic
1389 )
1390 }
1391 pub fn is_sweep(self) -> bool {
1392 matches!(
1393 self,
1394 GcState::SweepAllGc | GcState::SweepFinObj | GcState::SweepToBeFnz | GcState::SweepEnd
1395 )
1396 }
1397}
1398
1399#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1401pub enum StepOutcome {
1402 Paused,
1404 InProgress,
1407 SkippedStopped,
1410}
1411
1412#[derive(Copy, Clone, Debug)]
1420pub struct StepBudget {
1421 pub remaining_work: isize,
1422 pub max_credit: isize,
1423}
1424
1425impl StepBudget {
1426 pub fn from_work(work: isize) -> Self {
1428 Self {
1429 remaining_work: work.max(1),
1430 max_credit: work.max(1),
1431 }
1432 }
1433}
1434
1435const GC_MIN_THRESHOLD: usize = 1024 * 1024;
1450
1451pub struct Heap {
1452 head: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1455 finobj: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1457 tobefnz: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1459 survival: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1462 old1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1465 reallyold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1468 firstold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1471 finobjsur: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1473 finobjold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1475 finobjrold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1477 bytes: Cell<usize>,
1479 objects: Cell<usize>,
1481 current_white: Cell<Color>,
1483 allocation_tokens: RefCell<IdentityHashMap<usize>>,
1487 next_allocation_token: Cell<usize>,
1489 threshold: Cell<usize>,
1491 stress: bool,
1498 quarantine: bool,
1509 quarantined: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1513 uncollected: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1521 bootstrap_depth: std::rc::Rc<Cell<usize>>,
1534 pause_multiplier: Cell<usize>,
1536 state: Cell<GcState>,
1538 paused: Cell<bool>,
1541 collections: Cell<usize>,
1543 minor_collections: Cell<usize>,
1545 full_collections: Cell<usize>,
1547 last_mark_stats: Cell<MarkerStats>,
1549 last_sweep_stats: Cell<SweepStats>,
1551 grayagain: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1555 marker: RefCell<Option<Marker>>,
1558 marker_pool: RefCell<Option<(Vec<NonNull<GcBox<dyn Trace>>>, IdentityHashSet)>>,
1564 sweep_prev_next: Cell<Option<NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>>>,
1569 v51_udata_roster: RefCell<Vec<std::rc::Rc<dyn Udata51Probe>>>,
1576}
1577
1578impl Heap {
1579 pub fn new() -> std::rc::Rc<Self> {
1588 std::rc::Rc::new(Self {
1589 head: Cell::new(None),
1590 finobj: Cell::new(None),
1591 tobefnz: Cell::new(None),
1592 survival: Cell::new(None),
1593 old1: Cell::new(None),
1594 reallyold: Cell::new(None),
1595 firstold1: Cell::new(None),
1596 finobjsur: Cell::new(None),
1597 finobjold1: Cell::new(None),
1598 finobjrold: Cell::new(None),
1599 bytes: Cell::new(0),
1600 objects: Cell::new(0),
1601 current_white: Cell::new(Color::White0),
1602 allocation_tokens: RefCell::new(IdentityHashMap::default()),
1603 next_allocation_token: Cell::new(1),
1604 threshold: Cell::new(64 * 1024), stress: std::env::var_os("LUA_RS_GC_STRESS").is_some_and(|v| v == "1"),
1606 quarantine: std::env::var_os("LUA_RS_GC_QUARANTINE").is_some_and(|v| v == "1"),
1607 quarantined: Cell::new(None),
1608 uncollected: Cell::new(None),
1609 bootstrap_depth: std::rc::Rc::new(Cell::new(0)),
1610 pause_multiplier: Cell::new(200), state: Cell::new(GcState::Pause),
1612 paused: Cell::new(true), collections: Cell::new(0),
1614 minor_collections: Cell::new(0),
1615 full_collections: Cell::new(0),
1616 last_mark_stats: Cell::new(MarkerStats::default()),
1617 last_sweep_stats: Cell::new(SweepStats::default()),
1618 grayagain: Cell::new(None),
1619 marker: RefCell::new(None),
1620 marker_pool: RefCell::new(None),
1621 sweep_prev_next: Cell::new(None),
1622 v51_udata_roster: RefCell::new(Vec::new()),
1623 })
1624 }
1625
1626 pub fn unpause(&self) {
1629 self.paused.set(false);
1630 }
1631
1632 pub fn is_paused(&self) -> bool {
1633 self.paused.get()
1634 }
1635
1636 pub fn begin_bootstrap(&self) {
1644 self.bootstrap_depth.set(
1645 self.bootstrap_depth
1646 .get()
1647 .checked_add(1)
1648 .expect("Heap bootstrap depth overflow"),
1649 );
1650 }
1651
1652 pub fn end_bootstrap(&self) {
1655 let depth = self.bootstrap_depth.get();
1656 debug_assert!(depth > 0, "Heap::end_bootstrap without begin_bootstrap");
1657 self.bootstrap_depth.set(depth.saturating_sub(1));
1658 }
1659
1660 pub fn is_bootstrapping(&self) -> bool {
1661 self.bootstrap_depth.get() != 0
1662 }
1663
1664 pub fn bootstrap_scope(&self) -> BootstrapScope {
1671 self.begin_bootstrap();
1672 BootstrapScope {
1673 depth: std::rc::Rc::clone(&self.bootstrap_depth),
1674 }
1675 }
1676
1677 pub fn allocate<T: Trace + 'static>(&self, value: T) -> Gc<T> {
1681 if self.is_bootstrapping() {
1682 return self.allocate_uncollected(value);
1683 }
1684 let size = std::mem::size_of::<GcBox<T>>();
1685 let boxed = Box::new(GcBox {
1686 header: GcHeader::new_white(
1687 size,
1688 self.current_white.get(),
1689 HDR_COLLECTED | HDR_HEAP_OWNED,
1690 ),
1691 value,
1692 });
1693 boxed.header.next.set(self.head.get());
1694 let raw: *mut GcBox<T> = Box::into_raw(boxed);
1695 let ptr: NonNull<GcBox<T>> = NonNull::new(raw).expect("Box::into_raw is non-null");
1696 let dyn_ptr: NonNull<GcBox<dyn Trace>> = ptr;
1697 self.head.set(Some(dyn_ptr));
1698 self.bytes.set(self.bytes.get() + size);
1699 self.objects.set(self.objects.get() + 1);
1700 Gc {
1701 ptr,
1702 _marker: PhantomData,
1703 }
1704 }
1705
1706 pub fn allocate_uncollected<T: Trace + 'static>(&self, value: T) -> Gc<T> {
1717 let size = std::mem::size_of::<GcBox<T>>();
1718 let boxed = Box::new(GcBox {
1719 header: GcHeader::new_white(size, self.current_white.get(), HDR_HEAP_OWNED),
1720 value,
1721 });
1722 boxed.header.next.set(self.uncollected.get());
1723 let raw: *mut GcBox<T> = Box::into_raw(boxed);
1724 let ptr: NonNull<GcBox<T>> = NonNull::new(raw).expect("Box::into_raw is non-null");
1725 let dyn_ptr: NonNull<GcBox<dyn Trace>> = ptr;
1726 self.uncollected.set(Some(dyn_ptr));
1727 Gc {
1728 ptr,
1729 _marker: PhantomData,
1730 }
1731 }
1732
1733 pub fn bytes_used(&self) -> usize {
1735 self.bytes.get()
1736 }
1737
1738 pub fn adjust_bytes(&self, delta: isize) {
1743 if delta >= 0 {
1744 self.bytes
1745 .set(self.bytes.get().saturating_add(delta as usize));
1746 } else {
1747 self.bytes
1748 .set(self.bytes.get().saturating_sub((-delta) as usize));
1749 }
1750 }
1751
1752 pub fn threshold_bytes(&self) -> usize {
1757 self.threshold.get()
1758 }
1759
1760 pub fn set_threshold_bytes(&self, threshold: usize) {
1766 self.threshold.set(threshold.max(1));
1767 }
1768
1769 pub fn would_collect(&self) -> bool {
1773 if self.stress {
1774 return !self.paused.get();
1775 }
1776 !self.paused.get() && self.bytes.get() >= self.threshold.get()
1777 }
1778
1779 pub fn collections(&self) -> usize {
1780 self.collections.get()
1781 }
1782
1783 pub fn minor_collections(&self) -> usize {
1784 self.minor_collections.get()
1785 }
1786
1787 pub fn full_collections(&self) -> usize {
1788 self.full_collections.get()
1789 }
1790
1791 pub fn last_mark_stats(&self) -> MarkerStats {
1792 self.last_mark_stats.get()
1793 }
1794
1795 pub fn last_sweep_stats(&self) -> SweepStats {
1796 self.last_sweep_stats.get()
1797 }
1798
1799 pub fn allgc_cohort_stats(&self) -> AllGcCohortStats {
1800 let survival = self.survival.get();
1801 let old1 = self.old1.get();
1802 let reallyold = self.reallyold.get();
1803 let mut stats = AllGcCohortStats::default();
1804 let mut cursor = self.head.get();
1805 let mut seen = IdentityHashSet::default();
1806 let mut cohort = 0u8;
1807 while let Some(ptr) = cursor {
1808 let id = ptr.as_ptr() as *const () as usize;
1809 if !seen.insert(id) {
1810 break;
1811 }
1812 if Some(ptr) == reallyold {
1813 cohort = 3;
1814 } else if Some(ptr) == old1 {
1815 cohort = 2;
1816 } else if Some(ptr) == survival {
1817 cohort = 1;
1818 }
1819 match cohort {
1820 0 => stats.new += 1,
1821 1 => stats.survival += 1,
1822 2 => stats.old1 += 1,
1823 _ => stats.old += 1,
1824 }
1825 cursor = self.header_from_ptr(ptr).next.get();
1826 }
1827 stats
1828 }
1829
1830 fn next_token(&self) -> usize {
1831 let token = self.next_allocation_token.get().max(1);
1832 let next = token.checked_add(1).unwrap_or(1).max(1);
1833 self.next_allocation_token.set(next);
1834 token
1835 }
1836
1837 fn current_white(&self) -> Color {
1838 self.current_white.get()
1839 }
1840
1841 fn other_white(&self) -> Color {
1842 self.current_white.get().other_white()
1843 }
1844
1845 fn flip_current_white(&self) {
1846 self.current_white.set(self.other_white());
1847 }
1848
1849 fn for_each_list_header(
1850 &self,
1851 head: Option<NonNull<GcBox<dyn Trace>>>,
1852 f: &mut impl FnMut(&GcHeader),
1853 ) {
1854 let mut cursor = head;
1855 while let Some(ptr) = cursor {
1856 let header = self.header_from_ptr(ptr);
1857 cursor = header.next.get();
1858 f(header);
1859 }
1860 }
1861
1862 fn for_each_header(&self, mut f: impl FnMut(&GcHeader)) {
1863 self.for_each_list_header(self.head.get(), &mut f);
1864 self.for_each_list_header(self.finobj.get(), &mut f);
1865 self.for_each_list_header(self.tobefnz.get(), &mut f);
1866 }
1867
1868 fn header_from_ptr<'a>(&'a self, ptr: NonNull<GcBox<dyn Trace>>) -> &'a GcHeader {
1869 unsafe { &(*ptr.as_ptr()).header }
1870 }
1871
1872 fn release_box(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1879 if self.quarantine {
1880 let header = self.header_from_ptr(ptr);
1881 header.set_flag(HDR_FREED, true);
1882 header.next.set(self.quarantined.get());
1883 self.quarantined.set(Some(ptr));
1884 } else {
1885 unsafe {
1889 let _ = Box::from_raw(ptr.as_ptr());
1890 }
1891 }
1892 }
1893
1894 fn clear_generation_cursors(&self) {
1895 self.survival.set(None);
1896 self.old1.set(None);
1897 self.reallyold.set(None);
1898 self.firstold1.set(None);
1899 self.finobjsur.set(None);
1900 self.finobjold1.set(None);
1901 self.finobjrold.set(None);
1902 self.clear_grayagain();
1903 }
1904
1905 fn set_all_cursors_to_head(&self) {
1906 let head = self.head.get();
1907 self.survival.set(head);
1908 self.old1.set(head);
1909 self.reallyold.set(head);
1910 self.firstold1.set(None);
1911 let finobj = self.finobj.get();
1912 self.finobjsur.set(finobj);
1913 self.finobjold1.set(finobj);
1914 self.finobjrold.set(finobj);
1915 self.clear_grayagain();
1916 }
1917
1918 fn correct_generation_pointers(
1919 &self,
1920 removed: NonNull<GcBox<dyn Trace>>,
1921 next: Option<NonNull<GcBox<dyn Trace>>>,
1922 ) {
1923 if self.survival.get() == Some(removed) {
1924 self.survival.set(next);
1925 }
1926 if self.old1.get() == Some(removed) {
1927 self.old1.set(next);
1928 }
1929 if self.reallyold.get() == Some(removed) {
1930 self.reallyold.set(next);
1931 }
1932 if self.firstold1.get() == Some(removed) {
1933 self.firstold1.set(next);
1934 }
1935 if self.finobjsur.get() == Some(removed) {
1936 self.finobjsur.set(next);
1937 }
1938 if self.finobjold1.get() == Some(removed) {
1939 self.finobjold1.set(next);
1940 }
1941 if self.finobjrold.get() == Some(removed) {
1942 self.finobjrold.set(next);
1943 }
1944 if self.header_from_ptr(removed).gray_listed() {
1945 self.unlink_grayagain(removed);
1946 }
1947 }
1948
1949 fn unlink_from_list(
1950 &self,
1951 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1952 ptr: NonNull<GcBox<dyn Trace>>,
1953 ) -> bool {
1954 let mut prev_cell = list;
1955 loop {
1956 let Some(current) = prev_cell.get() else {
1957 return false;
1958 };
1959 let header = self.header_from_ptr(current);
1960 let next = header.next.get();
1961 if std::ptr::addr_eq(current.as_ptr(), ptr.as_ptr()) {
1962 prev_cell.set(next);
1963 let prev_next_ptr = NonNull::from(prev_cell);
1964 let removed_next_ptr = NonNull::from(&self.header_from_ptr(ptr).next);
1965 if self.sweep_prev_next.get() == Some(removed_next_ptr) {
1966 self.sweep_prev_next.set(Some(prev_next_ptr));
1967 }
1968 self.correct_generation_pointers(ptr, next);
1969 header.next.set(None);
1970 return true;
1971 }
1972 prev_cell = &header.next;
1973 }
1974 }
1975
1976 fn link_to_head(
1977 &self,
1978 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1979 ptr: NonNull<GcBox<dyn Trace>>,
1980 ) {
1981 let header = self.header_from_ptr(ptr);
1982 header.next.set(list.get());
1983 list.set(Some(ptr));
1984 }
1985
1986 fn link_to_tail(
1987 &self,
1988 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1989 ptr: NonNull<GcBox<dyn Trace>>,
1990 ) {
1991 let mut last_cell = list;
1992 loop {
1993 let Some(current) = last_cell.get() else {
1994 let header = self.header_from_ptr(ptr);
1995 header.next.set(None);
1996 last_cell.set(Some(ptr));
1997 return;
1998 };
1999 last_cell = &self.header_from_ptr(current).next;
2000 }
2001 }
2002
2003 pub fn move_allgc_to_finobj(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
2004 let header = self.header_from_ptr(ptr);
2005 if !header.collected() {
2006 return false;
2007 }
2008 if !self.unlink_from_list(&self.head, ptr) {
2009 return false;
2010 }
2011 if self.state.get().is_sweep() {
2012 header.color.set(self.current_white());
2013 }
2014 self.link_to_head(&self.finobj, ptr);
2015 true
2016 }
2017
2018 pub fn move_finobj_to_tobefnz(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
2019 if !self.unlink_from_list(&self.finobj, ptr) {
2020 return false;
2021 }
2022 self.link_to_tail(&self.tobefnz, ptr);
2023 true
2024 }
2025
2026 pub fn move_tobefnz_to_allgc(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
2027 let header = self.header_from_ptr(ptr);
2028 if !self.unlink_from_list(&self.tobefnz, ptr) {
2029 return false;
2030 }
2031 if self.state.get().is_sweep() {
2032 header.color.set(self.current_white());
2033 }
2034 self.link_to_head(&self.head, ptr);
2035 if header.age.get() == GcAge::Old1 {
2036 self.firstold1.set(Some(ptr));
2037 }
2038 true
2039 }
2040
2041 fn remember_minor_revisit(&self, ptr: NonNull<GcBox<dyn Trace>>) {
2042 let header = self.header_from_ptr(ptr);
2043 if header.gray_listed() {
2044 return;
2045 }
2046 header.gray_next.set(self.grayagain.get());
2047 header.set_gray_listed(true);
2048 self.grayagain.set(Some(ptr));
2049 }
2050
2051 fn mark_minor_revisit_objects(&self, marker: &mut Marker) {
2052 let mut cursor = self.grayagain.get();
2053 while let Some(ptr) = cursor {
2054 let header = self.header_from_ptr(ptr);
2055 cursor = header.gray_next.get();
2056 let id = ptr.as_ptr() as *const () as usize;
2057 marker.mark_box(ptr, header, id);
2058 }
2059 }
2060
2061 fn clear_grayagain(&self) {
2062 let mut cursor = self.grayagain.get();
2063 self.grayagain.set(None);
2064 while let Some(ptr) = cursor {
2065 let header = self.header_from_ptr(ptr);
2066 cursor = header.gray_next.get();
2067 header.gray_next.set(None);
2068 header.set_gray_listed(false);
2069 }
2070 }
2071
2072 fn take_grayagain(&self) -> Vec<NonNull<GcBox<dyn Trace>>> {
2073 let mut objects = Vec::new();
2074 let mut cursor = self.grayagain.get();
2075 self.grayagain.set(None);
2076 while let Some(ptr) = cursor {
2077 let header = self.header_from_ptr(ptr);
2078 cursor = header.gray_next.get();
2079 header.gray_next.set(None);
2080 header.set_gray_listed(false);
2081 objects.push(ptr);
2082 }
2083 objects
2084 }
2085
2086 fn replace_grayagain(&self, objects: Vec<NonNull<GcBox<dyn Trace>>>) {
2087 self.clear_grayagain();
2088 for ptr in objects.into_iter().rev() {
2089 self.remember_minor_revisit(ptr);
2090 }
2091 }
2092
2093 fn unlink_grayagain(&self, removed: NonNull<GcBox<dyn Trace>>) {
2094 let keep = self
2095 .take_grayagain()
2096 .into_iter()
2097 .filter(|ptr| !std::ptr::addr_eq(ptr.as_ptr(), removed.as_ptr()))
2098 .collect();
2099 self.replace_grayagain(keep);
2100 }
2101
2102 pub fn grayagain_count(&self) -> usize {
2103 let mut count = 0usize;
2104 let mut cursor = self.grayagain.get();
2105 while let Some(ptr) = cursor {
2106 count += 1;
2107 cursor = self.header_from_ptr(ptr).gray_next.get();
2108 }
2109 count
2110 }
2111
2112 pub fn register_v51_udata(&self, probe: std::rc::Rc<dyn Udata51Probe>) {
2119 self.v51_udata_roster.borrow_mut().push(probe);
2120 }
2121
2122 pub fn scan_v51_finalizable(&self) -> Vec<std::rc::Rc<dyn Udata51Probe>> {
2132 let mut roster = self.v51_udata_roster.borrow_mut();
2133 roster.retain(|probe| probe.is_alive());
2134 roster.clone()
2135 }
2136
2137 pub fn allocation_token(&self, identity: usize) -> Option<usize> {
2145 self.allocation_tokens.borrow().get(&identity).copied()
2146 }
2147
2148 pub fn register_allocation_token(&self, identity: usize) -> usize {
2166 let mut tokens = self.allocation_tokens.borrow_mut();
2167 if let Some(token) = tokens.get(&identity) {
2168 return *token;
2169 }
2170 let token = self.next_token();
2171 tokens.insert(identity, token);
2172 token
2173 }
2174
2175 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
2180 self.allocation_token(identity) == Some(token)
2181 }
2182
2183 pub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2193 where
2194 P: Trace + 'static,
2195 C: Trace + 'static,
2196 {
2197 if self.paused.get() || self.state.get().is_pause() {
2198 return;
2199 }
2200 if parent.header().color.get() != Color::Black {
2201 return;
2202 }
2203 if !child.header().color.get().is_white() {
2204 return;
2205 }
2206 child.header().color.set(Color::Gray);
2207 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
2208 if let Some(m) = m_opt.as_mut() {
2209 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
2210 m.gray_queue.push(ptr);
2211 m.visited.insert(child.identity());
2212 }
2213 }
2214 }
2215
2216 pub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2219 where
2220 P: Trace + 'static,
2221 C: Trace + 'static,
2222 {
2223 if self.paused.get() || self.state.get().is_pause() {
2224 return;
2225 }
2226 if parent.header().color.get() != Color::Black {
2227 return;
2228 }
2229 if !child.header().color.get().is_white() {
2230 return;
2231 }
2232 parent.header().color.set(Color::Gray);
2233 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
2234 if let Some(m) = m_opt.as_mut() {
2235 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
2236 m.gray_queue.push(ptr);
2237 m.visited.insert(parent.identity());
2238 }
2239 }
2240 }
2241
2242 pub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2247 where
2248 P: Trace + 'static,
2249 C: Trace + 'static,
2250 {
2251 if parent.age().is_old() && !child.age().is_old() {
2252 child.set_age(GcAge::Old0);
2253 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
2254 self.remember_minor_revisit(ptr);
2255 }
2256 self.barrier(parent, child);
2257 }
2258
2259 pub fn generational_backward_barrier<P>(&self, parent: Gc<P>)
2263 where
2264 P: Trace + 'static,
2265 {
2266 if parent.age().is_old() {
2267 parent.set_color(Color::Gray);
2268 parent.set_age(GcAge::Touched1);
2269 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
2270 self.remember_minor_revisit(ptr);
2271 }
2272 }
2273
2274 pub fn step(&self, roots: &dyn Trace) {
2278 self.step_with_post_mark(roots, |_: &mut Marker| {});
2279 }
2280
2281 pub fn step_with_post_mark<F: FnMut(&mut Marker)>(&self, roots: &dyn Trace, post_mark: F) {
2286 if self.paused.get() {
2287 return;
2288 }
2289 if !self.stress && self.bytes.get() < self.threshold.get() {
2290 return;
2291 }
2292 self.full_collect_with_post_mark(roots, post_mark);
2293 }
2294
2295 pub fn full_collect(&self, roots: &dyn Trace) {
2298 self.full_collect_with_post_mark(roots, |_: &mut Marker| {});
2299 }
2300
2301 pub fn mark_only_with_post_mark<F: FnMut(&mut Marker)>(
2306 &self,
2307 roots: &dyn Trace,
2308 mut post_mark: F,
2309 ) {
2310 if self.paused.get() {
2311 return;
2312 }
2313 let mut marker = self.marker_from_pool(MarkerMode::Full);
2314 roots.trace(&mut marker);
2315 marker.drain_gray_queue();
2316 post_mark(&mut marker);
2317 marker.drain_gray_queue();
2318 self.last_mark_stats.set(marker.stats());
2319 self.recycle_marker(marker);
2320 }
2321
2322 pub fn promote_all_to_old(&self) {
2325 self.for_each_header(|header| {
2326 header.age.set(GcAge::Old);
2327 header.color.set(Color::Black);
2328 });
2329 self.set_all_cursors_to_head();
2330 }
2331
2332 pub fn reset_all_ages(&self) {
2335 let current_white = self.current_white();
2336 self.for_each_header(|header| {
2337 header.age.set(GcAge::New);
2338 header.color.set(current_white);
2339 });
2340 self.clear_generation_cursors();
2341 }
2342
2343 pub fn minor_collect_with_post_mark<F: FnMut(&mut Marker)>(
2350 &self,
2351 roots: &dyn Trace,
2352 mut post_mark: F,
2353 ) {
2354 if self.paused.get() {
2355 return;
2356 }
2357 if !self.state.get().is_pause() {
2358 self.abort_cycle();
2359 }
2360
2361 self.state.set(GcState::Propagate);
2362 let mut marker = self.marker_from_pool(MarkerMode::Minor);
2363 self.last_sweep_stats.set(SweepStats::default());
2364 self.mark_minor_revisit_objects(&mut marker);
2365 roots.trace(&mut marker);
2366 marker.drain_gray_queue();
2367
2368 self.state.set(GcState::EnterAtomic);
2369 self.state.set(GcState::Atomic);
2370 post_mark(&mut marker);
2371 marker.drain_gray_queue();
2372 self.last_mark_stats.set(marker.stats());
2373 self.recycle_marker(marker);
2374
2375 self.state.set(GcState::SweepAllGc);
2376 self.sweep_young();
2377 self.recycle_marker_cell();
2378 self.sweep_prev_next.set(None);
2379 self.state.set(GcState::Pause);
2380 self.collections.set(self.collections.get() + 1);
2381 self.minor_collections.set(self.minor_collections.get() + 1);
2382 }
2383
2384 pub fn full_collect_with_post_mark<F: FnMut(&mut Marker)>(
2391 &self,
2392 roots: &dyn Trace,
2393 mut post_mark: F,
2394 ) {
2395 if self.paused.get() {
2396 return;
2397 }
2398 if !self.state.get().is_pause() {
2399 self.abort_cycle();
2400 }
2401 self.full_collections.set(self.full_collections.get() + 1);
2402 let unlimited = StepBudget {
2403 remaining_work: isize::MAX,
2404 max_credit: isize::MAX,
2405 };
2406 loop {
2407 let outcome = self.incremental_step_with_post_mark(roots, unlimited, &mut post_mark);
2408 if matches!(outcome, StepOutcome::Paused | StepOutcome::SkippedStopped) {
2409 break;
2410 }
2411 }
2412 }
2413
2414 pub fn incremental_step_with_post_mark<F: FnMut(&mut Marker)>(
2429 &self,
2430 roots: &dyn Trace,
2431 mut budget: StepBudget,
2432 mut post_mark: F,
2433 ) -> StepOutcome {
2434 if self.paused.get() {
2435 return StepOutcome::SkippedStopped;
2436 }
2437 self.run_budgeted(roots, &mut budget, &mut post_mark);
2438 if self.state.get().is_pause() {
2439 StepOutcome::Paused
2440 } else {
2441 StepOutcome::InProgress
2442 }
2443 }
2444
2445 fn run_budgeted(
2446 &self,
2447 roots: &dyn Trace,
2448 budget: &mut StepBudget,
2449 post_mark: &mut dyn FnMut(&mut Marker),
2450 ) -> bool {
2451 self.run_budgeted_until(roots, budget, post_mark, None)
2452 }
2453
2454 fn run_budgeted_until(
2455 &self,
2456 roots: &dyn Trace,
2457 budget: &mut StepBudget,
2458 post_mark: &mut dyn FnMut(&mut Marker),
2459 stop_at: Option<GcState>,
2460 ) -> bool {
2461 let mut did_work = false;
2462 loop {
2463 if stop_at == Some(self.state.get()) {
2464 return did_work;
2465 }
2466 if budget.remaining_work <= -budget.max_credit {
2467 return did_work;
2468 }
2469 match self.state.get() {
2470 GcState::Pause => {
2471 self.start_cycle(roots);
2472 self.state.set(GcState::Propagate);
2473 budget.remaining_work -= 1;
2474 did_work = true;
2475 if stop_at == Some(GcState::Propagate) {
2476 return did_work;
2477 }
2478 }
2479 GcState::Propagate => {
2480 let work = self.drain_gray_budgeted(budget.remaining_work.max(1));
2481 budget.remaining_work -= work as isize;
2482 did_work = did_work || work > 0;
2483 let empty = {
2484 let m = self.marker.borrow();
2485 m.as_ref().map(|m| m.gray_queue.is_empty()).unwrap_or(true)
2486 };
2487 if empty {
2488 self.state.set(GcState::EnterAtomic);
2489 if stop_at == Some(GcState::EnterAtomic) {
2490 return did_work;
2491 }
2492 } else if budget.remaining_work <= 0 {
2493 return did_work;
2494 }
2495 }
2496 GcState::EnterAtomic => {
2497 self.state.set(GcState::Atomic);
2498 budget.remaining_work -= 1;
2499 did_work = true;
2500 if stop_at == Some(GcState::Atomic) || budget.remaining_work <= 0 {
2501 return did_work;
2502 }
2503 }
2504 GcState::Atomic => {
2505 self.run_atomic(post_mark);
2506 self.state.set(GcState::SweepAllGc);
2507 budget.remaining_work -= 1;
2508 did_work = true;
2509 if stop_at == Some(GcState::SweepAllGc) {
2510 return did_work;
2511 }
2512 }
2513 GcState::SweepAllGc => {
2514 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2515 budget.remaining_work -= work as isize;
2516 did_work = did_work || work > 0;
2517 if self.sweep_prev_next.get().is_none() {
2518 self.state.set(GcState::SweepFinObj);
2519 self.sweep_prev_next.set(Some(NonNull::from(&self.finobj)));
2520 if stop_at == Some(GcState::SweepFinObj) {
2521 return did_work;
2522 }
2523 } else if budget.remaining_work <= 0 {
2524 return did_work;
2525 }
2526 }
2527 GcState::SweepFinObj => {
2528 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2529 budget.remaining_work -= work as isize;
2530 did_work = did_work || work > 0;
2531 if self.sweep_prev_next.get().is_none() {
2532 self.state.set(GcState::SweepToBeFnz);
2533 self.sweep_prev_next.set(Some(NonNull::from(&self.tobefnz)));
2534 if stop_at == Some(GcState::SweepToBeFnz) {
2535 return did_work;
2536 }
2537 } else if budget.remaining_work <= 0 {
2538 return did_work;
2539 }
2540 }
2541 GcState::SweepToBeFnz => {
2542 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2543 budget.remaining_work -= work as isize;
2544 did_work = did_work || work > 0;
2545 if self.sweep_prev_next.get().is_none() {
2546 self.state.set(GcState::SweepEnd);
2547 if stop_at == Some(GcState::SweepEnd) {
2548 return did_work;
2549 }
2550 } else if budget.remaining_work <= 0 {
2551 return did_work;
2552 }
2553 }
2554 GcState::SweepEnd => {
2555 self.state.set(GcState::CallFin);
2556 budget.remaining_work -= 1;
2557 did_work = true;
2558 if stop_at == Some(GcState::CallFin) || budget.remaining_work <= 0 {
2559 return did_work;
2560 }
2561 }
2562 GcState::CallFin => {
2563 self.finish_cycle();
2564 self.state.set(GcState::Pause);
2565 if stop_at == Some(GcState::Pause) {
2566 return did_work;
2567 }
2568 return did_work;
2569 }
2570 }
2571 }
2572 }
2573
2574 pub fn incremental_run_until_state_with_post_mark<F: FnMut(&mut Marker)>(
2579 &self,
2580 roots: &dyn Trace,
2581 target: GcState,
2582 max_work: isize,
2583 mut post_mark: F,
2584 ) -> StepOutcome {
2585 if self.paused.get() {
2586 return StepOutcome::SkippedStopped;
2587 }
2588 let work = max_work.max(1);
2589 let mut budget = StepBudget {
2590 remaining_work: work,
2591 max_credit: work,
2592 };
2593 self.run_budgeted_until(roots, &mut budget, &mut post_mark, Some(target));
2594 if self.state.get().is_pause() {
2595 StepOutcome::Paused
2596 } else {
2597 StepOutcome::InProgress
2598 }
2599 }
2600
2601 fn marker_from_pool(&self, mode: MarkerMode) -> Marker {
2604 match self.marker_pool.borrow_mut().take() {
2605 Some((gray_queue, visited)) => Marker {
2606 gray_queue,
2607 visited,
2608 stats: MarkerStats::default(),
2609 mode,
2610 },
2611 None => Marker::new_with_capacity(mode, self.objects.get()),
2612 }
2613 }
2614
2615 fn recycle_marker(&self, marker: Marker) {
2616 let Marker {
2617 mut gray_queue,
2618 mut visited,
2619 ..
2620 } = marker;
2621 gray_queue.clear();
2622 visited.clear();
2623 *self.marker_pool.borrow_mut() = Some((gray_queue, visited));
2624 }
2625
2626 fn recycle_marker_cell(&self) {
2627 if let Some(marker) = self.marker.borrow_mut().take() {
2628 self.recycle_marker(marker);
2629 }
2630 }
2631
2632 fn start_cycle(&self, roots: &dyn Trace) {
2633 self.flip_current_white();
2634 let dead_white = self.other_white();
2635 self.for_each_header(|header| {
2636 header.color.set(dead_white);
2637 });
2638 let mut marker = self.marker_from_pool(MarkerMode::Full);
2639 roots.trace(&mut marker);
2640 *self.marker.borrow_mut() = Some(marker);
2641 self.sweep_prev_next.set(None);
2642 }
2643
2644 fn drain_gray_budgeted(&self, max_units: isize) -> usize {
2645 let mut m_opt = self.marker.borrow_mut();
2646 let marker = match m_opt.as_mut() {
2647 Some(m) => m,
2648 None => return 0,
2649 };
2650 let mut work = 0usize;
2651 let mut budget = max_units;
2652 while budget > 0 {
2653 let next = match marker.gray_queue.pop() {
2654 Some(p) => p,
2655 None => break,
2656 };
2657 unsafe {
2658 let bx = next.as_ref();
2659 marker.stats.traced += 1;
2660 if bx.header.age.get().is_old() {
2661 marker.stats.traced_old += 1;
2662 } else {
2663 marker.stats.traced_young += 1;
2664 }
2665 bx.header.color.set(Color::Black);
2666 bx.value.trace(marker);
2667 }
2668 work += 1;
2669 budget -= 1;
2670 }
2671 work
2672 }
2673
2674 fn run_atomic(&self, post_mark: &mut dyn FnMut(&mut Marker)) {
2675 let mut m_opt = self.marker.borrow_mut();
2676 if let Some(marker) = m_opt.as_mut() {
2677 marker.drain_gray_queue();
2678 post_mark(marker);
2679 marker.drain_gray_queue();
2680 }
2681 self.sweep_prev_next.set(Some(NonNull::from(&self.head)));
2682 self.last_sweep_stats.set(SweepStats::default());
2683 }
2684
2685 fn sweep_budgeted(&self, max_units: isize) -> usize {
2686 let mut work = 0usize;
2687 let mut budget = max_units;
2688 let mut freed_bytes = 0usize;
2689 let mut stats = SweepStats::default();
2690 let current_white = self.current_white();
2691 let dead_white = self.other_white();
2692 let mut prev_next_ptr = match self.sweep_prev_next.get() {
2693 Some(p) => p,
2694 None => return 0,
2695 };
2696 while budget > 0 {
2697 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2698 let cursor = prev_cell.get();
2699 let ptr = match cursor {
2700 Some(p) => p,
2701 None => {
2702 self.sweep_prev_next.set(None);
2703 break;
2704 }
2705 };
2706 let header = self.header_from_ptr(ptr);
2707 let next = header.next.get();
2708 let age = header.age.get();
2709 stats.record_visit(age);
2710 let color = header.color.get();
2711 if color == dead_white {
2712 prev_cell.set(next);
2713 let size = header.size();
2714 freed_bytes += size;
2715 stats.record_free(size);
2716 self.correct_generation_pointers(ptr, next);
2717 self.allocation_tokens
2718 .borrow_mut()
2719 .remove(&(ptr.as_ptr() as *const () as usize));
2720 self.objects.set(self.objects.get().saturating_sub(1));
2721 self.release_box(ptr);
2722 } else {
2723 if matches!(color, Color::Black | Color::Gray) {
2724 header.color.set(current_white);
2725 }
2726 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2727 self.sweep_prev_next.set(Some(prev_next_ptr));
2728 }
2729 work += 1;
2730 budget -= 1;
2731 }
2732 if freed_bytes > 0 {
2733 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2734 }
2735 if stats.visited > 0 {
2736 let mut total = self.last_sweep_stats.get();
2737 total.add(stats);
2738 self.last_sweep_stats.set(total);
2739 }
2740 work
2741 }
2742
2743 fn push_next_revisit(
2744 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2745 seen: &mut IdentityHashSet,
2746 ptr: NonNull<GcBox<dyn Trace>>,
2747 age: GcAge,
2748 ) {
2749 if matches!(
2750 age,
2751 GcAge::Old0 | GcAge::Old1 | GcAge::Touched1 | GcAge::Touched2
2752 ) {
2753 let id = ptr.as_ptr() as *const () as usize;
2754 if seen.insert(id) {
2755 next_revisit.push(ptr);
2756 }
2757 }
2758 }
2759
2760 fn sweep_young_range(
2761 &self,
2762 mut prev_next_ptr: NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2763 limit: Option<NonNull<GcBox<dyn Trace>>>,
2764 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2765 next_revisit_seen: &mut IdentityHashSet,
2766 processed: &mut Option<OldRevisitTracker>,
2767 firstold1: &mut Option<NonNull<GcBox<dyn Trace>>>,
2768 freed_bytes: &mut usize,
2769 stats: &mut SweepStats,
2770 ) -> (
2771 NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2772 Option<NonNull<GcBox<dyn Trace>>>,
2773 ) {
2774 let current_white = self.current_white();
2775 loop {
2776 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2777 let Some(ptr) = prev_cell.get() else {
2778 return (prev_next_ptr, None);
2779 };
2780 if Some(ptr) == limit {
2781 return (prev_next_ptr, Some(ptr));
2782 }
2783 let header = self.header_from_ptr(ptr);
2784 let next = header.next.get();
2785 let age = header.age.get();
2786 stats.record_visit(age);
2787 if let Some(processed) = processed.as_mut() {
2788 processed.record_processed(ptr.as_ptr() as *const () as usize);
2789 }
2790 if header.color.get().is_white() && !age.is_old() {
2791 prev_cell.set(next);
2792 let size = header.size();
2793 *freed_bytes += size;
2794 stats.record_free(size);
2795 self.correct_generation_pointers(ptr, next);
2796 self.allocation_tokens
2797 .borrow_mut()
2798 .remove(&(ptr.as_ptr() as *const () as usize));
2799 self.objects.set(self.objects.get().saturating_sub(1));
2800 self.release_box(ptr);
2801 continue;
2802 }
2803
2804 if !header.color.get().is_white() {
2805 let next_age = age.next_after_minor();
2806 header.age.set(next_age);
2807 if next_age == GcAge::Old1 && firstold1.is_none() {
2808 *firstold1 = Some(ptr);
2809 }
2810 match age {
2811 GcAge::New => header.color.set(current_white),
2812 GcAge::Touched1 | GcAge::Touched2 => header.color.set(Color::Black),
2813 _ => {}
2814 }
2815 Self::push_next_revisit(next_revisit, next_revisit_seen, ptr, next_age);
2816 }
2817 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2818 }
2819 }
2820
2821 fn sweep_young(&self) {
2822 let mut freed_bytes = 0usize;
2823 let mut next_revisit = Vec::new();
2824 let mut next_revisit_seen = IdentityHashSet::default();
2825 let mut firstold1 = None;
2826 let mut stats = SweepStats::default();
2827 let old_revisit = self.take_grayagain();
2828 let mut processed = OldRevisitTracker::new(&old_revisit);
2829 let survival = self.survival.get();
2830 let old1 = self.old1.get();
2831
2832 let (psurvival, new_old1) = self.sweep_young_range(
2833 NonNull::from(&self.head),
2834 survival,
2835 &mut next_revisit,
2836 &mut next_revisit_seen,
2837 &mut processed,
2838 &mut firstold1,
2839 &mut freed_bytes,
2840 &mut stats,
2841 );
2842 self.sweep_young_range(
2843 psurvival,
2844 old1,
2845 &mut next_revisit,
2846 &mut next_revisit_seen,
2847 &mut processed,
2848 &mut firstold1,
2849 &mut freed_bytes,
2850 &mut stats,
2851 );
2852
2853 let finobjsur = self.finobjsur.get();
2854 let finobjold1 = self.finobjold1.get();
2855 let mut dummy_firstold1 = None;
2856 let (pfinobjsur, new_finobjold1) = self.sweep_young_range(
2857 NonNull::from(&self.finobj),
2858 finobjsur,
2859 &mut next_revisit,
2860 &mut next_revisit_seen,
2861 &mut processed,
2862 &mut dummy_firstold1,
2863 &mut freed_bytes,
2864 &mut stats,
2865 );
2866 self.sweep_young_range(
2867 pfinobjsur,
2868 finobjold1,
2869 &mut next_revisit,
2870 &mut next_revisit_seen,
2871 &mut processed,
2872 &mut dummy_firstold1,
2873 &mut freed_bytes,
2874 &mut stats,
2875 );
2876 self.sweep_young_range(
2877 NonNull::from(&self.tobefnz),
2878 None,
2879 &mut next_revisit,
2880 &mut next_revisit_seen,
2881 &mut processed,
2882 &mut dummy_firstold1,
2883 &mut freed_bytes,
2884 &mut stats,
2885 );
2886
2887 if let Some(processed) = processed.as_mut() {
2888 processed.finish();
2889 }
2890
2891 for ptr in old_revisit {
2892 let id = ptr.as_ptr() as *const () as usize;
2893 if processed
2894 .as_ref()
2895 .is_some_and(|processed| processed.was_processed(id))
2896 {
2897 continue;
2898 }
2899 stats.revisit += 1;
2900 let header = self.header_from_ptr(ptr);
2901 if header.color.get().is_white() {
2902 continue;
2903 }
2904 let age = header.age.get();
2905 let next_age = age.next_after_minor();
2906 header.age.set(next_age);
2907 if next_age == GcAge::Old1 && firstold1.is_none() {
2908 firstold1 = Some(ptr);
2909 }
2910 if matches!(age, GcAge::Touched1 | GcAge::Touched2) {
2911 header.color.set(Color::Black);
2912 }
2913 Self::push_next_revisit(&mut next_revisit, &mut next_revisit_seen, ptr, next_age);
2914 }
2915
2916 if freed_bytes > 0 {
2917 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2918 }
2919 self.replace_grayagain(next_revisit);
2920 self.reallyold.set(old1);
2921 self.old1.set(new_old1);
2922 self.survival.set(self.head.get());
2923 self.firstold1.set(firstold1);
2924 self.finobjrold.set(finobjold1);
2925 self.finobjold1.set(new_finobjold1);
2926 self.finobjsur.set(self.finobj.get());
2927 self.last_sweep_stats.set(stats);
2928 }
2929
2930 fn finish_cycle(&self) {
2931 let stats = self
2932 .marker
2933 .borrow()
2934 .as_ref()
2935 .map(|marker| marker.stats())
2936 .unwrap_or_default();
2937 self.last_mark_stats.set(stats);
2938 self.recycle_marker_cell();
2939 self.sweep_prev_next.set(None);
2940 let next = self.bytes.get().saturating_mul(self.pause_multiplier.get()) / 100;
2941 self.threshold.set(next.max(GC_MIN_THRESHOLD));
2942 self.collections.set(self.collections.get() + 1);
2943 }
2944
2945 pub fn finish_callfin_phase(&self) -> bool {
2948 if self.state.get() != GcState::CallFin {
2949 return false;
2950 }
2951 self.finish_cycle();
2952 self.state.set(GcState::Pause);
2953 true
2954 }
2955
2956 fn abort_cycle(&self) {
2957 if !self.state.get().is_pause() {
2958 self.recycle_marker_cell();
2959 self.sweep_prev_next.set(None);
2960 let current_white = self.current_white();
2961 self.for_each_header(|header| {
2962 header.color.set(current_white);
2963 });
2964 self.state.set(GcState::Pause);
2965 }
2966 }
2967
2968 pub fn gc_state(&self) -> GcState {
2970 self.state.get()
2971 }
2972
2973 pub fn allgc_count(&self) -> usize {
2975 self.objects.get()
2976 }
2977
2978 pub fn type_name_count(&self, mut predicate: impl FnMut(&'static str) -> bool) -> usize {
2982 let mut count = 0usize;
2983 for head in [self.head.get(), self.finobj.get(), self.tobefnz.get()] {
2984 let mut cursor = head;
2985 while let Some(ptr) = cursor {
2986 let bx = unsafe { ptr.as_ref() };
2987 cursor = bx.header.next.get();
2988 if predicate(bx.value().type_name()) {
2989 count += 1;
2990 }
2991 }
2992 }
2993 count
2994 }
2995
2996 pub fn drop_all(&self) {
3000 self.recycle_marker_cell();
3001 self.sweep_prev_next.set(None);
3002 self.clear_generation_cursors();
3003 self.state.set(GcState::Pause);
3004 self.allocation_tokens.borrow_mut().clear();
3005 self.drop_list(&self.head);
3006 self.drop_list(&self.finobj);
3007 self.drop_list(&self.tobefnz);
3008 self.drop_list(&self.quarantined);
3009 self.drop_list(&self.uncollected);
3010 self.bytes.set(0);
3011 self.objects.set(0);
3012 }
3013
3014 fn drop_list(&self, list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>) {
3015 let mut cursor = list.get();
3016 list.set(None);
3017 while let Some(ptr) = cursor {
3018 let next = unsafe {
3020 let next = (*ptr.as_ptr()).header.next.get();
3021 let _ = Box::from_raw(ptr.as_ptr());
3022 next
3023 };
3024 cursor = next;
3025 }
3026 }
3027}
3028
3029impl Drop for Heap {
3030 fn drop(&mut self) {
3031 self.drop_all();
3032 }
3033}
3034
3035#[cfg(test)]
3040mod tests {
3041 use super::*;
3042
3043 struct Cell0 {
3045 next: Cell<Option<Gc<Cell0>>>,
3046 marker_calls: Cell<usize>,
3047 }
3048
3049 impl Trace for Cell0 {
3050 fn trace(&self, m: &mut Marker) {
3051 self.marker_calls.set(self.marker_calls.get() + 1);
3052 if let Some(n) = self.next.get() {
3053 m.mark(n);
3054 }
3055 }
3056 }
3057
3058 struct OneRoot(Option<Gc<Cell0>>);
3060 impl Trace for OneRoot {
3061 fn trace(&self, m: &mut Marker) {
3062 if let Some(g) = self.0 {
3063 m.mark(g);
3064 }
3065 }
3066 }
3067
3068 struct TwoRoots {
3069 first: Option<Gc<Cell0>>,
3070 second: Option<Gc<Cell0>>,
3071 }
3072
3073 impl Trace for TwoRoots {
3074 fn trace(&self, m: &mut Marker) {
3075 if let Some(g) = self.first {
3076 m.mark(g);
3077 }
3078 if let Some(g) = self.second {
3079 m.mark(g);
3080 }
3081 }
3082 }
3083
3084 #[derive(Clone)]
3085 struct FinalizerCell {
3086 id: usize,
3087 age: GcAge,
3088 finalized: std::rc::Rc<Cell<bool>>,
3089 }
3090
3091 impl FinalizerCell {
3092 fn new(id: usize) -> Self {
3093 Self {
3094 id,
3095 age: GcAge::New,
3096 finalized: std::rc::Rc::new(Cell::new(false)),
3097 }
3098 }
3099 }
3100
3101 impl FinalizerEntry for FinalizerCell {
3102 fn identity(&self) -> usize {
3103 self.id
3104 }
3105
3106 fn age(&self) -> GcAge {
3107 self.age
3108 }
3109
3110 fn is_finalized(&self) -> bool {
3111 self.finalized.get()
3112 }
3113
3114 fn set_finalized(&self, finalized: bool) {
3115 self.finalized.set(finalized);
3116 }
3117 }
3118
3119 fn finalizer_ids(objects: &[FinalizerCell]) -> Vec<usize> {
3120 objects.iter().map(|object| object.id).collect()
3121 }
3122
3123 #[derive(Clone)]
3124 struct WeakCell {
3125 id: usize,
3126 live: bool,
3127 kind: WeakListKind,
3128 }
3129
3130 impl WeakEntry for WeakCell {
3131 type Strong = usize;
3132
3133 fn identity(&self) -> usize {
3134 self.id
3135 }
3136
3137 fn list_kind(&self) -> WeakListKind {
3138 self.kind
3139 }
3140
3141 fn upgrade(&self) -> Option<Self::Strong> {
3142 self.live.then_some(self.id)
3143 }
3144 }
3145
3146 #[test]
3147 fn weak_registry_dedups_snapshots_and_retains_live_ids() {
3148 let mut registry = WeakRegistry::default();
3149 registry.push_unique(WeakCell {
3150 id: 1,
3151 live: true,
3152 kind: WeakListKind::WeakValues,
3153 });
3154 registry.push_unique(WeakCell {
3155 id: 1,
3156 live: true,
3157 kind: WeakListKind::Ephemeron,
3158 });
3159 registry.push_unique(WeakCell {
3160 id: 2,
3161 live: false,
3162 kind: WeakListKind::AllWeak,
3163 });
3164 registry.push_unique(WeakCell {
3165 id: 3,
3166 live: true,
3167 kind: WeakListKind::WeakValues,
3168 });
3169
3170 let stats = registry.stats();
3171 assert_eq!(stats.weak_values, 1);
3172 assert_eq!(stats.ephemeron, 1);
3173 assert_eq!(stats.all_weak, 1);
3174
3175 let snapshot = registry.live_snapshot();
3176 assert_eq!(snapshot, vec![3, 1]);
3177 assert_eq!(
3178 registry.stats(),
3179 WeakRegistryStats {
3180 tracked: 3,
3181 snapshot_live: 2,
3182 snapshot_dead: 1,
3183 retained: 2,
3184 weak_values: 1,
3185 ephemeron: 1,
3186 all_weak: 0,
3187 }
3188 );
3189
3190 let keep: std::collections::HashSet<usize> = [3usize].into_iter().collect();
3191 registry.retain_identities(&keep);
3192 assert_eq!(registry.len(), 1);
3193 assert_eq!(registry.stats().retained, 1);
3194 assert_eq!(registry.stats().weak_values, 1);
3195 registry.remove_identity(3);
3196 assert_eq!(registry.len(), 0);
3197 }
3198
3199 #[test]
3200 fn finalizer_registry_tracks_generational_cohorts() {
3201 let mut registry = FinalizerRegistry::default();
3202 registry.push_pending_unique(FinalizerCell::new(1));
3203 registry.push_pending_unique(FinalizerCell::new(2));
3204
3205 let stats = registry.stats();
3206 assert_eq!(stats.finobj_new, 2);
3207 assert_eq!(stats.finobj_survival, 0);
3208 assert_eq!(stats.finobj_old1, 0);
3209 assert_eq!(stats.finobj_reallyold, 0);
3210 assert_eq!(stats.finobj_minor_scan, 2);
3211
3212 registry.finish_minor_collection();
3213 let stats = registry.stats();
3214 assert_eq!(stats.finobj_new, 0);
3215 assert_eq!(stats.finobj_survival, 2);
3216 assert_eq!(stats.finobj_old1, 0);
3217 assert_eq!(stats.finobj_reallyold, 0);
3218 assert_eq!(stats.finobj_minor_scan, 2);
3219
3220 registry.push_pending_unique(FinalizerCell::new(3));
3221 registry.finish_minor_collection();
3222 let stats = registry.stats();
3223 assert_eq!(stats.finobj_new, 0);
3224 assert_eq!(stats.finobj_survival, 1);
3225 assert_eq!(stats.finobj_old1, 2);
3226 assert_eq!(stats.finobj_reallyold, 0);
3227 assert_eq!(stats.finobj_minor_scan, 1);
3228
3229 registry.finish_minor_collection();
3230 let stats = registry.stats();
3231 assert_eq!(stats.finobj_new, 0);
3232 assert_eq!(stats.finobj_survival, 0);
3233 assert_eq!(stats.finobj_old1, 1);
3234 assert_eq!(stats.finobj_reallyold, 2);
3235 assert_eq!(stats.finobj_minor_scan, 0);
3236 }
3237
3238 #[test]
3239 fn finalizer_registry_minor_snapshot_uses_cohort_boundaries() {
3240 let mut registry = FinalizerRegistry::default();
3241 registry.push_pending_unique(FinalizerCell::new(1));
3242 registry.push_pending_unique(FinalizerCell::new(2));
3243 registry.push_pending_unique(FinalizerCell::new(3));
3244 registry.finish_minor_collection();
3245 registry.finish_minor_collection();
3246 registry.push_pending_unique(FinalizerCell::new(4));
3247 registry.push_pending_unique(FinalizerCell::new(5));
3248
3249 assert_eq!(
3250 finalizer_ids(®istry.pending_minor_snapshot()),
3251 vec![4, 5],
3252 "minor finalizer scan must skip the old1/reallyold prefix"
3253 );
3254
3255 registry.push_to_be_finalized(FinalizerCell::new(99));
3256 registry.promote_pending_to_finalized(vec![
3257 FinalizerCell::new(1),
3258 FinalizerCell::new(2),
3259 FinalizerCell::new(4),
3260 ]);
3261
3262 let stats = registry.stats();
3263 assert_eq!(stats.finobj_old1, 1);
3264 assert_eq!(stats.finobj_new, 1);
3265 assert_eq!(stats.finobj_minor_scan, 1);
3266 assert_eq!(finalizer_ids(registry.pending()), vec![3, 5]);
3267 assert_eq!(
3268 finalizer_ids(registry.to_be_finalized()),
3269 vec![99, 4, 2, 1],
3270 "new to-be-finalized batches append behind older queued finalizers"
3271 );
3272 }
3273
3274 #[test]
3275 fn finalizer_registry_marks_and_clears_finalized_bit() {
3276 let mut registry = FinalizerRegistry::default();
3277 let object = FinalizerCell::new(1);
3278
3279 assert!(!object.is_finalized());
3280 registry.push_pending_unique(object.clone());
3281 assert!(object.is_finalized());
3282
3283 registry.push_pending_unique(object.clone());
3284 assert_eq!(registry.pending_len(), 1);
3285
3286 registry.promote_pending_to_finalized(vec![object.clone()]);
3287 assert!(object.is_finalized());
3288 assert_eq!(registry.pending_len(), 0);
3289 assert_eq!(registry.to_be_finalized_len(), 1);
3290
3291 let popped = registry.pop_to_be_finalized().unwrap();
3292 assert_eq!(popped.id, 1);
3293 assert!(!object.is_finalized());
3294
3295 registry.push_pending_unique(object.clone());
3296 assert!(object.is_finalized());
3297 assert_eq!(registry.pending_len(), 1);
3298 }
3299
3300 #[test]
3301 fn alloc_and_drop_all() {
3302 let heap = Heap::new();
3303 heap.unpause();
3304 let _a = heap.allocate(Cell0 {
3305 next: Cell::new(None),
3306 marker_calls: Cell::new(0),
3307 });
3308 let _b = heap.allocate(Cell0 {
3309 next: Cell::new(None),
3310 marker_calls: Cell::new(0),
3311 });
3312 assert!(heap.bytes_used() > 0);
3313 heap.drop_all();
3314 assert_eq!(heap.bytes_used(), 0);
3315 }
3316
3317 fn list_len(heap: &Heap, mut cursor: Option<NonNull<GcBox<dyn Trace>>>) -> usize {
3318 let mut count = 0usize;
3319 while let Some(ptr) = cursor {
3320 count += 1;
3321 cursor = heap.header_from_ptr(ptr).next.get();
3322 }
3323 count
3324 }
3325
3326 #[test]
3327 fn finalizer_intrusive_lists_sweep_and_drop() {
3328 let heap = Heap::new();
3329 heap.unpause();
3330 let _normal = heap.allocate(Cell0 {
3331 next: Cell::new(None),
3332 marker_calls: Cell::new(0),
3333 });
3334 let finobj = heap.allocate(Cell0 {
3335 next: Cell::new(None),
3336 marker_calls: Cell::new(0),
3337 });
3338 let tobefnz = heap.allocate(Cell0 {
3339 next: Cell::new(None),
3340 marker_calls: Cell::new(0),
3341 });
3342
3343 assert!(heap.move_allgc_to_finobj(finobj.as_trace_ptr()));
3344 assert!(heap.move_allgc_to_finobj(tobefnz.as_trace_ptr()));
3345 assert!(heap.move_finobj_to_tobefnz(tobefnz.as_trace_ptr()));
3346 assert_eq!(list_len(&heap, heap.head.get()), 1);
3347 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3348 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3349 assert_eq!(heap.allgc_count(), 3);
3350
3351 heap.full_collect(&TwoRoots {
3352 first: Some(finobj),
3353 second: Some(tobefnz),
3354 });
3355 assert_eq!(list_len(&heap, heap.head.get()), 0);
3356 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3357 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3358 assert_eq!(heap.allgc_count(), 2);
3359
3360 assert!(heap.move_tobefnz_to_allgc(tobefnz.as_trace_ptr()));
3361 heap.full_collect(&OneRoot(Some(tobefnz)));
3362 assert_eq!(list_len(&heap, heap.head.get()), 1);
3363 assert_eq!(list_len(&heap, heap.finobj.get()), 0);
3364 assert_eq!(list_len(&heap, heap.tobefnz.get()), 0);
3365 assert_eq!(heap.allgc_count(), 1);
3366
3367 heap.drop_all();
3368 assert_eq!(heap.bytes_used(), 0);
3369 }
3370
3371 #[test]
3372 fn account_buffer_refunded_on_sweep() {
3373 let heap = Heap::new();
3374 heap.unpause();
3375 let baseline = heap.bytes_used();
3376 {
3377 let a = heap.allocate(Cell0 {
3378 next: Cell::new(None),
3379 marker_calls: Cell::new(0),
3380 });
3381 assert!(heap.bytes_used() > baseline);
3382 a.account_buffer(&heap, 4096);
3383 assert_eq!(
3384 a.header().size(),
3385 std::mem::size_of::<GcBox<Cell0>>() + 4096
3386 );
3387 }
3388 heap.full_collect(&OneRoot(None));
3391 assert_eq!(
3392 heap.bytes_used(),
3393 baseline,
3394 "account_buffer charge must be fully refunded by sweep"
3395 );
3396 }
3397
3398 #[test]
3399 fn account_buffer_noop_on_uncollected() {
3400 let heap = Heap::new();
3401 heap.unpause();
3402 let g = Gc::new_uncollected(Cell0 {
3403 next: Cell::new(None),
3404 marker_calls: Cell::new(0),
3405 });
3406 let before = heap.bytes_used();
3407 g.account_buffer(&heap, 8192);
3408 assert_eq!(
3409 heap.bytes_used(),
3410 before,
3411 "uncollected box must not charge the pacer"
3412 );
3413 }
3414
3415 #[test]
3416 fn collect_unreachable_frees_bytes() {
3417 let heap = Heap::new();
3418 heap.unpause();
3419 let _a = heap.allocate(Cell0 {
3420 next: Cell::new(None),
3421 marker_calls: Cell::new(0),
3422 });
3423 let bytes_before = heap.bytes_used();
3424 assert!(bytes_before > 0);
3425 heap.full_collect(&OneRoot(None));
3427 assert_eq!(heap.bytes_used(), 0);
3428 assert_eq!(heap.collections(), 1);
3429 }
3430
3431 #[test]
3432 fn collect_keeps_reachable() {
3433 let heap = Heap::new();
3434 heap.unpause();
3435 let root = heap.allocate(Cell0 {
3436 next: Cell::new(None),
3437 marker_calls: Cell::new(0),
3438 });
3439 let bytes_before = heap.bytes_used();
3440 heap.full_collect(&OneRoot(Some(root)));
3441 assert_eq!(heap.bytes_used(), bytes_before);
3442 assert_eq!(root.marker_calls.get(), 1);
3443 }
3444
3445 #[test]
3446 fn allocations_start_new_and_white() {
3447 let heap = Heap::new();
3448 heap.unpause();
3449 let obj = heap.allocate(Cell0 {
3450 next: Cell::new(None),
3451 marker_calls: Cell::new(0),
3452 });
3453 assert_eq!(obj.age(), GcAge::New);
3454 assert!(obj.color().is_white());
3455 }
3456
3457 #[test]
3458 fn allocation_tokens_track_exact_live_box() {
3459 let heap = Heap::new();
3460 heap.unpause();
3461 let obj = heap.allocate(Cell0 {
3462 next: Cell::new(None),
3463 marker_calls: Cell::new(0),
3464 });
3465 let id = obj.identity();
3466
3467 assert_eq!(
3468 heap.allocation_token(id),
3469 None,
3470 "lazy registration: a freshly allocated box has no token until it is downgraded"
3471 );
3472
3473 let token = heap.register_allocation_token(id);
3474 assert_eq!(
3475 heap.register_allocation_token(id),
3476 token,
3477 "registration is get-or-insert: re-registering a live identity returns the same token"
3478 );
3479 assert_eq!(heap.allocation_token(id), Some(token));
3480 assert!(heap.contains_allocation(id, token));
3481 assert!(!heap.contains_allocation(id, token + 1));
3482
3483 heap.full_collect(&OneRoot(None));
3484 assert_eq!(heap.allocation_token(id), None);
3485 assert!(!heap.contains_allocation(id, token));
3486 }
3487
3488 #[test]
3489 fn registering_after_sweep_yields_a_distinct_token_on_address_reuse() {
3490 let heap = Heap::new();
3491 heap.unpause();
3492 let first = heap.allocate(Cell0 {
3493 next: Cell::new(None),
3494 marker_calls: Cell::new(0),
3495 });
3496 let id = first.identity();
3497 let first_token = heap.register_allocation_token(id);
3498
3499 heap.full_collect(&OneRoot(None));
3500 assert_eq!(
3501 heap.allocation_token(id),
3502 None,
3503 "sweep removes the token for the dead identity"
3504 );
3505
3506 let second_token = heap.register_allocation_token(id);
3507 assert_ne!(
3508 first_token, second_token,
3509 "monotonic tokens keep address reuse from resurrecting a stale weak handle"
3510 );
3511 assert!(!heap.contains_allocation(id, first_token));
3512 assert!(heap.contains_allocation(id, second_token));
3513 }
3514
3515 #[test]
3516 fn allocation_during_incremental_sweep_survives_current_cycle() {
3517 let heap = Heap::new();
3518 heap.unpause();
3519 let old_dead = heap.allocate(Cell0 {
3520 next: Cell::new(None),
3521 marker_calls: Cell::new(0),
3522 });
3523 let old_id = old_dead.identity();
3524 heap.register_allocation_token(old_id);
3525
3526 let outcome = heap.incremental_run_until_state_with_post_mark(
3527 &OneRoot(None),
3528 GcState::SweepAllGc,
3529 1024,
3530 |_| {},
3531 );
3532 assert_eq!(outcome, StepOutcome::InProgress);
3533 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3534
3535 let new_during_sweep = heap.allocate(Cell0 {
3536 next: Cell::new(None),
3537 marker_calls: Cell::new(0),
3538 });
3539 let new_id = new_during_sweep.identity();
3540 heap.register_allocation_token(new_id);
3541
3542 loop {
3543 let outcome = heap.incremental_step_with_post_mark(
3544 &OneRoot(None),
3545 StepBudget::from_work(64),
3546 |_| {},
3547 );
3548 if matches!(outcome, StepOutcome::Paused) {
3549 break;
3550 }
3551 }
3552
3553 assert_eq!(heap.allocation_token(old_id), None);
3554 assert!(heap.allocation_token(new_id).is_some());
3555 assert_eq!(heap.allgc_count(), 1);
3556
3557 heap.full_collect(&OneRoot(None));
3558 assert_eq!(heap.allocation_token(new_id), None);
3559 assert_eq!(heap.allgc_count(), 0);
3560 }
3561
3562 #[test]
3563 fn promote_and_reset_all_ages() {
3564 let heap = Heap::new();
3565 heap.unpause();
3566 let a = heap.allocate(Cell0 {
3567 next: Cell::new(None),
3568 marker_calls: Cell::new(0),
3569 });
3570 let b = heap.allocate(Cell0 {
3571 next: Cell::new(None),
3572 marker_calls: Cell::new(0),
3573 });
3574
3575 heap.promote_all_to_old();
3576 assert_eq!(a.age(), GcAge::Old);
3577 assert_eq!(b.age(), GcAge::Old);
3578 assert_eq!(a.color(), Color::Black);
3579 assert_eq!(b.color(), Color::Black);
3580
3581 heap.reset_all_ages();
3582 assert_eq!(a.age(), GcAge::New);
3583 assert_eq!(b.age(), GcAge::New);
3584 assert!(a.color().is_white());
3585 assert!(b.color().is_white());
3586 }
3587
3588 #[test]
3589 fn generational_barriers_update_ages() {
3590 let heap = Heap::new();
3591 heap.unpause();
3592 let parent = heap.allocate(Cell0 {
3593 next: Cell::new(None),
3594 marker_calls: Cell::new(0),
3595 });
3596 let child = heap.allocate(Cell0 {
3597 next: Cell::new(None),
3598 marker_calls: Cell::new(0),
3599 });
3600
3601 parent.set_age(GcAge::Old);
3602 parent.set_color(Color::Black);
3603 heap.generational_backward_barrier(parent);
3604 assert_eq!(parent.age(), GcAge::Touched1);
3605 assert_eq!(parent.color(), Color::Gray);
3606
3607 heap.generational_forward_barrier(parent, child);
3608 assert_eq!(child.age(), GcAge::Old0);
3609 }
3610
3611 #[test]
3612 fn minor_collect_frees_young_and_keeps_old() {
3613 let heap = Heap::new();
3614 heap.unpause();
3615 let old_unreachable = heap.allocate(Cell0 {
3616 next: Cell::new(None),
3617 marker_calls: Cell::new(0),
3618 });
3619 old_unreachable.set_age(GcAge::Old);
3620 old_unreachable.set_color(Color::Black);
3621 let _young_unreachable = heap.allocate(Cell0 {
3622 next: Cell::new(None),
3623 marker_calls: Cell::new(0),
3624 });
3625 let young_survivor = heap.allocate(Cell0 {
3626 next: Cell::new(None),
3627 marker_calls: Cell::new(0),
3628 });
3629
3630 heap.minor_collect_with_post_mark(&OneRoot(Some(young_survivor)), |_| {});
3631
3632 assert_eq!(heap.allgc_count(), 2);
3633 assert_eq!(old_unreachable.age(), GcAge::Old);
3634 assert_eq!(young_survivor.age(), GcAge::Survival);
3635 assert!(young_survivor.color().is_white());
3636 }
3637
3638 #[test]
3639 fn minor_collect_skips_untouched_old_root_scan_work() {
3640 let heap = Heap::new();
3641 heap.unpause();
3642 let old_root = heap.allocate(Cell0 {
3643 next: Cell::new(None),
3644 marker_calls: Cell::new(0),
3645 });
3646 old_root.set_age(GcAge::Old);
3647 old_root.set_color(Color::Black);
3648 let young_root = heap.allocate(Cell0 {
3649 next: Cell::new(None),
3650 marker_calls: Cell::new(0),
3651 });
3652
3653 heap.minor_collect_with_post_mark(
3654 &TwoRoots {
3655 first: Some(old_root),
3656 second: Some(young_root),
3657 },
3658 |_| {},
3659 );
3660
3661 let stats = heap.last_mark_stats();
3662 assert_eq!(stats.marked, 2);
3663 assert_eq!(stats.marked_old, 1);
3664 assert_eq!(stats.marked_young, 1);
3665 assert_eq!(stats.traced, 1);
3666 assert_eq!(stats.traced_old, 0);
3667 assert_eq!(stats.traced_young, 1);
3668 assert_eq!(old_root.marker_calls.get(), 0);
3669 assert_eq!(young_root.marker_calls.get(), 1);
3670 assert_eq!(old_root.age(), GcAge::Old);
3671 assert_eq!(young_root.age(), GcAge::Survival);
3672 }
3673
3674 #[test]
3675 fn minor_collect_traces_touched_old_parent() {
3676 let heap = Heap::new();
3677 heap.unpause();
3678 let old_root = heap.allocate(Cell0 {
3679 next: Cell::new(None),
3680 marker_calls: Cell::new(0),
3681 });
3682 old_root.set_age(GcAge::Old);
3683 old_root.set_color(Color::Black);
3684 let young_child = heap.allocate(Cell0 {
3685 next: Cell::new(None),
3686 marker_calls: Cell::new(0),
3687 });
3688 old_root.next.set(Some(young_child));
3689 heap.generational_backward_barrier(old_root);
3690
3691 heap.minor_collect_with_post_mark(&OneRoot(Some(old_root)), |_| {});
3692
3693 let stats = heap.last_mark_stats();
3694 assert_eq!(stats.marked, 2);
3695 assert_eq!(stats.marked_old, 1);
3696 assert_eq!(stats.marked_young, 1);
3697 assert_eq!(stats.traced, 2);
3698 assert_eq!(stats.traced_old, 1);
3699 assert_eq!(stats.traced_young, 1);
3700 assert_eq!(old_root.marker_calls.get(), 1);
3701 assert_eq!(young_child.marker_calls.get(), 1);
3702 assert_eq!(old_root.age(), GcAge::Touched2);
3703 assert_eq!(young_child.age(), GcAge::Survival);
3704 }
3705
3706 #[test]
3707 fn minor_sweep_uses_generation_cursors_to_skip_old_tail() {
3708 let heap = Heap::new();
3709 heap.unpause();
3710 let mut old_objects = Vec::new();
3711 for _ in 0..64 {
3712 old_objects.push(heap.allocate(Cell0 {
3713 next: Cell::new(None),
3714 marker_calls: Cell::new(0),
3715 }));
3716 }
3717 heap.promote_all_to_old();
3718 let young_root = heap.allocate(Cell0 {
3719 next: Cell::new(None),
3720 marker_calls: Cell::new(0),
3721 });
3722
3723 heap.minor_collect_with_post_mark(&OneRoot(Some(young_root)), |_| {});
3724
3725 let stats = heap.last_sweep_stats();
3726 assert_eq!(stats.visited, 1);
3727 assert_eq!(stats.visited_young, 1);
3728 assert_eq!(stats.visited_old, 0);
3729 assert_eq!(heap.allgc_count(), old_objects.len() + 1);
3730 assert_eq!(young_root.age(), GcAge::Survival);
3731 }
3732
3733 #[test]
3734 fn full_sweep_corrects_generation_cursors_when_cursor_object_is_freed() {
3735 let heap = Heap::new();
3736 heap.unpause();
3737 let _old = heap.allocate(Cell0 {
3738 next: Cell::new(None),
3739 marker_calls: Cell::new(0),
3740 });
3741 heap.promote_all_to_old();
3742 assert!(heap.survival.get().is_some());
3743 assert!(heap.old1.get().is_some());
3744 assert!(heap.reallyold.get().is_some());
3745
3746 heap.full_collect(&OneRoot(None));
3747
3748 assert_eq!(heap.allgc_count(), 0);
3749 assert_eq!(heap.survival.get(), None);
3750 assert_eq!(heap.old1.get(), None);
3751 assert_eq!(heap.reallyold.get(), None);
3752 assert_eq!(heap.firstold1.get(), None);
3753 assert_eq!(heap.last_sweep_stats().freed, 1);
3754 }
3755
3756 #[test]
3757 fn full_sweep_unlinks_freed_grayagain_entries() {
3758 let heap = Heap::new();
3759 heap.unpause();
3760 let parent = heap.allocate(Cell0 {
3761 next: Cell::new(None),
3762 marker_calls: Cell::new(0),
3763 });
3764 heap.promote_all_to_old();
3765 heap.generational_backward_barrier(parent);
3766 assert_eq!(heap.grayagain_count(), 1);
3767
3768 heap.full_collect(&OneRoot(None));
3769
3770 assert_eq!(heap.allgc_count(), 0);
3771 assert_eq!(heap.grayagain_count(), 0);
3772
3773 let young = heap.allocate(Cell0 {
3774 next: Cell::new(None),
3775 marker_calls: Cell::new(0),
3776 });
3777 heap.minor_collect_with_post_mark(&OneRoot(Some(young)), |_| {});
3778 assert_eq!(young.age(), GcAge::Survival);
3779 }
3780
3781 #[test]
3782 fn grayagain_links_object_once() {
3783 let heap = Heap::new();
3784 heap.unpause();
3785 let parent = heap.allocate(Cell0 {
3786 next: Cell::new(None),
3787 marker_calls: Cell::new(0),
3788 });
3789 parent.set_age(GcAge::Old);
3790 parent.set_color(Color::Black);
3791
3792 heap.generational_backward_barrier(parent);
3793 heap.generational_backward_barrier(parent);
3794
3795 assert_eq!(heap.grayagain_count(), 1);
3796 }
3797
3798 #[test]
3799 fn grayagain_list_carries_old1_until_old() {
3800 let heap = Heap::new();
3801 heap.unpause();
3802 let survivor = heap.allocate(Cell0 {
3803 next: Cell::new(None),
3804 marker_calls: Cell::new(0),
3805 });
3806
3807 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3808 assert_eq!(survivor.age(), GcAge::Survival);
3809
3810 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3811 assert_eq!(survivor.age(), GcAge::Old1);
3812
3813 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3814 assert_eq!(survivor.age(), GcAge::Old);
3815 assert_eq!(heap.allgc_count(), 1);
3816 }
3817
3818 #[test]
3819 fn grayagain_list_carries_touched2_until_old() {
3820 let heap = Heap::new();
3821 heap.unpause();
3822 let parent = heap.allocate(Cell0 {
3823 next: Cell::new(None),
3824 marker_calls: Cell::new(0),
3825 });
3826 parent.set_age(GcAge::Old);
3827 parent.set_color(Color::Black);
3828 heap.generational_backward_barrier(parent);
3829
3830 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3831 assert_eq!(parent.age(), GcAge::Touched2);
3832
3833 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3834 assert_eq!(parent.age(), GcAge::Old);
3835 assert_eq!(parent.marker_calls.get(), 2);
3836 }
3837
3838 #[test]
3839 fn collect_traverses_cycles() {
3840 let heap = Heap::new();
3841 heap.unpause();
3842 let a = heap.allocate(Cell0 {
3843 next: Cell::new(None),
3844 marker_calls: Cell::new(0),
3845 });
3846 let b = heap.allocate(Cell0 {
3847 next: Cell::new(Some(a)),
3848 marker_calls: Cell::new(0),
3849 });
3850 a.next.set(Some(b)); heap.full_collect(&OneRoot(Some(a)));
3853 assert_eq!(a.marker_calls.get(), 1);
3854 assert_eq!(b.marker_calls.get(), 1);
3855 heap.full_collect(&OneRoot(None));
3859 assert_eq!(heap.bytes_used(), 0);
3860 }
3861
3862 #[test]
3863 fn heap_guard_stacks() {
3864 assert!(
3865 with_current_heap(|heap| heap.is_none()),
3866 "no guard initially"
3867 );
3868 let h1 = Heap::new();
3869 h1.unpause();
3870 {
3871 let _g1 = HeapGuard::push(&h1);
3872 assert!(with_current_heap(|heap| heap.is_some()));
3873 let h2 = Heap::new();
3874 h2.unpause();
3875 {
3876 let _g2 = HeapGuard::push(&h2);
3877 with_current_heap(|heap| {
3879 assert!(std::rc::Rc::ptr_eq(heap.unwrap(), &h2));
3880 });
3881 }
3882 with_current_heap(|heap| {
3884 assert!(std::rc::Rc::ptr_eq(heap.unwrap(), &h1));
3885 });
3886 }
3887 assert!(
3888 with_current_heap(|heap| heap.is_none()),
3889 "stack empty after all guards drop"
3890 );
3891 }
3892
3893 #[test]
3894 fn step_threshold_triggers_collect() {
3895 let heap = Heap::new();
3896 heap.unpause();
3897 let mut keeps = Vec::new();
3899 for _ in 0..64 {
3900 keeps.push(heap.allocate(Cell0 {
3904 next: Cell::new(None),
3905 marker_calls: Cell::new(0),
3906 }));
3907 }
3908 struct ManyRoots<'a>(&'a [Gc<Cell0>]);
3910 impl<'a> Trace for ManyRoots<'a> {
3911 fn trace(&self, m: &mut Marker) {
3912 for g in self.0.iter() {
3913 m.mark(*g);
3914 }
3915 }
3916 }
3917 heap.step(&ManyRoots(&keeps));
3918 assert!(heap.bytes_used() > 0);
3920 }
3921
3922 #[test]
3923 fn threshold_floored_after_collecting_tiny_heap() {
3924 let heap = Heap::new();
3925 heap.unpause();
3926 struct NoRoots;
3927 impl Trace for NoRoots {
3928 fn trace(&self, _m: &mut Marker) {}
3929 }
3930 for _ in 0..200 {
3931 heap.allocate(Cell0 {
3932 next: Cell::new(None),
3933 marker_calls: Cell::new(0),
3934 });
3935 }
3936 heap.full_collect(&NoRoots);
3937 assert!(
3938 heap.threshold_bytes() >= GC_MIN_THRESHOLD,
3939 "threshold {} collapsed below floor {}; a churning program would full-collect per allocation",
3940 heap.threshold_bytes(),
3941 GC_MIN_THRESHOLD
3942 );
3943 }
3944
3945 fn build_chain(heap: &Heap, len: usize) -> Gc<Cell0> {
3946 let head = heap.allocate(Cell0 {
3947 next: Cell::new(None),
3948 marker_calls: Cell::new(0),
3949 });
3950 let mut cur = head;
3951 for _ in 1..len {
3952 let n = heap.allocate(Cell0 {
3953 next: Cell::new(None),
3954 marker_calls: Cell::new(0),
3955 });
3956 cur.next.set(Some(n));
3957 cur = n;
3958 }
3959 head
3960 }
3961
3962 #[test]
3963 fn budget_zero_does_some_work() {
3964 let heap = Heap::new();
3965 heap.unpause();
3966 let head = build_chain(&heap, 8);
3967 let roots = OneRoot(Some(head));
3968 let budget = StepBudget::from_work(0);
3969 let outcome = heap.incremental_step_with_post_mark(&roots, budget, |_| {});
3970 assert_ne!(outcome, StepOutcome::SkippedStopped);
3971 assert_ne!(heap.gc_state(), GcState::Pause);
3972 }
3973
3974 #[test]
3975 fn run_until_state_stops_before_next_phase_work() {
3976 let heap = Heap::new();
3977 heap.unpause();
3978 let head = build_chain(&heap, 8);
3979 let roots = OneRoot(Some(head));
3980 let atomic_calls = Cell::new(0);
3981
3982 let outcome =
3983 heap.incremental_run_until_state_with_post_mark(&roots, GcState::Atomic, 1024, |_| {
3984 atomic_calls.set(atomic_calls.get() + 1)
3985 });
3986 assert_eq!(outcome, StepOutcome::InProgress);
3987 assert_eq!(heap.gc_state(), GcState::Atomic);
3988 assert_eq!(
3989 atomic_calls.get(),
3990 0,
3991 "atomic hook must not run before inspection"
3992 );
3993
3994 let outcome = heap.incremental_run_until_state_with_post_mark(
3995 &roots,
3996 GcState::SweepAllGc,
3997 1024,
3998 |_| atomic_calls.set(atomic_calls.get() + 1),
3999 );
4000 assert_eq!(outcome, StepOutcome::InProgress);
4001 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
4002 assert_eq!(
4003 atomic_calls.get(),
4004 1,
4005 "entering sweep must run the atomic hook once"
4006 );
4007 }
4008
4009 #[test]
4010 fn larger_budget_drains_more_gray_than_smaller() {
4011 let small_heap = Heap::new();
4012 small_heap.unpause();
4013 let h1 = build_chain(&small_heap, 64);
4014 let r1 = OneRoot(Some(h1));
4015 let mut small_calls = 0;
4016 loop {
4017 small_calls += 1;
4018 let outcome =
4019 small_heap.incremental_step_with_post_mark(&r1, StepBudget::from_work(2), |_| {});
4020 if outcome == StepOutcome::Paused {
4021 break;
4022 }
4023 assert!(small_calls < 10_000, "did not converge");
4024 }
4025
4026 let big_heap = Heap::new();
4027 big_heap.unpause();
4028 let h2 = build_chain(&big_heap, 64);
4029 let r2 = OneRoot(Some(h2));
4030 let mut big_calls = 0;
4031 loop {
4032 big_calls += 1;
4033 let outcome =
4034 big_heap.incremental_step_with_post_mark(&r2, StepBudget::from_work(64), |_| {});
4035 if outcome == StepOutcome::Paused {
4036 break;
4037 }
4038 assert!(big_calls < 10_000, "did not converge");
4039 }
4040
4041 assert!(
4042 big_calls < small_calls,
4043 "expected big_calls={} < small_calls={}",
4044 big_calls,
4045 small_calls
4046 );
4047 }
4048
4049 #[test]
4050 fn sweep_can_pause_and_resume() {
4051 let heap = Heap::new();
4052 heap.unpause();
4053 for _ in 0..16 {
4054 let _ = heap.allocate(Cell0 {
4055 next: Cell::new(None),
4056 marker_calls: Cell::new(0),
4057 });
4058 }
4059 let roots = OneRoot(None);
4060 let bytes_before = heap.bytes_used();
4061 assert!(bytes_before > 0);
4062 let mut step_count = 0;
4063 let mut saw_in_progress_during_sweep = false;
4064 loop {
4065 step_count += 1;
4066 let outcome =
4067 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {});
4068 if heap.gc_state().is_sweep() && outcome == StepOutcome::InProgress {
4069 saw_in_progress_during_sweep = true;
4070 }
4071 if outcome == StepOutcome::Paused {
4072 break;
4073 }
4074 assert!(step_count < 10_000, "did not converge");
4075 }
4076 assert!(saw_in_progress_during_sweep, "sweep never paused mid-list");
4077 assert_eq!(heap.bytes_used(), 0);
4078 }
4079
4080 #[test]
4081 fn post_mark_runs_once_per_atomic() {
4082 let heap = Heap::new();
4083 heap.unpause();
4084 for _ in 0..32 {
4085 let _ = heap.allocate(Cell0 {
4086 next: Cell::new(None),
4087 marker_calls: Cell::new(0),
4088 });
4089 }
4090 let roots = OneRoot(None);
4091 let call_count = std::cell::Cell::new(0);
4092 let mut step_count = 0;
4093 loop {
4094 step_count += 1;
4095 let outcome =
4096 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {
4097 call_count.set(call_count.get() + 1);
4098 });
4099 if outcome == StepOutcome::Paused {
4100 break;
4101 }
4102 assert!(step_count < 10_000, "did not converge");
4103 }
4104 assert_eq!(
4105 call_count.get(),
4106 1,
4107 "post_mark must run exactly once per cycle"
4108 );
4109 }
4110
4111 #[test]
4112 fn full_collect_equivalent_to_incremental_to_pause() {
4113 let h1 = Heap::new();
4114 h1.unpause();
4115 let head1 = h1.allocate(Cell0 {
4116 next: Cell::new(None),
4117 marker_calls: Cell::new(0),
4118 });
4119 let _orphan1 = h1.allocate(Cell0 {
4120 next: Cell::new(None),
4121 marker_calls: Cell::new(0),
4122 });
4123 let _orphan2 = h1.allocate(Cell0 {
4124 next: Cell::new(None),
4125 marker_calls: Cell::new(0),
4126 });
4127 let roots1 = OneRoot(Some(head1));
4128 h1.full_collect(&roots1);
4129 let bytes_full = h1.bytes_used();
4130
4131 let h2 = Heap::new();
4132 h2.unpause();
4133 let head2 = h2.allocate(Cell0 {
4134 next: Cell::new(None),
4135 marker_calls: Cell::new(0),
4136 });
4137 let _orphan3 = h2.allocate(Cell0 {
4138 next: Cell::new(None),
4139 marker_calls: Cell::new(0),
4140 });
4141 let _orphan4 = h2.allocate(Cell0 {
4142 next: Cell::new(None),
4143 marker_calls: Cell::new(0),
4144 });
4145 let roots2 = OneRoot(Some(head2));
4146 loop {
4147 let outcome =
4148 h2.incremental_step_with_post_mark(&roots2, StepBudget::from_work(1), |_| {});
4149 if outcome == StepOutcome::Paused {
4150 break;
4151 }
4152 }
4153 assert_eq!(h2.bytes_used(), bytes_full);
4154 }
4155
4156 struct DropFlag(std::rc::Rc<Cell<bool>>);
4161 impl Drop for DropFlag {
4162 fn drop(&mut self) {
4163 self.0.set(true);
4164 }
4165 }
4166 struct Tracked(#[allow(dead_code)] DropFlag);
4167 impl Trace for Tracked {
4168 fn trace(&self, _m: &mut Marker) {}
4169 }
4170
4171 #[test]
4172 fn allocate_uncollected_survives_collection_but_is_freed_on_heap_drop() {
4173 let heap = Heap::new();
4174 heap.unpause();
4175 let dropped = std::rc::Rc::new(Cell::new(false));
4176 let _gc = heap.allocate_uncollected(Tracked(DropFlag(dropped.clone())));
4177
4178 heap.full_collect(&OneRoot(None));
4181 assert!(
4182 !dropped.get(),
4183 "allocate_uncollected box must survive a full collection while the heap is alive"
4184 );
4185
4186 drop(heap);
4187 assert!(
4188 dropped.get(),
4189 "allocate_uncollected box must be freed once its heap drops (issue #249)"
4190 );
4191 }
4192
4193 #[test]
4194 fn bootstrapping_routes_allocate_to_the_uncollected_list() {
4195 let heap = Heap::new();
4196 heap.unpause();
4197 assert!(!heap.is_bootstrapping());
4198
4199 heap.begin_bootstrap();
4200 let dropped = std::rc::Rc::new(Cell::new(false));
4201 let _gc = heap.allocate(Tracked(DropFlag(dropped.clone())));
4202
4203 assert_eq!(
4206 heap.allgc_count(),
4207 0,
4208 "a bootstrap allocation must not join the normal collectable list"
4209 );
4210 heap.full_collect(&OneRoot(None));
4211 assert!(
4212 !dropped.get(),
4213 "a bootstrap allocation must survive collection while bootstrapping is active"
4214 );
4215
4216 heap.end_bootstrap();
4217 drop(heap);
4218 assert!(
4219 dropped.get(),
4220 "a bootstrap allocation must still be freed when the heap drops"
4221 );
4222 }
4223
4224 #[test]
4231 fn ownership_flags_distinguish_all_three_allocation_paths() {
4232 let heap = Heap::new();
4233 let tracked = heap.allocate(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4234 assert!(tracked.is_heap_owned());
4235 assert!(tracked.is_heap_tracked());
4236
4237 let bootstrap =
4238 heap.allocate_uncollected(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4239 assert!(
4240 bootstrap.is_heap_owned(),
4241 "bootstrap boxes die in drop_all — strict-guard hazard checks must see them"
4242 );
4243 assert!(!bootstrap.is_heap_tracked());
4244
4245 let detached = Gc::new_uncollected(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4246 assert!(!detached.is_heap_owned());
4247 assert!(!detached.is_heap_tracked());
4248 }
4249
4250 #[test]
4251 fn end_bootstrap_restores_normal_sweepable_allocation() {
4252 let heap = Heap::new();
4253 heap.unpause();
4254 heap.begin_bootstrap();
4255 heap.end_bootstrap();
4256
4257 let dropped = std::rc::Rc::new(Cell::new(false));
4258 let _gc = heap.allocate(Tracked(DropFlag(dropped.clone())));
4259 assert_eq!(heap.allgc_count(), 1);
4260
4261 heap.full_collect(&OneRoot(None));
4262 assert_eq!(
4268 heap.allgc_count(),
4269 0,
4270 "after end_bootstrap, an unreachable allocation must be swept normally"
4271 );
4272 }
4273}
4274
4275