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