1use std::cell::{Cell, RefCell};
44use std::collections::{HashMap, HashSet};
45use std::hash::{BuildHasherDefault, Hasher};
46use std::marker::PhantomData;
47use std::ptr::NonNull;
48
49#[derive(Default)]
50struct IdentityHasher {
51 value: u64,
52}
53
54impl Hasher for IdentityHasher {
55 #[inline]
56 fn write(&mut self, bytes: &[u8]) {
57 const PRIME: u64 = 0x0000_0100_0000_01b3;
58 for &byte in bytes {
59 self.value ^= u64::from(byte);
60 self.value = self.value.wrapping_mul(PRIME);
61 }
62 }
63
64 #[inline]
65 fn write_usize(&mut self, i: usize) {
66 self.value = i as u64;
67 }
68
69 #[inline]
70 fn write_u64(&mut self, i: u64) {
71 self.value = i;
72 }
73
74 #[inline]
75 fn finish(&self) -> u64 {
76 let mut x = self.value;
77 x ^= x >> 30;
78 x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9);
79 x ^= x >> 27;
80 x = x.wrapping_mul(0x94d0_49bb_1331_11eb);
81 x ^ (x >> 31)
82 }
83}
84
85type IdentityBuildHasher = BuildHasherDefault<IdentityHasher>;
86type IdentityHashSet = HashSet<usize, IdentityBuildHasher>;
87type IdentityHashMap<V> = HashMap<usize, V, IdentityBuildHasher>;
88
89thread_local! {
103 static CURRENT_HEAP_STACK: RefCell<Vec<std::rc::Rc<Heap>>> = const { RefCell::new(Vec::new()) };
104}
105
106pub struct HeapGuard {
121 heap: std::rc::Rc<Heap>,
122}
123
124impl HeapGuard {
125 pub fn push(heap: &std::rc::Rc<Heap>) -> Self {
127 CURRENT_HEAP_STACK.with(|stack| stack.borrow_mut().push(std::rc::Rc::clone(heap)));
128 HeapGuard {
129 heap: std::rc::Rc::clone(heap),
130 }
131 }
132}
133
134impl Drop for HeapGuard {
135 fn drop(&mut self) {
136 CURRENT_HEAP_STACK.with(|stack| {
137 let popped = stack.borrow_mut().pop();
138 debug_assert!(
139 popped
140 .as_ref()
141 .is_some_and(|top| std::rc::Rc::ptr_eq(top, &self.heap)),
142 "HeapGuard::drop popped a frame it did not push — guards must \
143 drop in reverse push order on the thread that created them"
144 );
145 });
146 }
147}
148
149pub struct BootstrapScope {
161 depth: std::rc::Rc<Cell<usize>>,
162}
163
164impl Drop for BootstrapScope {
165 fn drop(&mut self) {
166 let depth = self.depth.get();
167 debug_assert!(depth > 0, "BootstrapScope dropped with zero depth");
168 self.depth.set(depth.saturating_sub(1));
169 }
170}
171
172thread_local! {
173 static DETACHED_ALLOCATIONS: Cell<usize> = const { Cell::new(0) };
174}
175
176pub fn detached_allocations() -> usize {
183 DETACHED_ALLOCATIONS.with(|c| c.get())
184}
185
186pub fn with_current_heap<R>(f: impl for<'a> FnOnce(Option<&'a std::rc::Rc<Heap>>) -> R) -> R {
193 let top = CURRENT_HEAP_STACK.with(|stack| stack.borrow().last().cloned());
194 f(top.as_ref())
195}
196
197#[derive(Clone, Debug)]
203pub struct HeapRef {
204 weak: std::rc::Weak<Heap>,
205}
206
207impl HeapRef {
208 pub fn from_heap(heap: &std::rc::Rc<Heap>) -> Self {
209 HeapRef {
210 weak: std::rc::Rc::downgrade(heap),
211 }
212 }
213
214 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
215 match self.weak.upgrade() {
216 Some(heap) => heap.contains_allocation(identity, token),
217 None => false,
218 }
219 }
220}
221
222#[derive(Copy, Clone, PartialEq, Eq, Debug)]
224pub enum Color {
225 White0,
229 White1,
231 Gray,
233 Black,
235}
236
237impl Color {
238 pub fn is_white(self) -> bool {
239 matches!(self, Color::White0 | Color::White1)
240 }
241
242 fn other_white(self) -> Self {
243 match self {
244 Color::White0 => Color::White1,
245 Color::White1 => Color::White0,
246 Color::Gray | Color::Black => self,
247 }
248 }
249}
250
251#[derive(Copy, Clone, PartialEq, Eq, Debug)]
255pub enum GcAge {
256 New,
257 Survival,
258 Old0,
259 Old1,
260 Old,
261 Touched1,
262 Touched2,
263}
264
265impl GcAge {
266 pub fn is_old(self) -> bool {
267 !matches!(self, GcAge::New | GcAge::Survival)
268 }
269
270 fn next_after_minor(self) -> Self {
271 match self {
272 GcAge::New => GcAge::Survival,
273 GcAge::Survival | GcAge::Old0 => GcAge::Old1,
274 GcAge::Old1 | GcAge::Old | GcAge::Touched2 => GcAge::Old,
275 GcAge::Touched1 => GcAge::Touched2,
276 }
277 }
278}
279
280pub trait Udata51Probe: std::any::Any {
296 fn is_alive(&self) -> bool;
300
301 fn as_any(&self) -> &dyn std::any::Any;
303}
304
305pub trait FinalizerEntry: Clone {
308 fn identity(&self) -> usize;
309 fn heap_ptr(&self) -> Option<NonNull<GcBox<dyn Trace>>> {
310 None
311 }
312 fn age(&self) -> GcAge;
313 fn is_finalized(&self) -> bool;
314 fn set_finalized(&self, finalized: bool);
315}
316
317pub trait WeakEntry: Clone {
319 type Strong: Clone;
320
321 fn identity(&self) -> usize;
322 fn list_kind(&self) -> WeakListKind;
323 fn upgrade(&self) -> Option<Self::Strong>;
324}
325
326#[derive(Copy, Clone, Debug, PartialEq, Eq)]
327pub enum WeakListKind {
328 WeakValues,
329 Ephemeron,
330 AllWeak,
331}
332
333#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
334pub struct WeakRegistryStats {
335 pub tracked: usize,
336 pub snapshot_live: usize,
337 pub snapshot_dead: usize,
338 pub retained: usize,
339 pub weak_values: usize,
340 pub ephemeron: usize,
341 pub all_weak: usize,
342}
343
344#[derive(Clone, Debug)]
345pub struct WeakRegistry<T: WeakEntry> {
346 weak_values: Vec<T>,
347 ephemeron: Vec<T>,
348 all_weak: Vec<T>,
349 last_stats: WeakRegistryStats,
350}
351
352#[derive(Clone, Debug, PartialEq, Eq)]
353pub struct WeakRegistrySnapshot<T> {
354 pub weak_values: Vec<T>,
355 pub ephemeron: Vec<T>,
356 pub all_weak: Vec<T>,
357}
358
359impl<T> Default for WeakRegistrySnapshot<T> {
360 fn default() -> Self {
361 Self {
362 weak_values: Vec::new(),
363 ephemeron: Vec::new(),
364 all_weak: Vec::new(),
365 }
366 }
367}
368
369impl<T> WeakRegistrySnapshot<T> {
370 pub fn len(&self) -> usize {
371 self.weak_values
372 .len()
373 .saturating_add(self.ephemeron.len())
374 .saturating_add(self.all_weak.len())
375 }
376
377 pub fn into_flat(self) -> Vec<T> {
378 self.weak_values
379 .into_iter()
380 .chain(self.ephemeron)
381 .chain(self.all_weak)
382 .collect()
383 }
384}
385
386impl<T: WeakEntry> Default for WeakRegistry<T> {
387 fn default() -> Self {
388 Self {
389 weak_values: Vec::new(),
390 ephemeron: Vec::new(),
391 all_weak: Vec::new(),
392 last_stats: WeakRegistryStats::default(),
393 }
394 }
395}
396
397impl<T: WeakEntry> WeakRegistry<T> {
398 pub fn len(&self) -> usize {
399 self.weak_values
400 .len()
401 .saturating_add(self.ephemeron.len())
402 .saturating_add(self.all_weak.len())
403 }
404
405 pub fn stats(&self) -> WeakRegistryStats {
406 self.last_stats
407 }
408
409 fn list_mut(&mut self, kind: WeakListKind) -> &mut Vec<T> {
410 match kind {
411 WeakListKind::WeakValues => &mut self.weak_values,
412 WeakListKind::Ephemeron => &mut self.ephemeron,
413 WeakListKind::AllWeak => &mut self.all_weak,
414 }
415 }
416
417 pub fn remove_identity(&mut self, id: usize) {
418 self.weak_values.retain(|entry| entry.identity() != id);
419 self.ephemeron.retain(|entry| entry.identity() != id);
420 self.all_weak.retain(|entry| entry.identity() != id);
421 self.last_stats.tracked = self.len();
422 self.last_stats.retained = self.len();
423 self.update_cohort_stats();
424 }
425
426 fn update_cohort_stats(&mut self) {
427 self.last_stats.weak_values = self.weak_values.len();
428 self.last_stats.ephemeron = self.ephemeron.len();
429 self.last_stats.all_weak = self.all_weak.len();
430 }
431
432 pub fn push_unique(&mut self, entry: T) {
433 let id = entry.identity();
434 self.remove_identity(id);
435 self.list_mut(entry.list_kind()).push(entry);
436 self.last_stats.tracked = self.len();
437 self.last_stats.retained = self.len();
438 self.update_cohort_stats();
439 }
440
441 pub fn live_snapshot_by_kind(&mut self) -> WeakRegistrySnapshot<T::Strong> {
442 let tracked_before = self.len();
443 let weak_values_capacity = self.weak_values.len();
444 let ephemeron_capacity = self.ephemeron.len();
445 let all_weak_capacity = self.all_weak.len();
446 let mut seen = std::collections::HashSet::<usize>::with_capacity(tracked_before);
447 let mut live = WeakRegistrySnapshot {
448 weak_values: Vec::with_capacity(weak_values_capacity),
449 ephemeron: Vec::with_capacity(ephemeron_capacity),
450 all_weak: Vec::with_capacity(all_weak_capacity),
451 };
452 let mut dead = 0usize;
453
454 let entries = std::mem::take(&mut self.weak_values)
455 .into_iter()
456 .chain(std::mem::take(&mut self.ephemeron))
457 .chain(std::mem::take(&mut self.all_weak));
458 for entry in entries {
459 if !seen.insert(entry.identity()) {
460 continue;
461 }
462 match entry.upgrade() {
463 Some(strong) => {
464 match entry.list_kind() {
465 WeakListKind::WeakValues => live.weak_values.push(strong),
466 WeakListKind::Ephemeron => live.ephemeron.push(strong),
467 WeakListKind::AllWeak => live.all_weak.push(strong),
468 }
469 self.list_mut(entry.list_kind()).push(entry);
470 }
471 None => dead += 1,
472 }
473 }
474
475 self.last_stats = WeakRegistryStats {
476 tracked: tracked_before,
477 snapshot_live: live.len(),
478 snapshot_dead: dead,
479 retained: self.len(),
480 weak_values: self.weak_values.len(),
481 ephemeron: self.ephemeron.len(),
482 all_weak: self.all_weak.len(),
483 };
484 live
485 }
486
487 pub fn live_snapshot(&mut self) -> Vec<T::Strong> {
488 self.live_snapshot_by_kind().into_flat()
489 }
490
491 pub fn retain_identities(&mut self, ids: &std::collections::HashSet<usize>) {
492 self.weak_values
493 .retain(|entry| ids.contains(&entry.identity()));
494 self.ephemeron
495 .retain(|entry| ids.contains(&entry.identity()));
496 self.all_weak
497 .retain(|entry| ids.contains(&entry.identity()));
498 self.last_stats.retained = self.len();
499 self.last_stats.tracked = self.len();
500 self.update_cohort_stats();
501 }
502}
503
504#[derive(Clone, Debug)]
505pub struct FinalizerRegistry<T: FinalizerEntry> {
506 pending: Vec<T>,
507 to_be_finalized: Vec<T>,
508 pending_reallyold: usize,
509 pending_old1: usize,
510 pending_survival: usize,
511}
512
513#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
514pub struct FinalizerRegistryStats {
515 pub pending_young: usize,
516 pub pending_old: usize,
517 pub to_be_finalized_young: usize,
518 pub to_be_finalized_old: usize,
519 pub finobj_new: usize,
520 pub finobj_survival: usize,
521 pub finobj_old1: usize,
522 pub finobj_reallyold: usize,
523 pub finobj_minor_scan: usize,
524}
525
526impl<T: FinalizerEntry> Default for FinalizerRegistry<T> {
527 fn default() -> Self {
528 Self {
529 pending: Vec::new(),
530 to_be_finalized: Vec::new(),
531 pending_reallyold: 0,
532 pending_old1: 0,
533 pending_survival: 0,
534 }
535 }
536}
537
538impl<T: FinalizerEntry> FinalizerRegistry<T> {
539 fn pending_new_len(&self) -> usize {
540 self.pending.len().saturating_sub(
541 self.pending_reallyold
542 .saturating_add(self.pending_old1)
543 .saturating_add(self.pending_survival),
544 )
545 }
546
547 fn minor_scan_start(&self) -> usize {
548 self.pending_reallyold.saturating_add(self.pending_old1)
549 }
550
551 fn debug_assert_pending_cohorts(&self) {
552 debug_assert!(
553 self.pending_reallyold
554 .saturating_add(self.pending_old1)
555 .saturating_add(self.pending_survival)
556 <= self.pending.len()
557 );
558 }
559
560 pub fn pending(&self) -> &[T] {
561 &self.pending
562 }
563
564 pub fn pending_snapshot(&self) -> Vec<T> {
565 self.pending.clone()
566 }
567
568 pub fn pending_minor_snapshot(&self) -> Vec<T> {
569 self.pending[self.minor_scan_start().min(self.pending.len())..].to_vec()
570 }
571
572 pub fn to_be_finalized(&self) -> &[T] {
573 &self.to_be_finalized
574 }
575
576 pub fn pending_len(&self) -> usize {
577 self.pending.len()
578 }
579
580 pub fn to_be_finalized_len(&self) -> usize {
581 self.to_be_finalized.len()
582 }
583
584 pub fn has_to_be_finalized(&self) -> bool {
585 !self.to_be_finalized.is_empty()
586 }
587
588 pub fn stats(&self) -> FinalizerRegistryStats {
589 fn count_by_age<T: FinalizerEntry>(objects: &[T]) -> (usize, usize) {
590 objects
591 .iter()
592 .fold((0usize, 0usize), |(young, old), object| {
593 if object.age().is_old() {
594 (young, old + 1)
595 } else {
596 (young + 1, old)
597 }
598 })
599 }
600 let (pending_young, pending_old) = count_by_age(&self.pending);
601 let (to_be_finalized_young, to_be_finalized_old) = count_by_age(&self.to_be_finalized);
602 FinalizerRegistryStats {
603 pending_young,
604 pending_old,
605 to_be_finalized_young,
606 to_be_finalized_old,
607 finobj_new: self.pending_new_len(),
608 finobj_survival: self.pending_survival,
609 finobj_old1: self.pending_old1,
610 finobj_reallyold: self.pending_reallyold,
611 finobj_minor_scan: self.pending.len().saturating_sub(self.minor_scan_start()),
612 }
613 }
614
615 pub fn push_pending_unique(&mut self, object: T) -> bool {
616 if object.is_finalized() {
617 return false;
618 }
619 let id = object.identity();
620 if !self.pending.iter().any(|o| o.identity() == id) {
621 object.set_finalized(true);
622 self.pending.push(object);
623 self.debug_assert_pending_cohorts();
624 true
625 } else {
626 false
627 }
628 }
629
630 pub fn take_pending(&mut self) -> Vec<T> {
631 self.pending_reallyold = 0;
632 self.pending_old1 = 0;
633 self.pending_survival = 0;
634 std::mem::take(&mut self.pending)
635 }
636
637 fn retain_pending_not_in(&mut self, ids: &std::collections::HashSet<usize>) {
638 if ids.is_empty() {
639 return;
640 }
641 let original_reallyold = self.pending_reallyold;
642 let original_old1 = self.pending_old1;
643 let original_survival = self.pending_survival;
644 let mut retained_reallyold = original_reallyold;
645 let mut retained_old1 = original_old1;
646 let mut retained_survival = original_survival;
647 let mut retained = Vec::with_capacity(self.pending.len());
648 for (index, object) in std::mem::take(&mut self.pending).into_iter().enumerate() {
649 if ids.contains(&object.identity()) {
650 if index < original_reallyold {
651 retained_reallyold -= 1;
652 } else if index < original_reallyold + original_old1 {
653 retained_old1 -= 1;
654 } else if index < original_reallyold + original_old1 + original_survival {
655 retained_survival -= 1;
656 }
657 } else {
658 retained.push(object);
659 }
660 }
661 self.pending = retained;
662 self.pending_reallyold = retained_reallyold;
663 self.pending_old1 = retained_old1;
664 self.pending_survival = retained_survival;
665 self.debug_assert_pending_cohorts();
666 }
667
668 pub fn push_to_be_finalized(&mut self, object: T) {
669 object.set_finalized(true);
670 self.to_be_finalized.push(object);
671 }
672
673 fn extend_to_be_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
674 let drain_order: Vec<T> = objects.into_iter().rev().collect();
675 for object in drain_order.iter().cloned() {
676 self.push_to_be_finalized(object);
677 }
678 drain_order
679 }
680
681 pub fn promote_pending_to_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
682 if objects.is_empty() {
683 return Vec::new();
684 }
685 let mut ids: std::collections::HashSet<usize> =
686 std::collections::HashSet::with_capacity(objects.len());
687 ids.extend(objects.iter().map(|object| object.identity()));
688 self.retain_pending_not_in(&ids);
689 self.extend_to_be_finalized(objects)
690 }
691
692 pub fn promote_all_pending_to_old(&mut self) {
693 self.pending_reallyold = self.pending.len();
694 self.pending_old1 = 0;
695 self.pending_survival = 0;
696 }
697
698 pub fn reset_generation_boundaries(&mut self) {
699 self.pending_reallyold = 0;
700 self.pending_old1 = 0;
701 self.pending_survival = 0;
702 }
703
704 pub fn finish_minor_collection(&mut self) {
705 let new = self.pending_new_len();
706 self.pending_reallyold = self.pending_reallyold.saturating_add(self.pending_old1);
707 self.pending_old1 = self.pending_survival;
708 self.pending_survival = new;
709 self.debug_assert_pending_cohorts();
710 }
711
712 pub fn pop_to_be_finalized(&mut self) -> Option<T> {
713 let object = if self.to_be_finalized.is_empty() {
714 None
715 } else {
716 Some(self.to_be_finalized.remove(0))
717 };
718 if let Some(ref object) = object {
719 object.set_finalized(false);
720 }
721 object
722 }
723}
724
725#[repr(C)]
727pub struct GcHeader {
728 color: Cell<Color>,
739 age: Cell<GcAge>,
740 flags: Cell<u8>,
748 size: Cell<u32>,
755 next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
757}
758
759const HDR_FINALIZED: u8 = 1;
760const HDR_COLLECTED: u8 = 2;
761const HDR_GRAY_LISTED: u8 = 4;
762const HDR_HEAP_OWNED: u8 = 16;
769const HDR_FREED: u8 = 8;
774
775impl GcHeader {
776 fn new_white(size: usize, color: Color, flags: u8) -> Self {
777 Self {
778 color: Cell::new(color),
779 age: Cell::new(GcAge::New),
780 flags: Cell::new(flags),
781 size: Cell::new(size.min(u32::MAX as usize) as u32),
782 next: Cell::new(None),
783 }
784 }
785
786 fn flag(&self, bit: u8) -> bool {
787 self.flags.get() & bit != 0
788 }
789
790 fn set_flag(&self, bit: u8, on: bool) {
791 let f = self.flags.get();
792 self.flags.set(if on { f | bit } else { f & !bit });
793 }
794
795 pub fn finalized(&self) -> bool {
796 self.flag(HDR_FINALIZED)
797 }
798
799 pub fn set_finalized(&self, finalized: bool) {
800 self.set_flag(HDR_FINALIZED, finalized);
801 }
802
803 pub fn collected(&self) -> bool {
804 self.flag(HDR_COLLECTED)
805 }
806
807 pub fn gray_listed(&self) -> bool {
808 self.flag(HDR_GRAY_LISTED)
809 }
810
811 pub fn set_gray_listed(&self, listed: bool) {
812 self.set_flag(HDR_GRAY_LISTED, listed);
813 }
814
815 pub fn size(&self) -> usize {
816 self.size.get() as usize
817 }
818
819 pub fn set_size(&self, size: usize) {
820 self.size.set(size.min(u32::MAX as usize) as u32);
821 }
822}
823
824#[repr(C)]
826pub struct GcBox<T: ?Sized> {
827 header: GcHeader,
828 value: T,
829}
830
831impl<T: ?Sized> GcBox<T> {
832 pub fn header(&self) -> &GcHeader {
833 &self.header
834 }
835 pub fn value(&self) -> &T {
836 &self.value
837 }
838}
839
840pub struct Gc<T: ?Sized> {
842 ptr: NonNull<GcBox<T>>,
843 _marker: PhantomData<T>,
845}
846
847impl<T: ?Sized> Copy for Gc<T> {}
851impl<T: ?Sized> Clone for Gc<T> {
852 fn clone(&self) -> Self {
853 *self
854 }
855}
856
857impl<T: ?Sized> PartialEq for Gc<T> {
858 fn eq(&self, other: &Self) -> bool {
859 std::ptr::addr_eq(self.ptr.as_ptr(), other.ptr.as_ptr())
860 }
861}
862impl<T: ?Sized> Eq for Gc<T> {}
863
864impl<T: ?Sized> std::hash::Hash for Gc<T> {
865 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
866 self.ptr.as_ptr().hash(state)
867 }
868}
869
870impl<T: ?Sized> std::fmt::Debug for Gc<T> {
871 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
872 write!(f, "Gc({:p})", self.ptr.as_ptr())
873 }
874}
875
876impl<T: Trace + 'static> Gc<T> {
877 pub fn new_uncollected(value: T) -> Self {
883 DETACHED_ALLOCATIONS.with(|c| c.set(c.get() + 1));
884 let size = std::mem::size_of::<T>();
885 let boxed = Box::new(GcBox {
886 header: GcHeader::new_white(size, Color::White0, 0),
887 value,
888 });
889 Gc {
890 ptr: NonNull::new(Box::into_raw(boxed)).expect("Box::into_raw is non-null"),
891 _marker: PhantomData,
892 }
893 }
894
895 pub fn as_trace_ptr(self) -> NonNull<GcBox<dyn Trace>> {
897 self.ptr
898 }
899}
900
901impl<T: ?Sized> Gc<T> {
902 pub fn ptr_eq(a: Self, b: Self) -> bool {
904 std::ptr::addr_eq(a.ptr.as_ptr(), b.ptr.as_ptr())
905 }
906
907 pub fn identity(self) -> usize {
910 self.ptr.as_ptr() as *const () as usize
911 }
912
913 fn as_box(&self) -> &GcBox<T> {
917 let bx = unsafe { self.ptr.as_ref() };
923 debug_assert!(
924 !bx.header.flag(HDR_FREED),
925 "use-after-sweep: Gc<{}> dereferenced after the collector swept it \
926 (caught by LUA_RS_GC_QUARANTINE; this is a rooting bug — the object \
927 was reachable by execution but not by the root trace)",
928 std::any::type_name::<T>()
929 );
930 bx
931 }
932
933 fn header(&self) -> &GcHeader {
934 &self.as_box().header
935 }
936
937 pub fn is_heap_tracked(self) -> bool {
942 self.header().collected()
943 }
944
945 pub fn is_heap_owned(self) -> bool {
953 self.header().flag(HDR_HEAP_OWNED)
954 }
955
956 pub fn color(self) -> Color {
957 self.header().color.get()
958 }
959
960 pub fn set_color(self, color: Color) {
961 self.header().color.set(color);
962 }
963
964 pub fn age(self) -> GcAge {
965 self.header().age.get()
966 }
967
968 pub fn set_age(self, age: GcAge) {
969 self.header().age.set(age);
970 }
971
972 pub fn is_finalized(self) -> bool {
973 self.header().finalized()
974 }
975
976 pub fn set_finalized(self, finalized: bool) {
977 self.header().set_finalized(finalized);
978 }
979
980 pub fn account_buffer(&self, heap: &Heap, delta: isize) {
990 if delta == 0 {
991 return;
992 }
993 let header = self.header();
994 if !header.collected() {
995 return;
996 }
997 if delta >= 0 {
998 header.set_size(header.size().saturating_add(delta as usize));
999 } else {
1000 header.set_size(header.size().saturating_sub((-delta) as usize));
1001 }
1002 heap.adjust_bytes(delta);
1003 }
1004}
1005
1006impl<T: ?Sized> std::ops::Deref for Gc<T> {
1007 type Target = T;
1008 fn deref(&self) -> &T {
1009 &self.as_box().value
1010 }
1011}
1012
1013impl<T: ?Sized> AsRef<T> for Gc<T> {
1014 fn as_ref(&self) -> &T {
1015 &self.as_box().value
1016 }
1017}
1018
1019pub trait Trace {
1036 fn trace(&self, m: &mut Marker);
1037
1038 fn type_name(&self) -> &'static str {
1044 "unknown"
1045 }
1046}
1047
1048impl<T: Trace> Trace for Vec<T> {
1050 fn trace(&self, m: &mut Marker) {
1051 for item in self.iter() {
1052 item.trace(m);
1053 }
1054 }
1055}
1056
1057impl<T: Trace> Trace for Option<T> {
1058 fn trace(&self, m: &mut Marker) {
1059 if let Some(v) = self {
1060 v.trace(m);
1061 }
1062 }
1063}
1064
1065impl<T: Trace + ?Sized> Trace for Box<T> {
1066 fn trace(&self, m: &mut Marker) {
1067 (**self).trace(m);
1068 }
1069}
1070
1071impl<T: Trace + ?Sized> Trace for std::rc::Rc<T> {
1072 fn trace(&self, m: &mut Marker) {
1073 (**self).trace(m);
1074 }
1075}
1076
1077impl<T: Trace> Trace for RefCell<T> {
1078 fn trace(&self, m: &mut Marker) {
1079 self.borrow().trace(m);
1080 }
1081}
1082
1083impl<T: Trace + 'static> Trace for Gc<T> {
1085 fn trace(&self, m: &mut Marker) {
1086 m.mark(*self);
1087 }
1088}
1089
1090macro_rules! trace_noop {
1092 ($($t:ty),*) => {
1093 $(impl Trace for $t {
1094 fn trace(&self, _m: &mut Marker) {}
1095 })*
1096 };
1097}
1098trace_noop!(
1099 bool, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, char, String,
1100 str
1101);
1102
1103impl<T> Trace for std::marker::PhantomData<T> {
1104 fn trace(&self, _m: &mut Marker) {}
1105}
1106
1107#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1112pub struct MarkerStats {
1113 pub marked: usize,
1114 pub marked_young: usize,
1115 pub marked_old: usize,
1116 pub traced: usize,
1117 pub traced_young: usize,
1118 pub traced_old: usize,
1119}
1120
1121#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1123pub struct SweepStats {
1124 pub visited: usize,
1125 pub visited_young: usize,
1126 pub visited_old: usize,
1127 pub revisit: usize,
1128 pub freed: usize,
1129 pub freed_bytes: usize,
1130}
1131
1132impl SweepStats {
1133 fn record_visit(&mut self, age: GcAge) {
1134 self.visited += 1;
1135 if age.is_old() {
1136 self.visited_old += 1;
1137 } else {
1138 self.visited_young += 1;
1139 }
1140 }
1141
1142 fn record_free(&mut self, bytes: usize) {
1143 self.freed += 1;
1144 self.freed_bytes += bytes;
1145 }
1146
1147 fn add(&mut self, other: Self) {
1148 self.visited += other.visited;
1149 self.visited_young += other.visited_young;
1150 self.visited_old += other.visited_old;
1151 self.revisit += other.revisit;
1152 self.freed += other.freed;
1153 self.freed_bytes += other.freed_bytes;
1154 }
1155}
1156
1157struct OldRevisitTracker {
1158 old_revisit_ids: Vec<usize>,
1159 processed_ids: Vec<usize>,
1160}
1161
1162impl OldRevisitTracker {
1163 fn new(old_revisit: &[NonNull<GcBox<dyn Trace>>]) -> Option<Self> {
1164 if old_revisit.is_empty() {
1165 return None;
1166 }
1167 let mut old_revisit_ids: Vec<usize> = old_revisit
1168 .iter()
1169 .map(|ptr| ptr.as_ptr() as *const () as usize)
1170 .collect();
1171 old_revisit_ids.sort_unstable();
1172 old_revisit_ids.dedup();
1173 Some(Self {
1174 old_revisit_ids,
1175 processed_ids: Vec::new(),
1176 })
1177 }
1178
1179 #[inline(always)]
1180 fn record_processed(&mut self, id: usize) {
1181 if self.old_revisit_ids.binary_search(&id).is_ok() {
1182 self.processed_ids.push(id);
1183 }
1184 }
1185
1186 fn finish(&mut self) {
1187 self.processed_ids.sort_unstable();
1188 self.processed_ids.dedup();
1189 }
1190
1191 #[inline(always)]
1192 fn was_processed(&self, id: usize) -> bool {
1193 self.processed_ids.binary_search(&id).is_ok()
1194 }
1195}
1196
1197#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1199pub struct AllGcCohortStats {
1200 pub new: usize,
1201 pub survival: usize,
1202 pub old1: usize,
1203 pub old: usize,
1204}
1205
1206#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1207enum MarkerMode {
1208 Full,
1209 Minor,
1210}
1211
1212pub struct Marker {
1214 gray_queue: Vec<NonNull<GcBox<dyn Trace>>>,
1215 visited: IdentityHashSet,
1216 stats: MarkerStats,
1217 mode: MarkerMode,
1218}
1219
1220impl Marker {
1221 fn new_with_capacity(mode: MarkerMode, capacity: usize) -> Self {
1222 Self {
1223 gray_queue: Vec::with_capacity(256),
1224 visited: IdentityHashSet::with_capacity_and_hasher(
1225 capacity,
1226 IdentityBuildHasher::default(),
1227 ),
1228 stats: MarkerStats::default(),
1229 mode,
1230 }
1231 }
1232
1233 fn should_trace_age(&self, age: GcAge) -> bool {
1234 match self.mode {
1235 MarkerMode::Full => true,
1236 MarkerMode::Minor => !matches!(age, GcAge::Old),
1237 }
1238 }
1239
1240 pub fn mark<T: Trace + 'static>(&mut self, gc: Gc<T>) {
1253 let ptr: NonNull<GcBox<dyn Trace>> = gc.ptr;
1254 self.mark_box(ptr, gc.header(), gc.identity());
1255 }
1256
1257 fn mark_box(&mut self, ptr: NonNull<GcBox<dyn Trace>>, header: &GcHeader, id: usize) {
1258 debug_assert!(
1259 !header.flag(HDR_FREED),
1260 "GC marker reached a quarantined (swept) object at {id:#x} — a root \
1261 traced a stale GcRef (caught by LUA_RS_GC_QUARANTINE; bug-B class: \
1262 garbage slot fed into the marker)"
1263 );
1264 if self.visited.insert(id) {
1265 let age = header.age.get();
1266 self.stats.marked += 1;
1267 if age.is_old() {
1268 self.stats.marked_old += 1;
1269 } else {
1270 self.stats.marked_young += 1;
1271 }
1272 if self.should_trace_age(age) {
1273 header.color.set(Color::Gray);
1274 self.gray_queue.push(ptr);
1275 }
1276 }
1277 }
1278
1279 pub fn try_visit(&mut self, addr: usize) -> bool {
1286 self.visited.insert(addr)
1287 }
1288
1289 pub fn is_visited(&self, id: usize) -> bool {
1294 self.visited.contains(&id)
1295 }
1296
1297 pub fn is_marked_or_old<T: Trace + 'static>(&self, gc: Gc<T>) -> bool {
1300 self.is_visited(gc.identity())
1301 || (matches!(self.mode, MarkerMode::Minor) && gc.age().is_old())
1302 }
1303
1304 pub fn visited_count(&self) -> usize {
1308 self.visited.len()
1309 }
1310
1311 pub fn stats(&self) -> MarkerStats {
1313 self.stats
1314 }
1315
1316 pub fn drain_gray_queue(&mut self) {
1325 while let Some(gray_ptr) = self.gray_queue.pop() {
1326 unsafe {
1327 let bx = gray_ptr.as_ref();
1328 self.stats.traced += 1;
1329 if bx.header.age.get().is_old() {
1330 self.stats.traced_old += 1;
1331 } else {
1332 self.stats.traced_young += 1;
1333 }
1334 bx.header.color.set(Color::Black);
1335 bx.value.trace(self);
1336 }
1337 }
1338 }
1339}
1340
1341#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1360pub enum GcState {
1361 Pause,
1362 Propagate,
1363 EnterAtomic,
1364 Atomic,
1365 SweepAllGc,
1366 SweepFinObj,
1367 SweepToBeFnz,
1368 SweepEnd,
1369 CallFin,
1370}
1371
1372impl GcState {
1373 pub fn is_pause(self) -> bool {
1374 matches!(self, GcState::Pause)
1375 }
1376 pub fn is_propagate(self) -> bool {
1377 matches!(self, GcState::Propagate)
1378 }
1379 pub fn is_invariant(self) -> bool {
1380 matches!(
1381 self,
1382 GcState::Propagate | GcState::EnterAtomic | GcState::Atomic
1383 )
1384 }
1385 pub fn is_sweep(self) -> bool {
1386 matches!(
1387 self,
1388 GcState::SweepAllGc | GcState::SweepFinObj | GcState::SweepToBeFnz | GcState::SweepEnd
1389 )
1390 }
1391}
1392
1393#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1395pub enum StepOutcome {
1396 Paused,
1398 InProgress,
1401 SkippedStopped,
1404}
1405
1406#[derive(Copy, Clone, Debug)]
1414pub struct StepBudget {
1415 pub remaining_work: isize,
1416 pub max_credit: isize,
1417}
1418
1419impl StepBudget {
1420 pub fn from_work(work: isize) -> Self {
1422 Self {
1423 remaining_work: work.max(1),
1424 max_credit: work.max(1),
1425 }
1426 }
1427}
1428
1429const GC_MIN_THRESHOLD: usize = 1024 * 1024;
1444
1445pub struct Heap {
1446 head: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1449 finobj: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1451 tobefnz: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1453 survival: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1456 old1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1459 reallyold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1462 firstold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1465 finobjsur: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1467 finobjold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1469 finobjrold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1471 bytes: Cell<usize>,
1473 objects: Cell<usize>,
1475 current_white: Cell<Color>,
1477 allocation_tokens: RefCell<IdentityHashMap<usize>>,
1481 next_allocation_token: Cell<usize>,
1483 threshold: Cell<usize>,
1485 stress: bool,
1492 quarantine: bool,
1503 quarantined: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1507 uncollected: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1515 closed: Cell<bool>,
1524 tearing_down: Cell<bool>,
1532 bootstrap_depth: std::rc::Rc<Cell<usize>>,
1545 pause_multiplier: Cell<usize>,
1547 state: Cell<GcState>,
1549 paused: Cell<bool>,
1552 collections: Cell<usize>,
1554 minor_collections: Cell<usize>,
1556 full_collections: Cell<usize>,
1558 last_mark_stats: Cell<MarkerStats>,
1560 last_sweep_stats: Cell<SweepStats>,
1562 grayagain: RefCell<Vec<NonNull<GcBox<dyn Trace>>>>,
1571 grayagain_scratch: RefCell<Vec<NonNull<GcBox<dyn Trace>>>>,
1578 marker: RefCell<Option<Marker>>,
1581 marker_pool: RefCell<Option<(Vec<NonNull<GcBox<dyn Trace>>>, IdentityHashSet)>>,
1587 sweep_prev_next: Cell<Option<NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>>>,
1592 v51_udata_roster: RefCell<Vec<std::rc::Rc<dyn Udata51Probe>>>,
1599}
1600
1601impl Heap {
1602 pub fn new() -> std::rc::Rc<Self> {
1611 std::rc::Rc::new(Self {
1612 head: Cell::new(None),
1613 finobj: Cell::new(None),
1614 tobefnz: Cell::new(None),
1615 survival: Cell::new(None),
1616 old1: Cell::new(None),
1617 reallyold: Cell::new(None),
1618 firstold1: Cell::new(None),
1619 finobjsur: Cell::new(None),
1620 finobjold1: Cell::new(None),
1621 finobjrold: Cell::new(None),
1622 bytes: Cell::new(0),
1623 objects: Cell::new(0),
1624 current_white: Cell::new(Color::White0),
1625 allocation_tokens: RefCell::new(IdentityHashMap::default()),
1626 next_allocation_token: Cell::new(1),
1627 threshold: Cell::new(64 * 1024), stress: std::env::var_os("LUA_RS_GC_STRESS").is_some_and(|v| v == "1"),
1629 quarantine: std::env::var_os("LUA_RS_GC_QUARANTINE").is_some_and(|v| v == "1"),
1630 quarantined: Cell::new(None),
1631 uncollected: Cell::new(None),
1632 closed: Cell::new(false),
1633 tearing_down: Cell::new(false),
1634 bootstrap_depth: std::rc::Rc::new(Cell::new(0)),
1635 pause_multiplier: Cell::new(200), state: Cell::new(GcState::Pause),
1637 paused: Cell::new(true), collections: Cell::new(0),
1639 minor_collections: Cell::new(0),
1640 full_collections: Cell::new(0),
1641 last_mark_stats: Cell::new(MarkerStats::default()),
1642 last_sweep_stats: Cell::new(SweepStats::default()),
1643 grayagain: RefCell::new(Vec::new()),
1644 grayagain_scratch: RefCell::new(Vec::new()),
1645 marker: RefCell::new(None),
1646 marker_pool: RefCell::new(None),
1647 sweep_prev_next: Cell::new(None),
1648 v51_udata_roster: RefCell::new(Vec::new()),
1649 })
1650 }
1651
1652 pub fn unpause(&self) {
1655 self.paused.set(false);
1656 }
1657
1658 pub fn is_paused(&self) -> bool {
1659 self.paused.get()
1660 }
1661
1662 pub fn is_closed(&self) -> bool {
1670 self.closed.get()
1671 }
1672
1673 pub fn begin_bootstrap(&self) {
1681 self.bootstrap_depth.set(
1682 self.bootstrap_depth
1683 .get()
1684 .checked_add(1)
1685 .expect("Heap bootstrap depth overflow"),
1686 );
1687 }
1688
1689 pub fn end_bootstrap(&self) {
1692 let depth = self.bootstrap_depth.get();
1693 debug_assert!(depth > 0, "Heap::end_bootstrap without begin_bootstrap");
1694 self.bootstrap_depth.set(depth.saturating_sub(1));
1695 }
1696
1697 pub fn is_bootstrapping(&self) -> bool {
1698 self.bootstrap_depth.get() != 0
1699 }
1700
1701 pub fn bootstrap_scope(&self) -> BootstrapScope {
1708 self.begin_bootstrap();
1709 BootstrapScope {
1710 depth: std::rc::Rc::clone(&self.bootstrap_depth),
1711 }
1712 }
1713
1714 fn assert_open(&self) {
1721 if self.closed.get() && !self.tearing_down.get() {
1722 panic!("allocation into a closed heap — a HeapGuard outlived close()");
1723 }
1724 }
1725
1726 pub fn allocate<T: Trace + 'static>(&self, value: T) -> Gc<T> {
1730 self.assert_open();
1731 if self.is_bootstrapping() {
1732 return self.allocate_uncollected(value);
1733 }
1734 let size = std::mem::size_of::<GcBox<T>>();
1735 let boxed = Box::new(GcBox {
1736 header: GcHeader::new_white(
1737 size,
1738 self.current_white.get(),
1739 HDR_COLLECTED | HDR_HEAP_OWNED,
1740 ),
1741 value,
1742 });
1743 boxed.header.next.set(self.head.get());
1744 let raw: *mut GcBox<T> = Box::into_raw(boxed);
1745 let ptr: NonNull<GcBox<T>> = NonNull::new(raw).expect("Box::into_raw is non-null");
1746 let dyn_ptr: NonNull<GcBox<dyn Trace>> = ptr;
1747 self.head.set(Some(dyn_ptr));
1748 self.bytes.set(self.bytes.get() + size);
1749 self.objects.set(self.objects.get() + 1);
1750 Gc {
1751 ptr,
1752 _marker: PhantomData,
1753 }
1754 }
1755
1756 pub fn allocate_uncollected<T: Trace + 'static>(&self, value: T) -> Gc<T> {
1767 self.assert_open();
1768 let size = std::mem::size_of::<GcBox<T>>();
1769 let boxed = Box::new(GcBox {
1770 header: GcHeader::new_white(size, self.current_white.get(), HDR_HEAP_OWNED),
1771 value,
1772 });
1773 boxed.header.next.set(self.uncollected.get());
1774 let raw: *mut GcBox<T> = Box::into_raw(boxed);
1775 let ptr: NonNull<GcBox<T>> = NonNull::new(raw).expect("Box::into_raw is non-null");
1776 let dyn_ptr: NonNull<GcBox<dyn Trace>> = ptr;
1777 self.uncollected.set(Some(dyn_ptr));
1778 Gc {
1779 ptr,
1780 _marker: PhantomData,
1781 }
1782 }
1783
1784 pub fn bytes_used(&self) -> usize {
1786 self.bytes.get()
1787 }
1788
1789 pub fn adjust_bytes(&self, delta: isize) {
1794 if delta >= 0 {
1795 self.bytes
1796 .set(self.bytes.get().saturating_add(delta as usize));
1797 } else {
1798 self.bytes
1799 .set(self.bytes.get().saturating_sub((-delta) as usize));
1800 }
1801 }
1802
1803 pub fn threshold_bytes(&self) -> usize {
1808 self.threshold.get()
1809 }
1810
1811 pub fn set_threshold_bytes(&self, threshold: usize) {
1817 self.threshold.set(threshold.max(1));
1818 }
1819
1820 pub fn would_collect(&self) -> bool {
1825 if self.collection_inert() {
1826 return false;
1827 }
1828 if self.stress {
1829 return true;
1830 }
1831 self.bytes.get() >= self.threshold.get()
1832 }
1833
1834 fn collection_inert(&self) -> bool {
1844 self.paused.get() || self.closed.get()
1845 }
1846
1847 pub fn collections(&self) -> usize {
1848 self.collections.get()
1849 }
1850
1851 pub fn minor_collections(&self) -> usize {
1852 self.minor_collections.get()
1853 }
1854
1855 pub fn full_collections(&self) -> usize {
1856 self.full_collections.get()
1857 }
1858
1859 pub fn last_mark_stats(&self) -> MarkerStats {
1860 self.last_mark_stats.get()
1861 }
1862
1863 pub fn last_sweep_stats(&self) -> SweepStats {
1864 self.last_sweep_stats.get()
1865 }
1866
1867 pub fn allgc_cohort_stats(&self) -> AllGcCohortStats {
1868 let survival = self.survival.get();
1869 let old1 = self.old1.get();
1870 let reallyold = self.reallyold.get();
1871 let mut stats = AllGcCohortStats::default();
1872 let mut cursor = self.head.get();
1873 let mut seen = IdentityHashSet::default();
1874 let mut cohort = 0u8;
1875 while let Some(ptr) = cursor {
1876 let id = ptr.as_ptr() as *const () as usize;
1877 if !seen.insert(id) {
1878 break;
1879 }
1880 if Some(ptr) == reallyold {
1881 cohort = 3;
1882 } else if Some(ptr) == old1 {
1883 cohort = 2;
1884 } else if Some(ptr) == survival {
1885 cohort = 1;
1886 }
1887 match cohort {
1888 0 => stats.new += 1,
1889 1 => stats.survival += 1,
1890 2 => stats.old1 += 1,
1891 _ => stats.old += 1,
1892 }
1893 cursor = self.header_from_ptr(ptr).next.get();
1894 }
1895 stats
1896 }
1897
1898 fn next_token(&self) -> usize {
1899 let token = self.next_allocation_token.get().max(1);
1900 let next = token.checked_add(1).unwrap_or(1).max(1);
1901 self.next_allocation_token.set(next);
1902 token
1903 }
1904
1905 fn current_white(&self) -> Color {
1906 self.current_white.get()
1907 }
1908
1909 fn other_white(&self) -> Color {
1910 self.current_white.get().other_white()
1911 }
1912
1913 fn flip_current_white(&self) {
1914 self.current_white.set(self.other_white());
1915 }
1916
1917 fn for_each_list_header(
1918 &self,
1919 head: Option<NonNull<GcBox<dyn Trace>>>,
1920 f: &mut impl FnMut(&GcHeader),
1921 ) {
1922 let mut cursor = head;
1923 while let Some(ptr) = cursor {
1924 let header = self.header_from_ptr(ptr);
1925 cursor = header.next.get();
1926 f(header);
1927 }
1928 }
1929
1930 fn for_each_header(&self, mut f: impl FnMut(&GcHeader)) {
1931 self.for_each_list_header(self.head.get(), &mut f);
1932 self.for_each_list_header(self.finobj.get(), &mut f);
1933 self.for_each_list_header(self.tobefnz.get(), &mut f);
1934 }
1935
1936 fn header_from_ptr<'a>(&'a self, ptr: NonNull<GcBox<dyn Trace>>) -> &'a GcHeader {
1937 unsafe { &(*ptr.as_ptr()).header }
1938 }
1939
1940 fn release_box(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1947 if self.quarantine {
1948 let header = self.header_from_ptr(ptr);
1949 header.set_flag(HDR_FREED, true);
1950 header.next.set(self.quarantined.get());
1951 self.quarantined.set(Some(ptr));
1952 } else {
1953 unsafe {
1957 let _ = Box::from_raw(ptr.as_ptr());
1958 }
1959 }
1960 }
1961
1962 fn clear_generation_cursors(&self) {
1963 self.survival.set(None);
1964 self.old1.set(None);
1965 self.reallyold.set(None);
1966 self.firstold1.set(None);
1967 self.finobjsur.set(None);
1968 self.finobjold1.set(None);
1969 self.finobjrold.set(None);
1970 self.clear_grayagain();
1971 }
1972
1973 fn set_all_cursors_to_head(&self) {
1974 let head = self.head.get();
1975 self.survival.set(head);
1976 self.old1.set(head);
1977 self.reallyold.set(head);
1978 self.firstold1.set(None);
1979 let finobj = self.finobj.get();
1980 self.finobjsur.set(finobj);
1981 self.finobjold1.set(finobj);
1982 self.finobjrold.set(finobj);
1983 self.clear_grayagain();
1984 }
1985
1986 fn correct_generation_pointers(
1987 &self,
1988 removed: NonNull<GcBox<dyn Trace>>,
1989 next: Option<NonNull<GcBox<dyn Trace>>>,
1990 ) {
1991 if self.survival.get() == Some(removed) {
1992 self.survival.set(next);
1993 }
1994 if self.old1.get() == Some(removed) {
1995 self.old1.set(next);
1996 }
1997 if self.reallyold.get() == Some(removed) {
1998 self.reallyold.set(next);
1999 }
2000 if self.firstold1.get() == Some(removed) {
2001 self.firstold1.set(next);
2002 }
2003 if self.finobjsur.get() == Some(removed) {
2004 self.finobjsur.set(next);
2005 }
2006 if self.finobjold1.get() == Some(removed) {
2007 self.finobjold1.set(next);
2008 }
2009 if self.finobjrold.get() == Some(removed) {
2010 self.finobjrold.set(next);
2011 }
2012 if self.header_from_ptr(removed).gray_listed() {
2013 self.unlink_grayagain(removed);
2014 }
2015 }
2016
2017 fn unlink_from_list(
2018 &self,
2019 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
2020 ptr: NonNull<GcBox<dyn Trace>>,
2021 ) -> bool {
2022 let mut prev_cell = list;
2023 loop {
2024 let Some(current) = prev_cell.get() else {
2025 return false;
2026 };
2027 let header = self.header_from_ptr(current);
2028 let next = header.next.get();
2029 if std::ptr::addr_eq(current.as_ptr(), ptr.as_ptr()) {
2030 prev_cell.set(next);
2031 let prev_next_ptr = NonNull::from(prev_cell);
2032 let removed_next_ptr = NonNull::from(&self.header_from_ptr(ptr).next);
2033 if self.sweep_prev_next.get() == Some(removed_next_ptr) {
2034 self.sweep_prev_next.set(Some(prev_next_ptr));
2035 }
2036 self.correct_generation_pointers(ptr, next);
2037 header.next.set(None);
2038 return true;
2039 }
2040 prev_cell = &header.next;
2041 }
2042 }
2043
2044 fn link_to_head(
2045 &self,
2046 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
2047 ptr: NonNull<GcBox<dyn Trace>>,
2048 ) {
2049 let header = self.header_from_ptr(ptr);
2050 header.next.set(list.get());
2051 list.set(Some(ptr));
2052 }
2053
2054 fn link_to_tail(
2055 &self,
2056 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
2057 ptr: NonNull<GcBox<dyn Trace>>,
2058 ) {
2059 let mut last_cell = list;
2060 loop {
2061 let Some(current) = last_cell.get() else {
2062 let header = self.header_from_ptr(ptr);
2063 header.next.set(None);
2064 last_cell.set(Some(ptr));
2065 return;
2066 };
2067 last_cell = &self.header_from_ptr(current).next;
2068 }
2069 }
2070
2071 pub fn move_allgc_to_finobj(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
2072 let header = self.header_from_ptr(ptr);
2073 if !header.collected() {
2074 return false;
2075 }
2076 if !self.unlink_from_list(&self.head, ptr) {
2077 return false;
2078 }
2079 if self.state.get().is_sweep() {
2080 header.color.set(self.current_white());
2081 }
2082 self.link_to_head(&self.finobj, ptr);
2083 true
2084 }
2085
2086 pub fn move_finobj_to_tobefnz(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
2087 if !self.unlink_from_list(&self.finobj, ptr) {
2088 return false;
2089 }
2090 self.link_to_tail(&self.tobefnz, ptr);
2091 true
2092 }
2093
2094 pub fn move_tobefnz_to_allgc(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
2095 let header = self.header_from_ptr(ptr);
2096 if !self.unlink_from_list(&self.tobefnz, ptr) {
2097 return false;
2098 }
2099 if self.state.get().is_sweep() {
2100 header.color.set(self.current_white());
2101 }
2102 self.link_to_head(&self.head, ptr);
2103 if header.age.get() == GcAge::Old1 {
2104 self.firstold1.set(Some(ptr));
2105 }
2106 true
2107 }
2108
2109 fn remember_minor_revisit(&self, ptr: NonNull<GcBox<dyn Trace>>) {
2126 if self.closed.get() {
2127 return;
2128 }
2129 let header = self.header_from_ptr(ptr);
2130 if header.gray_listed() {
2131 return;
2132 }
2133 header.set_gray_listed(true);
2134 self.grayagain.borrow_mut().push(ptr);
2135 }
2136
2137 fn mark_minor_revisit_objects(&self, marker: &mut Marker) {
2142 let grayagain = self.grayagain.borrow();
2143 for &ptr in grayagain.iter() {
2144 let header = self.header_from_ptr(ptr);
2145 let id = ptr.as_ptr() as *const () as usize;
2146 marker.mark_box(ptr, header, id);
2147 }
2148 }
2149
2150 fn clear_grayagain(&self) {
2153 let mut grayagain = self.grayagain.borrow_mut();
2154 for &ptr in grayagain.iter() {
2155 self.header_from_ptr(ptr).set_gray_listed(false);
2156 }
2157 grayagain.clear();
2158 }
2159
2160 fn take_grayagain(&self) -> Vec<NonNull<GcBox<dyn Trace>>> {
2165 let objects = std::mem::take(&mut *self.grayagain.borrow_mut());
2166 for &ptr in &objects {
2167 self.header_from_ptr(ptr).set_gray_listed(false);
2168 }
2169 objects
2170 }
2171
2172 fn replace_grayagain(&self, objects: Vec<NonNull<GcBox<dyn Trace>>>) {
2177 self.clear_grayagain();
2178 for &ptr in &objects {
2179 self.header_from_ptr(ptr).set_gray_listed(true);
2180 }
2181 *self.grayagain.borrow_mut() = objects;
2182 }
2183
2184 fn unlink_grayagain(&self, removed: NonNull<GcBox<dyn Trace>>) {
2190 self.header_from_ptr(removed).set_gray_listed(false);
2191 self.grayagain
2192 .borrow_mut()
2193 .retain(|ptr| !std::ptr::addr_eq(ptr.as_ptr(), removed.as_ptr()));
2194 }
2195
2196 pub fn grayagain_count(&self) -> usize {
2200 self.grayagain.borrow().len()
2201 }
2202
2203 pub fn register_v51_udata(&self, probe: std::rc::Rc<dyn Udata51Probe>) {
2210 self.v51_udata_roster.borrow_mut().push(probe);
2211 }
2212
2213 pub fn scan_v51_finalizable(&self) -> Vec<std::rc::Rc<dyn Udata51Probe>> {
2223 let mut roster = self.v51_udata_roster.borrow_mut();
2224 roster.retain(|probe| probe.is_alive());
2225 roster.clone()
2226 }
2227
2228 pub fn allocation_token(&self, identity: usize) -> Option<usize> {
2236 self.allocation_tokens.borrow().get(&identity).copied()
2237 }
2238
2239 pub fn register_allocation_token(&self, identity: usize) -> usize {
2263 if self.closed.get() {
2264 return 0;
2265 }
2266 let mut tokens = self.allocation_tokens.borrow_mut();
2267 if let Some(token) = tokens.get(&identity) {
2268 return *token;
2269 }
2270 let token = self.next_token();
2271 tokens.insert(identity, token);
2272 token
2273 }
2274
2275 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
2282 if self.closed.get() {
2283 return false;
2284 }
2285 self.allocation_token(identity) == Some(token)
2286 }
2287
2288 pub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2298 where
2299 P: Trace + 'static,
2300 C: Trace + 'static,
2301 {
2302 if self.paused.get() || self.state.get().is_pause() {
2303 return;
2304 }
2305 if parent.header().color.get() != Color::Black {
2306 return;
2307 }
2308 if !child.header().color.get().is_white() {
2309 return;
2310 }
2311 child.header().color.set(Color::Gray);
2312 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
2313 if let Some(m) = m_opt.as_mut() {
2314 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
2315 m.gray_queue.push(ptr);
2316 m.visited.insert(child.identity());
2317 }
2318 }
2319 }
2320
2321 pub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2324 where
2325 P: Trace + 'static,
2326 C: Trace + 'static,
2327 {
2328 if self.paused.get() || self.state.get().is_pause() {
2329 return;
2330 }
2331 if parent.header().color.get() != Color::Black {
2332 return;
2333 }
2334 if !child.header().color.get().is_white() {
2335 return;
2336 }
2337 parent.header().color.set(Color::Gray);
2338 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
2339 if let Some(m) = m_opt.as_mut() {
2340 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
2341 m.gray_queue.push(ptr);
2342 m.visited.insert(parent.identity());
2343 }
2344 }
2345 }
2346
2347 pub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2352 where
2353 P: Trace + 'static,
2354 C: Trace + 'static,
2355 {
2356 if parent.age().is_old() && !child.age().is_old() {
2357 child.set_age(GcAge::Old0);
2358 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
2359 self.remember_minor_revisit(ptr);
2360 }
2361 self.barrier(parent, child);
2362 }
2363
2364 pub fn generational_backward_barrier<P>(&self, parent: Gc<P>)
2368 where
2369 P: Trace + 'static,
2370 {
2371 if parent.age().is_old() {
2372 parent.set_color(Color::Gray);
2373 parent.set_age(GcAge::Touched1);
2374 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
2375 self.remember_minor_revisit(ptr);
2376 }
2377 }
2378
2379 pub fn step(&self, roots: &dyn Trace) {
2383 self.step_with_post_mark(roots, |_: &mut Marker| {});
2384 }
2385
2386 pub fn step_with_post_mark<F: FnMut(&mut Marker)>(&self, roots: &dyn Trace, post_mark: F) {
2391 if self.collection_inert() {
2392 return;
2393 }
2394 if !self.stress && self.bytes.get() < self.threshold.get() {
2395 return;
2396 }
2397 self.full_collect_with_post_mark(roots, post_mark);
2398 }
2399
2400 pub fn full_collect(&self, roots: &dyn Trace) {
2403 self.full_collect_with_post_mark(roots, |_: &mut Marker| {});
2404 }
2405
2406 pub fn mark_only_with_post_mark<F: FnMut(&mut Marker)>(
2411 &self,
2412 roots: &dyn Trace,
2413 mut post_mark: F,
2414 ) {
2415 if self.collection_inert() {
2416 return;
2417 }
2418 let mut marker = self.marker_from_pool(MarkerMode::Full);
2419 roots.trace(&mut marker);
2420 marker.drain_gray_queue();
2421 post_mark(&mut marker);
2422 marker.drain_gray_queue();
2423 self.last_mark_stats.set(marker.stats());
2424 self.recycle_marker(marker);
2425 }
2426
2427 pub fn promote_all_to_old(&self) {
2430 self.for_each_header(|header| {
2431 header.age.set(GcAge::Old);
2432 header.color.set(Color::Black);
2433 });
2434 self.set_all_cursors_to_head();
2435 }
2436
2437 pub fn reset_all_ages(&self) {
2440 let current_white = self.current_white();
2441 self.for_each_header(|header| {
2442 header.age.set(GcAge::New);
2443 header.color.set(current_white);
2444 });
2445 self.clear_generation_cursors();
2446 }
2447
2448 pub fn minor_collect_with_post_mark<F: FnMut(&mut Marker)>(
2455 &self,
2456 roots: &dyn Trace,
2457 mut post_mark: F,
2458 ) {
2459 if self.collection_inert() {
2460 return;
2461 }
2462 if !self.state.get().is_pause() {
2463 self.abort_cycle();
2464 }
2465
2466 self.state.set(GcState::Propagate);
2467 let mut marker = self.marker_from_pool(MarkerMode::Minor);
2468 self.last_sweep_stats.set(SweepStats::default());
2469 self.mark_minor_revisit_objects(&mut marker);
2470 roots.trace(&mut marker);
2471 marker.drain_gray_queue();
2472
2473 self.state.set(GcState::EnterAtomic);
2474 self.state.set(GcState::Atomic);
2475 post_mark(&mut marker);
2476 marker.drain_gray_queue();
2477 self.last_mark_stats.set(marker.stats());
2478 self.recycle_marker(marker);
2479
2480 self.state.set(GcState::SweepAllGc);
2481 self.sweep_young();
2482 self.recycle_marker_cell();
2483 self.sweep_prev_next.set(None);
2484 self.state.set(GcState::Pause);
2485 self.collections.set(self.collections.get() + 1);
2486 self.minor_collections.set(self.minor_collections.get() + 1);
2487 }
2488
2489 pub fn full_collect_with_post_mark<F: FnMut(&mut Marker)>(
2496 &self,
2497 roots: &dyn Trace,
2498 mut post_mark: F,
2499 ) {
2500 if self.collection_inert() {
2501 return;
2502 }
2503 if !self.state.get().is_pause() {
2504 self.abort_cycle();
2505 }
2506 self.full_collections.set(self.full_collections.get() + 1);
2507 let unlimited = StepBudget {
2508 remaining_work: isize::MAX,
2509 max_credit: isize::MAX,
2510 };
2511 loop {
2512 let outcome = self.incremental_step_with_post_mark(roots, unlimited, &mut post_mark);
2513 if matches!(outcome, StepOutcome::Paused | StepOutcome::SkippedStopped) {
2514 break;
2515 }
2516 }
2517 }
2518
2519 pub fn incremental_step_with_post_mark<F: FnMut(&mut Marker)>(
2534 &self,
2535 roots: &dyn Trace,
2536 mut budget: StepBudget,
2537 mut post_mark: F,
2538 ) -> StepOutcome {
2539 if self.collection_inert() {
2540 return StepOutcome::SkippedStopped;
2541 }
2542 self.run_budgeted(roots, &mut budget, &mut post_mark);
2543 if self.state.get().is_pause() {
2544 StepOutcome::Paused
2545 } else {
2546 StepOutcome::InProgress
2547 }
2548 }
2549
2550 fn run_budgeted(
2551 &self,
2552 roots: &dyn Trace,
2553 budget: &mut StepBudget,
2554 post_mark: &mut dyn FnMut(&mut Marker),
2555 ) -> bool {
2556 self.run_budgeted_until(roots, budget, post_mark, None)
2557 }
2558
2559 fn run_budgeted_until(
2560 &self,
2561 roots: &dyn Trace,
2562 budget: &mut StepBudget,
2563 post_mark: &mut dyn FnMut(&mut Marker),
2564 stop_at: Option<GcState>,
2565 ) -> bool {
2566 let mut did_work = false;
2567 loop {
2568 if stop_at == Some(self.state.get()) {
2569 return did_work;
2570 }
2571 if budget.remaining_work <= -budget.max_credit {
2572 return did_work;
2573 }
2574 match self.state.get() {
2575 GcState::Pause => {
2576 self.start_cycle(roots);
2577 self.state.set(GcState::Propagate);
2578 budget.remaining_work -= 1;
2579 did_work = true;
2580 if stop_at == Some(GcState::Propagate) {
2581 return did_work;
2582 }
2583 }
2584 GcState::Propagate => {
2585 let work = self.drain_gray_budgeted(budget.remaining_work.max(1));
2586 budget.remaining_work -= work as isize;
2587 did_work = did_work || work > 0;
2588 let empty = {
2589 let m = self.marker.borrow();
2590 m.as_ref().map(|m| m.gray_queue.is_empty()).unwrap_or(true)
2591 };
2592 if empty {
2593 self.state.set(GcState::EnterAtomic);
2594 if stop_at == Some(GcState::EnterAtomic) {
2595 return did_work;
2596 }
2597 } else if budget.remaining_work <= 0 {
2598 return did_work;
2599 }
2600 }
2601 GcState::EnterAtomic => {
2602 self.state.set(GcState::Atomic);
2603 budget.remaining_work -= 1;
2604 did_work = true;
2605 if stop_at == Some(GcState::Atomic) || budget.remaining_work <= 0 {
2606 return did_work;
2607 }
2608 }
2609 GcState::Atomic => {
2610 self.run_atomic(post_mark);
2611 self.state.set(GcState::SweepAllGc);
2612 budget.remaining_work -= 1;
2613 did_work = true;
2614 if stop_at == Some(GcState::SweepAllGc) {
2615 return did_work;
2616 }
2617 }
2618 GcState::SweepAllGc => {
2619 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2620 budget.remaining_work -= work as isize;
2621 did_work = did_work || work > 0;
2622 if self.sweep_prev_next.get().is_none() {
2623 self.state.set(GcState::SweepFinObj);
2624 self.sweep_prev_next.set(Some(NonNull::from(&self.finobj)));
2625 if stop_at == Some(GcState::SweepFinObj) {
2626 return did_work;
2627 }
2628 } else if budget.remaining_work <= 0 {
2629 return did_work;
2630 }
2631 }
2632 GcState::SweepFinObj => {
2633 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2634 budget.remaining_work -= work as isize;
2635 did_work = did_work || work > 0;
2636 if self.sweep_prev_next.get().is_none() {
2637 self.state.set(GcState::SweepToBeFnz);
2638 self.sweep_prev_next.set(Some(NonNull::from(&self.tobefnz)));
2639 if stop_at == Some(GcState::SweepToBeFnz) {
2640 return did_work;
2641 }
2642 } else if budget.remaining_work <= 0 {
2643 return did_work;
2644 }
2645 }
2646 GcState::SweepToBeFnz => {
2647 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2648 budget.remaining_work -= work as isize;
2649 did_work = did_work || work > 0;
2650 if self.sweep_prev_next.get().is_none() {
2651 self.state.set(GcState::SweepEnd);
2652 if stop_at == Some(GcState::SweepEnd) {
2653 return did_work;
2654 }
2655 } else if budget.remaining_work <= 0 {
2656 return did_work;
2657 }
2658 }
2659 GcState::SweepEnd => {
2660 self.state.set(GcState::CallFin);
2661 budget.remaining_work -= 1;
2662 did_work = true;
2663 if stop_at == Some(GcState::CallFin) || budget.remaining_work <= 0 {
2664 return did_work;
2665 }
2666 }
2667 GcState::CallFin => {
2668 self.finish_cycle();
2669 self.state.set(GcState::Pause);
2670 if stop_at == Some(GcState::Pause) {
2671 return did_work;
2672 }
2673 return did_work;
2674 }
2675 }
2676 }
2677 }
2678
2679 pub fn incremental_run_until_state_with_post_mark<F: FnMut(&mut Marker)>(
2684 &self,
2685 roots: &dyn Trace,
2686 target: GcState,
2687 max_work: isize,
2688 mut post_mark: F,
2689 ) -> StepOutcome {
2690 if self.collection_inert() {
2691 return StepOutcome::SkippedStopped;
2692 }
2693 let work = max_work.max(1);
2694 let mut budget = StepBudget {
2695 remaining_work: work,
2696 max_credit: work,
2697 };
2698 self.run_budgeted_until(roots, &mut budget, &mut post_mark, Some(target));
2699 if self.state.get().is_pause() {
2700 StepOutcome::Paused
2701 } else {
2702 StepOutcome::InProgress
2703 }
2704 }
2705
2706 fn marker_from_pool(&self, mode: MarkerMode) -> Marker {
2709 match self.marker_pool.borrow_mut().take() {
2710 Some((gray_queue, visited)) => Marker {
2711 gray_queue,
2712 visited,
2713 stats: MarkerStats::default(),
2714 mode,
2715 },
2716 None => Marker::new_with_capacity(mode, self.objects.get()),
2717 }
2718 }
2719
2720 fn recycle_marker(&self, marker: Marker) {
2721 let Marker {
2722 mut gray_queue,
2723 mut visited,
2724 ..
2725 } = marker;
2726 gray_queue.clear();
2727 visited.clear();
2728 *self.marker_pool.borrow_mut() = Some((gray_queue, visited));
2729 }
2730
2731 fn recycle_marker_cell(&self) {
2732 if let Some(marker) = self.marker.borrow_mut().take() {
2733 self.recycle_marker(marker);
2734 }
2735 }
2736
2737 fn start_cycle(&self, roots: &dyn Trace) {
2738 self.flip_current_white();
2739 let dead_white = self.other_white();
2740 self.for_each_header(|header| {
2741 header.color.set(dead_white);
2742 });
2743 let mut marker = self.marker_from_pool(MarkerMode::Full);
2744 roots.trace(&mut marker);
2745 *self.marker.borrow_mut() = Some(marker);
2746 self.sweep_prev_next.set(None);
2747 }
2748
2749 fn drain_gray_budgeted(&self, max_units: isize) -> usize {
2750 let mut m_opt = self.marker.borrow_mut();
2751 let marker = match m_opt.as_mut() {
2752 Some(m) => m,
2753 None => return 0,
2754 };
2755 let mut work = 0usize;
2756 let mut budget = max_units;
2757 while budget > 0 {
2758 let next = match marker.gray_queue.pop() {
2759 Some(p) => p,
2760 None => break,
2761 };
2762 unsafe {
2763 let bx = next.as_ref();
2764 marker.stats.traced += 1;
2765 if bx.header.age.get().is_old() {
2766 marker.stats.traced_old += 1;
2767 } else {
2768 marker.stats.traced_young += 1;
2769 }
2770 bx.header.color.set(Color::Black);
2771 bx.value.trace(marker);
2772 }
2773 work += 1;
2774 budget -= 1;
2775 }
2776 work
2777 }
2778
2779 fn run_atomic(&self, post_mark: &mut dyn FnMut(&mut Marker)) {
2780 let mut m_opt = self.marker.borrow_mut();
2781 if let Some(marker) = m_opt.as_mut() {
2782 marker.drain_gray_queue();
2783 post_mark(marker);
2784 marker.drain_gray_queue();
2785 }
2786 self.sweep_prev_next.set(Some(NonNull::from(&self.head)));
2787 self.last_sweep_stats.set(SweepStats::default());
2788 }
2789
2790 fn sweep_budgeted(&self, max_units: isize) -> usize {
2791 let mut work = 0usize;
2792 let mut budget = max_units;
2793 let mut freed_bytes = 0usize;
2794 let mut stats = SweepStats::default();
2795 let current_white = self.current_white();
2796 let dead_white = self.other_white();
2797 let mut prev_next_ptr = match self.sweep_prev_next.get() {
2798 Some(p) => p,
2799 None => return 0,
2800 };
2801 while budget > 0 {
2802 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2803 let cursor = prev_cell.get();
2804 let ptr = match cursor {
2805 Some(p) => p,
2806 None => {
2807 self.sweep_prev_next.set(None);
2808 break;
2809 }
2810 };
2811 let header = self.header_from_ptr(ptr);
2812 let next = header.next.get();
2813 let age = header.age.get();
2814 stats.record_visit(age);
2815 let color = header.color.get();
2816 if color == dead_white {
2817 prev_cell.set(next);
2818 let size = header.size();
2819 freed_bytes += size;
2820 stats.record_free(size);
2821 self.correct_generation_pointers(ptr, next);
2822 self.allocation_tokens
2823 .borrow_mut()
2824 .remove(&(ptr.as_ptr() as *const () as usize));
2825 self.objects.set(self.objects.get().saturating_sub(1));
2826 self.release_box(ptr);
2827 } else {
2828 if matches!(color, Color::Black | Color::Gray) {
2829 header.color.set(current_white);
2830 }
2831 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2832 self.sweep_prev_next.set(Some(prev_next_ptr));
2833 }
2834 work += 1;
2835 budget -= 1;
2836 }
2837 if freed_bytes > 0 {
2838 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2839 }
2840 if stats.visited > 0 {
2841 let mut total = self.last_sweep_stats.get();
2842 total.add(stats);
2843 self.last_sweep_stats.set(total);
2844 }
2845 work
2846 }
2847
2848 fn push_next_revisit(
2849 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2850 seen: &mut IdentityHashSet,
2851 ptr: NonNull<GcBox<dyn Trace>>,
2852 age: GcAge,
2853 ) {
2854 if matches!(
2855 age,
2856 GcAge::Old0 | GcAge::Old1 | GcAge::Touched1 | GcAge::Touched2
2857 ) {
2858 let id = ptr.as_ptr() as *const () as usize;
2859 if seen.insert(id) {
2860 next_revisit.push(ptr);
2861 }
2862 }
2863 }
2864
2865 fn sweep_young_range(
2866 &self,
2867 mut prev_next_ptr: NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2868 limit: Option<NonNull<GcBox<dyn Trace>>>,
2869 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2870 next_revisit_seen: &mut IdentityHashSet,
2871 processed: &mut Option<OldRevisitTracker>,
2872 firstold1: &mut Option<NonNull<GcBox<dyn Trace>>>,
2873 freed_bytes: &mut usize,
2874 stats: &mut SweepStats,
2875 ) -> (
2876 NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2877 Option<NonNull<GcBox<dyn Trace>>>,
2878 ) {
2879 let current_white = self.current_white();
2880 loop {
2881 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2882 let Some(ptr) = prev_cell.get() else {
2883 return (prev_next_ptr, None);
2884 };
2885 if Some(ptr) == limit {
2886 return (prev_next_ptr, Some(ptr));
2887 }
2888 let header = self.header_from_ptr(ptr);
2889 let next = header.next.get();
2890 let age = header.age.get();
2891 stats.record_visit(age);
2892 if let Some(processed) = processed.as_mut() {
2893 processed.record_processed(ptr.as_ptr() as *const () as usize);
2894 }
2895 if header.color.get().is_white() && !age.is_old() {
2896 prev_cell.set(next);
2897 let size = header.size();
2898 *freed_bytes += size;
2899 stats.record_free(size);
2900 self.correct_generation_pointers(ptr, next);
2901 self.allocation_tokens
2902 .borrow_mut()
2903 .remove(&(ptr.as_ptr() as *const () as usize));
2904 self.objects.set(self.objects.get().saturating_sub(1));
2905 self.release_box(ptr);
2906 continue;
2907 }
2908
2909 if !header.color.get().is_white() {
2910 let next_age = age.next_after_minor();
2911 header.age.set(next_age);
2912 if next_age == GcAge::Old1 && firstold1.is_none() {
2913 *firstold1 = Some(ptr);
2914 }
2915 match age {
2916 GcAge::New => header.color.set(current_white),
2917 GcAge::Touched1 | GcAge::Touched2 => header.color.set(Color::Black),
2918 _ => {}
2919 }
2920 Self::push_next_revisit(next_revisit, next_revisit_seen, ptr, next_age);
2921 }
2922 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2923 }
2924 }
2925
2926 fn sweep_young(&self) {
2927 let mut freed_bytes = 0usize;
2928 let mut next_revisit = std::mem::take(&mut *self.grayagain_scratch.borrow_mut());
2929 debug_assert!(next_revisit.is_empty(), "grayagain scratch must be parked empty");
2930 let mut next_revisit_seen = IdentityHashSet::default();
2931 let mut firstold1 = None;
2932 let mut stats = SweepStats::default();
2933 let old_revisit = self.take_grayagain();
2934 let mut processed = OldRevisitTracker::new(&old_revisit);
2935 let survival = self.survival.get();
2936 let old1 = self.old1.get();
2937
2938 let (psurvival, new_old1) = self.sweep_young_range(
2939 NonNull::from(&self.head),
2940 survival,
2941 &mut next_revisit,
2942 &mut next_revisit_seen,
2943 &mut processed,
2944 &mut firstold1,
2945 &mut freed_bytes,
2946 &mut stats,
2947 );
2948 self.sweep_young_range(
2949 psurvival,
2950 old1,
2951 &mut next_revisit,
2952 &mut next_revisit_seen,
2953 &mut processed,
2954 &mut firstold1,
2955 &mut freed_bytes,
2956 &mut stats,
2957 );
2958
2959 let finobjsur = self.finobjsur.get();
2960 let finobjold1 = self.finobjold1.get();
2961 let mut dummy_firstold1 = None;
2962 let (pfinobjsur, new_finobjold1) = self.sweep_young_range(
2963 NonNull::from(&self.finobj),
2964 finobjsur,
2965 &mut next_revisit,
2966 &mut next_revisit_seen,
2967 &mut processed,
2968 &mut dummy_firstold1,
2969 &mut freed_bytes,
2970 &mut stats,
2971 );
2972 self.sweep_young_range(
2973 pfinobjsur,
2974 finobjold1,
2975 &mut next_revisit,
2976 &mut next_revisit_seen,
2977 &mut processed,
2978 &mut dummy_firstold1,
2979 &mut freed_bytes,
2980 &mut stats,
2981 );
2982 self.sweep_young_range(
2983 NonNull::from(&self.tobefnz),
2984 None,
2985 &mut next_revisit,
2986 &mut next_revisit_seen,
2987 &mut processed,
2988 &mut dummy_firstold1,
2989 &mut freed_bytes,
2990 &mut stats,
2991 );
2992
2993 if let Some(processed) = processed.as_mut() {
2994 processed.finish();
2995 }
2996
2997 for &ptr in &old_revisit {
2998 let id = ptr.as_ptr() as *const () as usize;
2999 if processed
3000 .as_ref()
3001 .is_some_and(|processed| processed.was_processed(id))
3002 {
3003 continue;
3004 }
3005 stats.revisit += 1;
3006 let header = self.header_from_ptr(ptr);
3007 if header.color.get().is_white() {
3008 continue;
3009 }
3010 let age = header.age.get();
3011 let next_age = age.next_after_minor();
3012 header.age.set(next_age);
3013 if next_age == GcAge::Old1 && firstold1.is_none() {
3014 firstold1 = Some(ptr);
3015 }
3016 if matches!(age, GcAge::Touched1 | GcAge::Touched2) {
3017 header.color.set(Color::Black);
3018 }
3019 Self::push_next_revisit(&mut next_revisit, &mut next_revisit_seen, ptr, next_age);
3020 }
3021
3022 if freed_bytes > 0 {
3023 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
3024 }
3025 let mut recycled = old_revisit;
3026 recycled.clear();
3027 *self.grayagain_scratch.borrow_mut() = recycled;
3028 self.replace_grayagain(next_revisit);
3029 self.reallyold.set(old1);
3030 self.old1.set(new_old1);
3031 self.survival.set(self.head.get());
3032 self.firstold1.set(firstold1);
3033 self.finobjrold.set(finobjold1);
3034 self.finobjold1.set(new_finobjold1);
3035 self.finobjsur.set(self.finobj.get());
3036 self.last_sweep_stats.set(stats);
3037 }
3038
3039 fn finish_cycle(&self) {
3040 let stats = self
3041 .marker
3042 .borrow()
3043 .as_ref()
3044 .map(|marker| marker.stats())
3045 .unwrap_or_default();
3046 self.last_mark_stats.set(stats);
3047 self.recycle_marker_cell();
3048 self.sweep_prev_next.set(None);
3049 let next = self.bytes.get().saturating_mul(self.pause_multiplier.get()) / 100;
3050 self.threshold.set(next.max(GC_MIN_THRESHOLD));
3051 self.collections.set(self.collections.get() + 1);
3052 }
3053
3054 pub fn finish_callfin_phase(&self) -> bool {
3057 if self.state.get() != GcState::CallFin {
3058 return false;
3059 }
3060 self.finish_cycle();
3061 self.state.set(GcState::Pause);
3062 true
3063 }
3064
3065 fn abort_cycle(&self) {
3066 if !self.state.get().is_pause() {
3067 self.recycle_marker_cell();
3068 self.sweep_prev_next.set(None);
3069 let current_white = self.current_white();
3070 self.for_each_header(|header| {
3071 header.color.set(current_white);
3072 });
3073 self.state.set(GcState::Pause);
3074 }
3075 }
3076
3077 pub fn gc_state(&self) -> GcState {
3079 self.state.get()
3080 }
3081
3082 pub fn allgc_count(&self) -> usize {
3084 self.objects.get()
3085 }
3086
3087 pub fn type_name_count(&self, mut predicate: impl FnMut(&'static str) -> bool) -> usize {
3091 let mut count = 0usize;
3092 for head in [self.head.get(), self.finobj.get(), self.tobefnz.get()] {
3093 let mut cursor = head;
3094 while let Some(ptr) = cursor {
3095 let bx = unsafe { ptr.as_ref() };
3096 cursor = bx.header.next.get();
3097 if predicate(bx.value().type_name()) {
3098 count += 1;
3099 }
3100 }
3101 }
3102 count
3103 }
3104
3105 pub fn drop_all(&self) {
3153 struct TearingDownReset<'a> {
3158 flag: &'a Cell<bool>,
3159 armed: bool,
3160 }
3161 impl Drop for TearingDownReset<'_> {
3162 fn drop(&mut self) {
3163 if self.armed {
3164 self.flag.set(false);
3165 }
3166 }
3167 }
3168
3169 self.closed.set(true);
3170 let _reset = TearingDownReset {
3171 armed: !self.tearing_down.replace(true),
3172 flag: &self.tearing_down,
3173 };
3174 self.recycle_marker_cell();
3175 self.sweep_prev_next.set(None);
3176 self.clear_generation_cursors();
3177 self.state.set(GcState::Pause);
3178 self.allocation_tokens.borrow_mut().clear();
3179
3180 let mut passes = 0usize;
3181 loop {
3182 let mut freed_any = false;
3183 freed_any |= self.drop_list(&self.head);
3184 freed_any |= self.drop_list(&self.finobj);
3185 freed_any |= self.drop_list(&self.tobefnz);
3186 freed_any |= self.drop_list(&self.quarantined);
3187 freed_any |= self.drop_list(&self.uncollected);
3188 if !freed_any {
3189 break;
3190 }
3191 passes = passes.saturating_add(1);
3192 if passes >= 10_000 {
3193 panic!(
3194 "Heap::drop_all did not converge after 10000 passes — a \
3195 nonconvergent destructor is allocating a new GC box on \
3196 every teardown pass"
3197 );
3198 }
3199 }
3200
3201 self.allocation_tokens.borrow_mut().clear();
3202 self.bytes.set(0);
3203 self.objects.set(0);
3204 }
3205
3206 fn drop_list(&self, list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>) -> bool {
3212 let mut cursor = list.get();
3213 list.set(None);
3214 let mut freed = false;
3215 while let Some(ptr) = cursor {
3216 freed = true;
3217 let next = unsafe {
3219 let next = (*ptr.as_ptr()).header.next.get();
3220 let _ = Box::from_raw(ptr.as_ptr());
3221 next
3222 };
3223 cursor = next;
3224 }
3225 freed
3226 }
3227}
3228
3229impl Drop for Heap {
3240 fn drop(&mut self) {
3241 self.drop_all();
3242 }
3243}
3244
3245#[cfg(test)]
3250mod tests {
3251 use super::*;
3252
3253 struct Cell0 {
3255 next: Cell<Option<Gc<Cell0>>>,
3256 marker_calls: Cell<usize>,
3257 }
3258
3259 impl Trace for Cell0 {
3260 fn trace(&self, m: &mut Marker) {
3261 self.marker_calls.set(self.marker_calls.get() + 1);
3262 if let Some(n) = self.next.get() {
3263 m.mark(n);
3264 }
3265 }
3266 }
3267
3268 struct OneRoot(Option<Gc<Cell0>>);
3270 impl Trace for OneRoot {
3271 fn trace(&self, m: &mut Marker) {
3272 if let Some(g) = self.0 {
3273 m.mark(g);
3274 }
3275 }
3276 }
3277
3278 struct TwoRoots {
3279 first: Option<Gc<Cell0>>,
3280 second: Option<Gc<Cell0>>,
3281 }
3282
3283 impl Trace for TwoRoots {
3284 fn trace(&self, m: &mut Marker) {
3285 if let Some(g) = self.first {
3286 m.mark(g);
3287 }
3288 if let Some(g) = self.second {
3289 m.mark(g);
3290 }
3291 }
3292 }
3293
3294 #[derive(Clone)]
3295 struct FinalizerCell {
3296 id: usize,
3297 age: GcAge,
3298 finalized: std::rc::Rc<Cell<bool>>,
3299 }
3300
3301 impl FinalizerCell {
3302 fn new(id: usize) -> Self {
3303 Self {
3304 id,
3305 age: GcAge::New,
3306 finalized: std::rc::Rc::new(Cell::new(false)),
3307 }
3308 }
3309 }
3310
3311 impl FinalizerEntry for FinalizerCell {
3312 fn identity(&self) -> usize {
3313 self.id
3314 }
3315
3316 fn age(&self) -> GcAge {
3317 self.age
3318 }
3319
3320 fn is_finalized(&self) -> bool {
3321 self.finalized.get()
3322 }
3323
3324 fn set_finalized(&self, finalized: bool) {
3325 self.finalized.set(finalized);
3326 }
3327 }
3328
3329 fn finalizer_ids(objects: &[FinalizerCell]) -> Vec<usize> {
3330 objects.iter().map(|object| object.id).collect()
3331 }
3332
3333 #[derive(Clone)]
3334 struct WeakCell {
3335 id: usize,
3336 live: bool,
3337 kind: WeakListKind,
3338 }
3339
3340 impl WeakEntry for WeakCell {
3341 type Strong = usize;
3342
3343 fn identity(&self) -> usize {
3344 self.id
3345 }
3346
3347 fn list_kind(&self) -> WeakListKind {
3348 self.kind
3349 }
3350
3351 fn upgrade(&self) -> Option<Self::Strong> {
3352 self.live.then_some(self.id)
3353 }
3354 }
3355
3356 #[test]
3357 fn weak_registry_dedups_snapshots_and_retains_live_ids() {
3358 let mut registry = WeakRegistry::default();
3359 registry.push_unique(WeakCell {
3360 id: 1,
3361 live: true,
3362 kind: WeakListKind::WeakValues,
3363 });
3364 registry.push_unique(WeakCell {
3365 id: 1,
3366 live: true,
3367 kind: WeakListKind::Ephemeron,
3368 });
3369 registry.push_unique(WeakCell {
3370 id: 2,
3371 live: false,
3372 kind: WeakListKind::AllWeak,
3373 });
3374 registry.push_unique(WeakCell {
3375 id: 3,
3376 live: true,
3377 kind: WeakListKind::WeakValues,
3378 });
3379
3380 let stats = registry.stats();
3381 assert_eq!(stats.weak_values, 1);
3382 assert_eq!(stats.ephemeron, 1);
3383 assert_eq!(stats.all_weak, 1);
3384
3385 let snapshot = registry.live_snapshot();
3386 assert_eq!(snapshot, vec![3, 1]);
3387 assert_eq!(
3388 registry.stats(),
3389 WeakRegistryStats {
3390 tracked: 3,
3391 snapshot_live: 2,
3392 snapshot_dead: 1,
3393 retained: 2,
3394 weak_values: 1,
3395 ephemeron: 1,
3396 all_weak: 0,
3397 }
3398 );
3399
3400 let keep: std::collections::HashSet<usize> = [3usize].into_iter().collect();
3401 registry.retain_identities(&keep);
3402 assert_eq!(registry.len(), 1);
3403 assert_eq!(registry.stats().retained, 1);
3404 assert_eq!(registry.stats().weak_values, 1);
3405 registry.remove_identity(3);
3406 assert_eq!(registry.len(), 0);
3407 }
3408
3409 #[test]
3410 fn finalizer_registry_tracks_generational_cohorts() {
3411 let mut registry = FinalizerRegistry::default();
3412 registry.push_pending_unique(FinalizerCell::new(1));
3413 registry.push_pending_unique(FinalizerCell::new(2));
3414
3415 let stats = registry.stats();
3416 assert_eq!(stats.finobj_new, 2);
3417 assert_eq!(stats.finobj_survival, 0);
3418 assert_eq!(stats.finobj_old1, 0);
3419 assert_eq!(stats.finobj_reallyold, 0);
3420 assert_eq!(stats.finobj_minor_scan, 2);
3421
3422 registry.finish_minor_collection();
3423 let stats = registry.stats();
3424 assert_eq!(stats.finobj_new, 0);
3425 assert_eq!(stats.finobj_survival, 2);
3426 assert_eq!(stats.finobj_old1, 0);
3427 assert_eq!(stats.finobj_reallyold, 0);
3428 assert_eq!(stats.finobj_minor_scan, 2);
3429
3430 registry.push_pending_unique(FinalizerCell::new(3));
3431 registry.finish_minor_collection();
3432 let stats = registry.stats();
3433 assert_eq!(stats.finobj_new, 0);
3434 assert_eq!(stats.finobj_survival, 1);
3435 assert_eq!(stats.finobj_old1, 2);
3436 assert_eq!(stats.finobj_reallyold, 0);
3437 assert_eq!(stats.finobj_minor_scan, 1);
3438
3439 registry.finish_minor_collection();
3440 let stats = registry.stats();
3441 assert_eq!(stats.finobj_new, 0);
3442 assert_eq!(stats.finobj_survival, 0);
3443 assert_eq!(stats.finobj_old1, 1);
3444 assert_eq!(stats.finobj_reallyold, 2);
3445 assert_eq!(stats.finobj_minor_scan, 0);
3446 }
3447
3448 #[test]
3449 fn finalizer_registry_minor_snapshot_uses_cohort_boundaries() {
3450 let mut registry = FinalizerRegistry::default();
3451 registry.push_pending_unique(FinalizerCell::new(1));
3452 registry.push_pending_unique(FinalizerCell::new(2));
3453 registry.push_pending_unique(FinalizerCell::new(3));
3454 registry.finish_minor_collection();
3455 registry.finish_minor_collection();
3456 registry.push_pending_unique(FinalizerCell::new(4));
3457 registry.push_pending_unique(FinalizerCell::new(5));
3458
3459 assert_eq!(
3460 finalizer_ids(®istry.pending_minor_snapshot()),
3461 vec![4, 5],
3462 "minor finalizer scan must skip the old1/reallyold prefix"
3463 );
3464
3465 registry.push_to_be_finalized(FinalizerCell::new(99));
3466 registry.promote_pending_to_finalized(vec![
3467 FinalizerCell::new(1),
3468 FinalizerCell::new(2),
3469 FinalizerCell::new(4),
3470 ]);
3471
3472 let stats = registry.stats();
3473 assert_eq!(stats.finobj_old1, 1);
3474 assert_eq!(stats.finobj_new, 1);
3475 assert_eq!(stats.finobj_minor_scan, 1);
3476 assert_eq!(finalizer_ids(registry.pending()), vec![3, 5]);
3477 assert_eq!(
3478 finalizer_ids(registry.to_be_finalized()),
3479 vec![99, 4, 2, 1],
3480 "new to-be-finalized batches append behind older queued finalizers"
3481 );
3482 }
3483
3484 #[test]
3485 fn finalizer_registry_marks_and_clears_finalized_bit() {
3486 let mut registry = FinalizerRegistry::default();
3487 let object = FinalizerCell::new(1);
3488
3489 assert!(!object.is_finalized());
3490 registry.push_pending_unique(object.clone());
3491 assert!(object.is_finalized());
3492
3493 registry.push_pending_unique(object.clone());
3494 assert_eq!(registry.pending_len(), 1);
3495
3496 registry.promote_pending_to_finalized(vec![object.clone()]);
3497 assert!(object.is_finalized());
3498 assert_eq!(registry.pending_len(), 0);
3499 assert_eq!(registry.to_be_finalized_len(), 1);
3500
3501 let popped = registry.pop_to_be_finalized().unwrap();
3502 assert_eq!(popped.id, 1);
3503 assert!(!object.is_finalized());
3504
3505 registry.push_pending_unique(object.clone());
3506 assert!(object.is_finalized());
3507 assert_eq!(registry.pending_len(), 1);
3508 }
3509
3510 #[test]
3511 fn alloc_and_drop_all() {
3512 let heap = Heap::new();
3513 heap.unpause();
3514 let _a = heap.allocate(Cell0 {
3515 next: Cell::new(None),
3516 marker_calls: Cell::new(0),
3517 });
3518 let _b = heap.allocate(Cell0 {
3519 next: Cell::new(None),
3520 marker_calls: Cell::new(0),
3521 });
3522 assert!(heap.bytes_used() > 0);
3523 heap.drop_all();
3524 assert_eq!(heap.bytes_used(), 0);
3525 }
3526
3527 fn list_len(heap: &Heap, mut cursor: Option<NonNull<GcBox<dyn Trace>>>) -> usize {
3528 let mut count = 0usize;
3529 while let Some(ptr) = cursor {
3530 count += 1;
3531 cursor = heap.header_from_ptr(ptr).next.get();
3532 }
3533 count
3534 }
3535
3536 #[test]
3537 fn finalizer_intrusive_lists_sweep_and_drop() {
3538 let heap = Heap::new();
3539 heap.unpause();
3540 let _normal = heap.allocate(Cell0 {
3541 next: Cell::new(None),
3542 marker_calls: Cell::new(0),
3543 });
3544 let finobj = heap.allocate(Cell0 {
3545 next: Cell::new(None),
3546 marker_calls: Cell::new(0),
3547 });
3548 let tobefnz = heap.allocate(Cell0 {
3549 next: Cell::new(None),
3550 marker_calls: Cell::new(0),
3551 });
3552
3553 assert!(heap.move_allgc_to_finobj(finobj.as_trace_ptr()));
3554 assert!(heap.move_allgc_to_finobj(tobefnz.as_trace_ptr()));
3555 assert!(heap.move_finobj_to_tobefnz(tobefnz.as_trace_ptr()));
3556 assert_eq!(list_len(&heap, heap.head.get()), 1);
3557 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3558 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3559 assert_eq!(heap.allgc_count(), 3);
3560
3561 heap.full_collect(&TwoRoots {
3562 first: Some(finobj),
3563 second: Some(tobefnz),
3564 });
3565 assert_eq!(list_len(&heap, heap.head.get()), 0);
3566 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3567 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3568 assert_eq!(heap.allgc_count(), 2);
3569
3570 assert!(heap.move_tobefnz_to_allgc(tobefnz.as_trace_ptr()));
3571 heap.full_collect(&OneRoot(Some(tobefnz)));
3572 assert_eq!(list_len(&heap, heap.head.get()), 1);
3573 assert_eq!(list_len(&heap, heap.finobj.get()), 0);
3574 assert_eq!(list_len(&heap, heap.tobefnz.get()), 0);
3575 assert_eq!(heap.allgc_count(), 1);
3576
3577 heap.drop_all();
3578 assert_eq!(heap.bytes_used(), 0);
3579 }
3580
3581 #[test]
3582 fn account_buffer_refunded_on_sweep() {
3583 let heap = Heap::new();
3584 heap.unpause();
3585 let baseline = heap.bytes_used();
3586 {
3587 let a = heap.allocate(Cell0 {
3588 next: Cell::new(None),
3589 marker_calls: Cell::new(0),
3590 });
3591 assert!(heap.bytes_used() > baseline);
3592 a.account_buffer(&heap, 4096);
3593 assert_eq!(
3594 a.header().size(),
3595 std::mem::size_of::<GcBox<Cell0>>() + 4096
3596 );
3597 }
3598 heap.full_collect(&OneRoot(None));
3601 assert_eq!(
3602 heap.bytes_used(),
3603 baseline,
3604 "account_buffer charge must be fully refunded by sweep"
3605 );
3606 }
3607
3608 #[test]
3609 fn account_buffer_noop_on_uncollected() {
3610 let heap = Heap::new();
3611 heap.unpause();
3612 let g = Gc::new_uncollected(Cell0 {
3613 next: Cell::new(None),
3614 marker_calls: Cell::new(0),
3615 });
3616 let before = heap.bytes_used();
3617 g.account_buffer(&heap, 8192);
3618 assert_eq!(
3619 heap.bytes_used(),
3620 before,
3621 "uncollected box must not charge the pacer"
3622 );
3623 }
3624
3625 #[test]
3626 fn collect_unreachable_frees_bytes() {
3627 let heap = Heap::new();
3628 heap.unpause();
3629 let _a = heap.allocate(Cell0 {
3630 next: Cell::new(None),
3631 marker_calls: Cell::new(0),
3632 });
3633 let bytes_before = heap.bytes_used();
3634 assert!(bytes_before > 0);
3635 heap.full_collect(&OneRoot(None));
3637 assert_eq!(heap.bytes_used(), 0);
3638 assert_eq!(heap.collections(), 1);
3639 }
3640
3641 #[test]
3642 fn collect_keeps_reachable() {
3643 let heap = Heap::new();
3644 heap.unpause();
3645 let root = heap.allocate(Cell0 {
3646 next: Cell::new(None),
3647 marker_calls: Cell::new(0),
3648 });
3649 let bytes_before = heap.bytes_used();
3650 heap.full_collect(&OneRoot(Some(root)));
3651 assert_eq!(heap.bytes_used(), bytes_before);
3652 assert_eq!(root.marker_calls.get(), 1);
3653 }
3654
3655 #[test]
3656 fn allocations_start_new_and_white() {
3657 let heap = Heap::new();
3658 heap.unpause();
3659 let obj = heap.allocate(Cell0 {
3660 next: Cell::new(None),
3661 marker_calls: Cell::new(0),
3662 });
3663 assert_eq!(obj.age(), GcAge::New);
3664 assert!(obj.color().is_white());
3665 }
3666
3667 #[test]
3668 fn allocation_tokens_track_exact_live_box() {
3669 let heap = Heap::new();
3670 heap.unpause();
3671 let obj = heap.allocate(Cell0 {
3672 next: Cell::new(None),
3673 marker_calls: Cell::new(0),
3674 });
3675 let id = obj.identity();
3676
3677 assert_eq!(
3678 heap.allocation_token(id),
3679 None,
3680 "lazy registration: a freshly allocated box has no token until it is downgraded"
3681 );
3682
3683 let token = heap.register_allocation_token(id);
3684 assert_eq!(
3685 heap.register_allocation_token(id),
3686 token,
3687 "registration is get-or-insert: re-registering a live identity returns the same token"
3688 );
3689 assert_eq!(heap.allocation_token(id), Some(token));
3690 assert!(heap.contains_allocation(id, token));
3691 assert!(!heap.contains_allocation(id, token + 1));
3692
3693 heap.full_collect(&OneRoot(None));
3694 assert_eq!(heap.allocation_token(id), None);
3695 assert!(!heap.contains_allocation(id, token));
3696 }
3697
3698 #[test]
3699 fn registering_after_sweep_yields_a_distinct_token_on_address_reuse() {
3700 let heap = Heap::new();
3701 heap.unpause();
3702 let first = heap.allocate(Cell0 {
3703 next: Cell::new(None),
3704 marker_calls: Cell::new(0),
3705 });
3706 let id = first.identity();
3707 let first_token = heap.register_allocation_token(id);
3708
3709 heap.full_collect(&OneRoot(None));
3710 assert_eq!(
3711 heap.allocation_token(id),
3712 None,
3713 "sweep removes the token for the dead identity"
3714 );
3715
3716 let second_token = heap.register_allocation_token(id);
3717 assert_ne!(
3718 first_token, second_token,
3719 "monotonic tokens keep address reuse from resurrecting a stale weak handle"
3720 );
3721 assert!(!heap.contains_allocation(id, first_token));
3722 assert!(heap.contains_allocation(id, second_token));
3723 }
3724
3725 #[test]
3726 fn allocation_during_incremental_sweep_survives_current_cycle() {
3727 let heap = Heap::new();
3728 heap.unpause();
3729 let old_dead = heap.allocate(Cell0 {
3730 next: Cell::new(None),
3731 marker_calls: Cell::new(0),
3732 });
3733 let old_id = old_dead.identity();
3734 heap.register_allocation_token(old_id);
3735
3736 let outcome = heap.incremental_run_until_state_with_post_mark(
3737 &OneRoot(None),
3738 GcState::SweepAllGc,
3739 1024,
3740 |_| {},
3741 );
3742 assert_eq!(outcome, StepOutcome::InProgress);
3743 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3744
3745 let new_during_sweep = heap.allocate(Cell0 {
3746 next: Cell::new(None),
3747 marker_calls: Cell::new(0),
3748 });
3749 let new_id = new_during_sweep.identity();
3750 heap.register_allocation_token(new_id);
3751
3752 loop {
3753 let outcome = heap.incremental_step_with_post_mark(
3754 &OneRoot(None),
3755 StepBudget::from_work(64),
3756 |_| {},
3757 );
3758 if matches!(outcome, StepOutcome::Paused) {
3759 break;
3760 }
3761 }
3762
3763 assert_eq!(heap.allocation_token(old_id), None);
3764 assert!(heap.allocation_token(new_id).is_some());
3765 assert_eq!(heap.allgc_count(), 1);
3766
3767 heap.full_collect(&OneRoot(None));
3768 assert_eq!(heap.allocation_token(new_id), None);
3769 assert_eq!(heap.allgc_count(), 0);
3770 }
3771
3772 #[test]
3773 fn promote_and_reset_all_ages() {
3774 let heap = Heap::new();
3775 heap.unpause();
3776 let a = heap.allocate(Cell0 {
3777 next: Cell::new(None),
3778 marker_calls: Cell::new(0),
3779 });
3780 let b = heap.allocate(Cell0 {
3781 next: Cell::new(None),
3782 marker_calls: Cell::new(0),
3783 });
3784
3785 heap.promote_all_to_old();
3786 assert_eq!(a.age(), GcAge::Old);
3787 assert_eq!(b.age(), GcAge::Old);
3788 assert_eq!(a.color(), Color::Black);
3789 assert_eq!(b.color(), Color::Black);
3790
3791 heap.reset_all_ages();
3792 assert_eq!(a.age(), GcAge::New);
3793 assert_eq!(b.age(), GcAge::New);
3794 assert!(a.color().is_white());
3795 assert!(b.color().is_white());
3796 }
3797
3798 #[test]
3799 fn generational_barriers_update_ages() {
3800 let heap = Heap::new();
3801 heap.unpause();
3802 let parent = heap.allocate(Cell0 {
3803 next: Cell::new(None),
3804 marker_calls: Cell::new(0),
3805 });
3806 let child = heap.allocate(Cell0 {
3807 next: Cell::new(None),
3808 marker_calls: Cell::new(0),
3809 });
3810
3811 parent.set_age(GcAge::Old);
3812 parent.set_color(Color::Black);
3813 heap.generational_backward_barrier(parent);
3814 assert_eq!(parent.age(), GcAge::Touched1);
3815 assert_eq!(parent.color(), Color::Gray);
3816
3817 heap.generational_forward_barrier(parent, child);
3818 assert_eq!(child.age(), GcAge::Old0);
3819 }
3820
3821 #[test]
3822 fn minor_collect_frees_young_and_keeps_old() {
3823 let heap = Heap::new();
3824 heap.unpause();
3825 let old_unreachable = heap.allocate(Cell0 {
3826 next: Cell::new(None),
3827 marker_calls: Cell::new(0),
3828 });
3829 old_unreachable.set_age(GcAge::Old);
3830 old_unreachable.set_color(Color::Black);
3831 let _young_unreachable = heap.allocate(Cell0 {
3832 next: Cell::new(None),
3833 marker_calls: Cell::new(0),
3834 });
3835 let young_survivor = heap.allocate(Cell0 {
3836 next: Cell::new(None),
3837 marker_calls: Cell::new(0),
3838 });
3839
3840 heap.minor_collect_with_post_mark(&OneRoot(Some(young_survivor)), |_| {});
3841
3842 assert_eq!(heap.allgc_count(), 2);
3843 assert_eq!(old_unreachable.age(), GcAge::Old);
3844 assert_eq!(young_survivor.age(), GcAge::Survival);
3845 assert!(young_survivor.color().is_white());
3846 }
3847
3848 #[test]
3849 fn minor_collect_skips_untouched_old_root_scan_work() {
3850 let heap = Heap::new();
3851 heap.unpause();
3852 let old_root = heap.allocate(Cell0 {
3853 next: Cell::new(None),
3854 marker_calls: Cell::new(0),
3855 });
3856 old_root.set_age(GcAge::Old);
3857 old_root.set_color(Color::Black);
3858 let young_root = heap.allocate(Cell0 {
3859 next: Cell::new(None),
3860 marker_calls: Cell::new(0),
3861 });
3862
3863 heap.minor_collect_with_post_mark(
3864 &TwoRoots {
3865 first: Some(old_root),
3866 second: Some(young_root),
3867 },
3868 |_| {},
3869 );
3870
3871 let stats = heap.last_mark_stats();
3872 assert_eq!(stats.marked, 2);
3873 assert_eq!(stats.marked_old, 1);
3874 assert_eq!(stats.marked_young, 1);
3875 assert_eq!(stats.traced, 1);
3876 assert_eq!(stats.traced_old, 0);
3877 assert_eq!(stats.traced_young, 1);
3878 assert_eq!(old_root.marker_calls.get(), 0);
3879 assert_eq!(young_root.marker_calls.get(), 1);
3880 assert_eq!(old_root.age(), GcAge::Old);
3881 assert_eq!(young_root.age(), GcAge::Survival);
3882 }
3883
3884 #[test]
3885 fn minor_collect_traces_touched_old_parent() {
3886 let heap = Heap::new();
3887 heap.unpause();
3888 let old_root = heap.allocate(Cell0 {
3889 next: Cell::new(None),
3890 marker_calls: Cell::new(0),
3891 });
3892 old_root.set_age(GcAge::Old);
3893 old_root.set_color(Color::Black);
3894 let young_child = heap.allocate(Cell0 {
3895 next: Cell::new(None),
3896 marker_calls: Cell::new(0),
3897 });
3898 old_root.next.set(Some(young_child));
3899 heap.generational_backward_barrier(old_root);
3900
3901 heap.minor_collect_with_post_mark(&OneRoot(Some(old_root)), |_| {});
3902
3903 let stats = heap.last_mark_stats();
3904 assert_eq!(stats.marked, 2);
3905 assert_eq!(stats.marked_old, 1);
3906 assert_eq!(stats.marked_young, 1);
3907 assert_eq!(stats.traced, 2);
3908 assert_eq!(stats.traced_old, 1);
3909 assert_eq!(stats.traced_young, 1);
3910 assert_eq!(old_root.marker_calls.get(), 1);
3911 assert_eq!(young_child.marker_calls.get(), 1);
3912 assert_eq!(old_root.age(), GcAge::Touched2);
3913 assert_eq!(young_child.age(), GcAge::Survival);
3914 }
3915
3916 #[test]
3917 fn minor_sweep_uses_generation_cursors_to_skip_old_tail() {
3918 let heap = Heap::new();
3919 heap.unpause();
3920 let mut old_objects = Vec::new();
3921 for _ in 0..64 {
3922 old_objects.push(heap.allocate(Cell0 {
3923 next: Cell::new(None),
3924 marker_calls: Cell::new(0),
3925 }));
3926 }
3927 heap.promote_all_to_old();
3928 let young_root = heap.allocate(Cell0 {
3929 next: Cell::new(None),
3930 marker_calls: Cell::new(0),
3931 });
3932
3933 heap.minor_collect_with_post_mark(&OneRoot(Some(young_root)), |_| {});
3934
3935 let stats = heap.last_sweep_stats();
3936 assert_eq!(stats.visited, 1);
3937 assert_eq!(stats.visited_young, 1);
3938 assert_eq!(stats.visited_old, 0);
3939 assert_eq!(heap.allgc_count(), old_objects.len() + 1);
3940 assert_eq!(young_root.age(), GcAge::Survival);
3941 }
3942
3943 #[test]
3944 fn full_sweep_corrects_generation_cursors_when_cursor_object_is_freed() {
3945 let heap = Heap::new();
3946 heap.unpause();
3947 let _old = heap.allocate(Cell0 {
3948 next: Cell::new(None),
3949 marker_calls: Cell::new(0),
3950 });
3951 heap.promote_all_to_old();
3952 assert!(heap.survival.get().is_some());
3953 assert!(heap.old1.get().is_some());
3954 assert!(heap.reallyold.get().is_some());
3955
3956 heap.full_collect(&OneRoot(None));
3957
3958 assert_eq!(heap.allgc_count(), 0);
3959 assert_eq!(heap.survival.get(), None);
3960 assert_eq!(heap.old1.get(), None);
3961 assert_eq!(heap.reallyold.get(), None);
3962 assert_eq!(heap.firstold1.get(), None);
3963 assert_eq!(heap.last_sweep_stats().freed, 1);
3964 }
3965
3966 #[test]
3974 fn cross_list_move_deletes_grayagain_entry() {
3975 let heap = Heap::new();
3976 heap.unpause();
3977 let obj = heap.allocate(Cell0 {
3978 next: Cell::new(None),
3979 marker_calls: Cell::new(0),
3980 });
3981 obj.set_age(GcAge::Old1);
3982 obj.set_color(Color::Gray);
3983 heap.remember_minor_revisit(obj.as_trace_ptr());
3984 assert_eq!(heap.grayagain_count(), 1);
3985 assert!(heap.header_from_ptr(obj.as_trace_ptr()).gray_listed());
3986 assert!(heap.move_allgc_to_finobj(obj.as_trace_ptr()));
3987 assert_eq!(
3988 heap.grayagain_count(),
3989 0,
3990 "allgc->finobj move deletes the grayagain entry"
3991 );
3992 assert!(!heap.header_from_ptr(obj.as_trace_ptr()).gray_listed());
3993
3994 let heap = Heap::new();
3995 heap.unpause();
3996 let obj = heap.allocate(Cell0 {
3997 next: Cell::new(None),
3998 marker_calls: Cell::new(0),
3999 });
4000 assert!(heap.move_allgc_to_finobj(obj.as_trace_ptr()));
4001 assert!(heap.move_finobj_to_tobefnz(obj.as_trace_ptr()));
4002 obj.set_age(GcAge::Old1);
4003 obj.set_color(Color::Gray);
4004 heap.remember_minor_revisit(obj.as_trace_ptr());
4005 assert_eq!(heap.grayagain_count(), 1);
4006 assert!(heap.move_tobefnz_to_allgc(obj.as_trace_ptr()));
4007 assert_eq!(
4008 heap.grayagain_count(),
4009 0,
4010 "tobefnz->allgc move deletes the grayagain entry"
4011 );
4012 assert!(!heap.header_from_ptr(obj.as_trace_ptr()).gray_listed());
4013 }
4014
4015 #[test]
4038 #[ignore = "pre-existing generational-liveness gap (issue #263): reachable \
4039 young child dies when a gray-listed transitional is moved \
4040 cross-list; identical on pristine and W1 — not a W1 bug"]
4041 fn g3_moved_transitional_keeps_young_child_reachable() {
4042 let heap = Heap::new();
4043 heap.unpause();
4044 let child = heap.allocate(Cell0 {
4045 next: Cell::new(None),
4046 marker_calls: Cell::new(0),
4047 });
4048 let transitional = heap.allocate(Cell0 {
4049 next: Cell::new(Some(child)),
4050 marker_calls: Cell::new(0),
4051 });
4052 let parent = heap.allocate(Cell0 {
4053 next: Cell::new(Some(transitional)),
4054 marker_calls: Cell::new(0),
4055 });
4056 parent.set_age(GcAge::Old);
4057 parent.set_color(Color::Black);
4058 transitional.set_age(GcAge::Old1);
4059 transitional.set_color(Color::Gray);
4060 heap.remember_minor_revisit(transitional.as_trace_ptr());
4061 assert_eq!(heap.grayagain_count(), 1);
4062 assert_eq!(heap.allgc_count(), 3);
4063
4064 assert!(heap.move_allgc_to_finobj(transitional.as_trace_ptr()));
4065 assert_eq!(
4066 heap.grayagain_count(),
4067 0,
4068 "cross-list move deletes the grayagain entry"
4069 );
4070
4071 heap.minor_collect_with_post_mark(&OneRoot(Some(parent)), |_| {});
4072
4073 assert_eq!(
4074 heap.allgc_count(),
4075 3,
4076 "reachable young child should survive the minor"
4077 );
4078 }
4079
4080 #[test]
4087 fn g3_baseline_child_freed_after_move_allgc_to_finobj() {
4088 let heap = Heap::new();
4089 heap.unpause();
4090 let child = heap.allocate(Cell0 {
4091 next: Cell::new(None),
4092 marker_calls: Cell::new(0),
4093 });
4094 let transitional = heap.allocate(Cell0 {
4095 next: Cell::new(Some(child)),
4096 marker_calls: Cell::new(0),
4097 });
4098 let parent = heap.allocate(Cell0 {
4099 next: Cell::new(Some(transitional)),
4100 marker_calls: Cell::new(0),
4101 });
4102 parent.set_age(GcAge::Old);
4103 parent.set_color(Color::Black);
4104 transitional.set_age(GcAge::Old1);
4105 transitional.set_color(Color::Gray);
4106 heap.remember_minor_revisit(transitional.as_trace_ptr());
4107
4108 assert!(heap.move_allgc_to_finobj(transitional.as_trace_ptr()));
4109 assert_eq!(heap.grayagain_count(), 0);
4110
4111 heap.minor_collect_with_post_mark(&OneRoot(Some(parent)), |_| {});
4112
4113 assert_eq!(
4114 heap.allgc_count(),
4115 2,
4116 "baseline (pre-#263-fix): the young child is freed; if this \
4117 changes, either #263 was fixed (invert this test and un-ignore \
4118 G3) or minor liveness regressed"
4119 );
4120 }
4121
4122 #[test]
4129 fn g3_baseline_child_freed_after_move_tobefnz_to_allgc() {
4130 let heap = Heap::new();
4131 heap.unpause();
4132 let child = heap.allocate(Cell0 {
4133 next: Cell::new(None),
4134 marker_calls: Cell::new(0),
4135 });
4136 let transitional = heap.allocate(Cell0 {
4137 next: Cell::new(Some(child)),
4138 marker_calls: Cell::new(0),
4139 });
4140 let parent = heap.allocate(Cell0 {
4141 next: Cell::new(Some(transitional)),
4142 marker_calls: Cell::new(0),
4143 });
4144 parent.set_age(GcAge::Old);
4145 parent.set_color(Color::Black);
4146 transitional.set_age(GcAge::Old1);
4147 transitional.set_color(Color::Gray);
4148
4149 assert!(heap.move_allgc_to_finobj(transitional.as_trace_ptr()));
4150 assert!(heap.move_finobj_to_tobefnz(transitional.as_trace_ptr()));
4151 heap.remember_minor_revisit(transitional.as_trace_ptr());
4152 assert_eq!(heap.grayagain_count(), 1);
4153
4154 assert!(heap.move_tobefnz_to_allgc(transitional.as_trace_ptr()));
4155 assert_eq!(
4156 heap.grayagain_count(),
4157 0,
4158 "tobefnz->allgc move deletes the grayagain entry"
4159 );
4160
4161 heap.minor_collect_with_post_mark(&OneRoot(Some(parent)), |_| {});
4162
4163 assert_eq!(
4164 heap.allgc_count(),
4165 2,
4166 "baseline (pre-#263-fix): the young child is freed after the \
4167 tobefnz->allgc move deletes the revisit entry; invert together \
4168 with the #263 fix"
4169 );
4170 }
4171
4172 #[test]
4179 fn drop_all_clears_grayagain_before_freeing() {
4180 let heap = Heap::new();
4181 heap.unpause();
4182 let obj = heap.allocate(Cell0 {
4183 next: Cell::new(None),
4184 marker_calls: Cell::new(0),
4185 });
4186 obj.set_age(GcAge::Old);
4187 heap.generational_backward_barrier(obj);
4188 assert_eq!(heap.grayagain_count(), 1);
4189
4190 heap.drop_all();
4191
4192 assert_eq!(heap.grayagain_count(), 0);
4193 assert_eq!(heap.allgc_count(), 0);
4194 }
4195
4196 #[test]
4204 #[cfg(target_pointer_width = "64")]
4205 fn gcheader_is_24_bytes_after_grayagain_diet() {
4206 assert_eq!(std::mem::size_of::<GcHeader>(), 24);
4207 }
4208
4209 #[test]
4210 fn full_sweep_unlinks_freed_grayagain_entries() {
4211 let heap = Heap::new();
4212 heap.unpause();
4213 let parent = heap.allocate(Cell0 {
4214 next: Cell::new(None),
4215 marker_calls: Cell::new(0),
4216 });
4217 heap.promote_all_to_old();
4218 heap.generational_backward_barrier(parent);
4219 assert_eq!(heap.grayagain_count(), 1);
4220
4221 heap.full_collect(&OneRoot(None));
4222
4223 assert_eq!(heap.allgc_count(), 0);
4224 assert_eq!(heap.grayagain_count(), 0);
4225
4226 let young = heap.allocate(Cell0 {
4227 next: Cell::new(None),
4228 marker_calls: Cell::new(0),
4229 });
4230 heap.minor_collect_with_post_mark(&OneRoot(Some(young)), |_| {});
4231 assert_eq!(young.age(), GcAge::Survival);
4232 }
4233
4234 #[test]
4235 fn grayagain_links_object_once() {
4236 let heap = Heap::new();
4237 heap.unpause();
4238 let parent = heap.allocate(Cell0 {
4239 next: Cell::new(None),
4240 marker_calls: Cell::new(0),
4241 });
4242 parent.set_age(GcAge::Old);
4243 parent.set_color(Color::Black);
4244
4245 heap.generational_backward_barrier(parent);
4246 heap.generational_backward_barrier(parent);
4247
4248 assert_eq!(heap.grayagain_count(), 1);
4249 }
4250
4251 #[test]
4252 fn grayagain_list_carries_old1_until_old() {
4253 let heap = Heap::new();
4254 heap.unpause();
4255 let survivor = heap.allocate(Cell0 {
4256 next: Cell::new(None),
4257 marker_calls: Cell::new(0),
4258 });
4259
4260 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
4261 assert_eq!(survivor.age(), GcAge::Survival);
4262
4263 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
4264 assert_eq!(survivor.age(), GcAge::Old1);
4265
4266 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
4267 assert_eq!(survivor.age(), GcAge::Old);
4268 assert_eq!(heap.allgc_count(), 1);
4269 }
4270
4271 #[test]
4272 fn grayagain_list_carries_touched2_until_old() {
4273 let heap = Heap::new();
4274 heap.unpause();
4275 let parent = heap.allocate(Cell0 {
4276 next: Cell::new(None),
4277 marker_calls: Cell::new(0),
4278 });
4279 parent.set_age(GcAge::Old);
4280 parent.set_color(Color::Black);
4281 heap.generational_backward_barrier(parent);
4282
4283 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
4284 assert_eq!(parent.age(), GcAge::Touched2);
4285
4286 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
4287 assert_eq!(parent.age(), GcAge::Old);
4288 assert_eq!(parent.marker_calls.get(), 2);
4289 }
4290
4291 #[test]
4292 fn collect_traverses_cycles() {
4293 let heap = Heap::new();
4294 heap.unpause();
4295 let a = heap.allocate(Cell0 {
4296 next: Cell::new(None),
4297 marker_calls: Cell::new(0),
4298 });
4299 let b = heap.allocate(Cell0 {
4300 next: Cell::new(Some(a)),
4301 marker_calls: Cell::new(0),
4302 });
4303 a.next.set(Some(b)); heap.full_collect(&OneRoot(Some(a)));
4306 assert_eq!(a.marker_calls.get(), 1);
4307 assert_eq!(b.marker_calls.get(), 1);
4308 heap.full_collect(&OneRoot(None));
4312 assert_eq!(heap.bytes_used(), 0);
4313 }
4314
4315 #[test]
4316 fn heap_guard_stacks() {
4317 assert!(
4318 with_current_heap(|heap| heap.is_none()),
4319 "no guard initially"
4320 );
4321 let h1 = Heap::new();
4322 h1.unpause();
4323 {
4324 let _g1 = HeapGuard::push(&h1);
4325 assert!(with_current_heap(|heap| heap.is_some()));
4326 let h2 = Heap::new();
4327 h2.unpause();
4328 {
4329 let _g2 = HeapGuard::push(&h2);
4330 with_current_heap(|heap| {
4332 assert!(std::rc::Rc::ptr_eq(heap.unwrap(), &h2));
4333 });
4334 }
4335 with_current_heap(|heap| {
4337 assert!(std::rc::Rc::ptr_eq(heap.unwrap(), &h1));
4338 });
4339 }
4340 assert!(
4341 with_current_heap(|heap| heap.is_none()),
4342 "stack empty after all guards drop"
4343 );
4344 }
4345
4346 #[test]
4347 fn step_threshold_triggers_collect() {
4348 let heap = Heap::new();
4349 heap.unpause();
4350 let mut keeps = Vec::new();
4352 for _ in 0..64 {
4353 keeps.push(heap.allocate(Cell0 {
4357 next: Cell::new(None),
4358 marker_calls: Cell::new(0),
4359 }));
4360 }
4361 struct ManyRoots<'a>(&'a [Gc<Cell0>]);
4363 impl<'a> Trace for ManyRoots<'a> {
4364 fn trace(&self, m: &mut Marker) {
4365 for g in self.0.iter() {
4366 m.mark(*g);
4367 }
4368 }
4369 }
4370 heap.step(&ManyRoots(&keeps));
4371 assert!(heap.bytes_used() > 0);
4373 }
4374
4375 #[test]
4376 fn threshold_floored_after_collecting_tiny_heap() {
4377 let heap = Heap::new();
4378 heap.unpause();
4379 struct NoRoots;
4380 impl Trace for NoRoots {
4381 fn trace(&self, _m: &mut Marker) {}
4382 }
4383 for _ in 0..200 {
4384 heap.allocate(Cell0 {
4385 next: Cell::new(None),
4386 marker_calls: Cell::new(0),
4387 });
4388 }
4389 heap.full_collect(&NoRoots);
4390 assert!(
4391 heap.threshold_bytes() >= GC_MIN_THRESHOLD,
4392 "threshold {} collapsed below floor {}; a churning program would full-collect per allocation",
4393 heap.threshold_bytes(),
4394 GC_MIN_THRESHOLD
4395 );
4396 }
4397
4398 fn build_chain(heap: &Heap, len: usize) -> Gc<Cell0> {
4399 let head = heap.allocate(Cell0 {
4400 next: Cell::new(None),
4401 marker_calls: Cell::new(0),
4402 });
4403 let mut cur = head;
4404 for _ in 1..len {
4405 let n = heap.allocate(Cell0 {
4406 next: Cell::new(None),
4407 marker_calls: Cell::new(0),
4408 });
4409 cur.next.set(Some(n));
4410 cur = n;
4411 }
4412 head
4413 }
4414
4415 #[test]
4416 fn budget_zero_does_some_work() {
4417 let heap = Heap::new();
4418 heap.unpause();
4419 let head = build_chain(&heap, 8);
4420 let roots = OneRoot(Some(head));
4421 let budget = StepBudget::from_work(0);
4422 let outcome = heap.incremental_step_with_post_mark(&roots, budget, |_| {});
4423 assert_ne!(outcome, StepOutcome::SkippedStopped);
4424 assert_ne!(heap.gc_state(), GcState::Pause);
4425 }
4426
4427 #[test]
4428 fn run_until_state_stops_before_next_phase_work() {
4429 let heap = Heap::new();
4430 heap.unpause();
4431 let head = build_chain(&heap, 8);
4432 let roots = OneRoot(Some(head));
4433 let atomic_calls = Cell::new(0);
4434
4435 let outcome =
4436 heap.incremental_run_until_state_with_post_mark(&roots, GcState::Atomic, 1024, |_| {
4437 atomic_calls.set(atomic_calls.get() + 1)
4438 });
4439 assert_eq!(outcome, StepOutcome::InProgress);
4440 assert_eq!(heap.gc_state(), GcState::Atomic);
4441 assert_eq!(
4442 atomic_calls.get(),
4443 0,
4444 "atomic hook must not run before inspection"
4445 );
4446
4447 let outcome = heap.incremental_run_until_state_with_post_mark(
4448 &roots,
4449 GcState::SweepAllGc,
4450 1024,
4451 |_| atomic_calls.set(atomic_calls.get() + 1),
4452 );
4453 assert_eq!(outcome, StepOutcome::InProgress);
4454 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
4455 assert_eq!(
4456 atomic_calls.get(),
4457 1,
4458 "entering sweep must run the atomic hook once"
4459 );
4460 }
4461
4462 #[test]
4463 fn larger_budget_drains_more_gray_than_smaller() {
4464 let small_heap = Heap::new();
4465 small_heap.unpause();
4466 let h1 = build_chain(&small_heap, 64);
4467 let r1 = OneRoot(Some(h1));
4468 let mut small_calls = 0;
4469 loop {
4470 small_calls += 1;
4471 let outcome =
4472 small_heap.incremental_step_with_post_mark(&r1, StepBudget::from_work(2), |_| {});
4473 if outcome == StepOutcome::Paused {
4474 break;
4475 }
4476 assert!(small_calls < 10_000, "did not converge");
4477 }
4478
4479 let big_heap = Heap::new();
4480 big_heap.unpause();
4481 let h2 = build_chain(&big_heap, 64);
4482 let r2 = OneRoot(Some(h2));
4483 let mut big_calls = 0;
4484 loop {
4485 big_calls += 1;
4486 let outcome =
4487 big_heap.incremental_step_with_post_mark(&r2, StepBudget::from_work(64), |_| {});
4488 if outcome == StepOutcome::Paused {
4489 break;
4490 }
4491 assert!(big_calls < 10_000, "did not converge");
4492 }
4493
4494 assert!(
4495 big_calls < small_calls,
4496 "expected big_calls={} < small_calls={}",
4497 big_calls,
4498 small_calls
4499 );
4500 }
4501
4502 #[test]
4503 fn sweep_can_pause_and_resume() {
4504 let heap = Heap::new();
4505 heap.unpause();
4506 for _ in 0..16 {
4507 let _ = heap.allocate(Cell0 {
4508 next: Cell::new(None),
4509 marker_calls: Cell::new(0),
4510 });
4511 }
4512 let roots = OneRoot(None);
4513 let bytes_before = heap.bytes_used();
4514 assert!(bytes_before > 0);
4515 let mut step_count = 0;
4516 let mut saw_in_progress_during_sweep = false;
4517 loop {
4518 step_count += 1;
4519 let outcome =
4520 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {});
4521 if heap.gc_state().is_sweep() && outcome == StepOutcome::InProgress {
4522 saw_in_progress_during_sweep = true;
4523 }
4524 if outcome == StepOutcome::Paused {
4525 break;
4526 }
4527 assert!(step_count < 10_000, "did not converge");
4528 }
4529 assert!(saw_in_progress_during_sweep, "sweep never paused mid-list");
4530 assert_eq!(heap.bytes_used(), 0);
4531 }
4532
4533 #[test]
4534 fn post_mark_runs_once_per_atomic() {
4535 let heap = Heap::new();
4536 heap.unpause();
4537 for _ in 0..32 {
4538 let _ = heap.allocate(Cell0 {
4539 next: Cell::new(None),
4540 marker_calls: Cell::new(0),
4541 });
4542 }
4543 let roots = OneRoot(None);
4544 let call_count = std::cell::Cell::new(0);
4545 let mut step_count = 0;
4546 loop {
4547 step_count += 1;
4548 let outcome =
4549 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {
4550 call_count.set(call_count.get() + 1);
4551 });
4552 if outcome == StepOutcome::Paused {
4553 break;
4554 }
4555 assert!(step_count < 10_000, "did not converge");
4556 }
4557 assert_eq!(
4558 call_count.get(),
4559 1,
4560 "post_mark must run exactly once per cycle"
4561 );
4562 }
4563
4564 #[test]
4565 fn full_collect_equivalent_to_incremental_to_pause() {
4566 let h1 = Heap::new();
4567 h1.unpause();
4568 let head1 = h1.allocate(Cell0 {
4569 next: Cell::new(None),
4570 marker_calls: Cell::new(0),
4571 });
4572 let _orphan1 = h1.allocate(Cell0 {
4573 next: Cell::new(None),
4574 marker_calls: Cell::new(0),
4575 });
4576 let _orphan2 = h1.allocate(Cell0 {
4577 next: Cell::new(None),
4578 marker_calls: Cell::new(0),
4579 });
4580 let roots1 = OneRoot(Some(head1));
4581 h1.full_collect(&roots1);
4582 let bytes_full = h1.bytes_used();
4583
4584 let h2 = Heap::new();
4585 h2.unpause();
4586 let head2 = h2.allocate(Cell0 {
4587 next: Cell::new(None),
4588 marker_calls: Cell::new(0),
4589 });
4590 let _orphan3 = h2.allocate(Cell0 {
4591 next: Cell::new(None),
4592 marker_calls: Cell::new(0),
4593 });
4594 let _orphan4 = h2.allocate(Cell0 {
4595 next: Cell::new(None),
4596 marker_calls: Cell::new(0),
4597 });
4598 let roots2 = OneRoot(Some(head2));
4599 loop {
4600 let outcome =
4601 h2.incremental_step_with_post_mark(&roots2, StepBudget::from_work(1), |_| {});
4602 if outcome == StepOutcome::Paused {
4603 break;
4604 }
4605 }
4606 assert_eq!(h2.bytes_used(), bytes_full);
4607 }
4608
4609 struct DropFlag(std::rc::Rc<Cell<bool>>);
4614 impl Drop for DropFlag {
4615 fn drop(&mut self) {
4616 self.0.set(true);
4617 }
4618 }
4619 struct Tracked(#[allow(dead_code)] DropFlag);
4620 impl Trace for Tracked {
4621 fn trace(&self, _m: &mut Marker) {}
4622 }
4623
4624 #[test]
4625 fn allocate_uncollected_survives_collection_but_is_freed_on_heap_drop() {
4626 let heap = Heap::new();
4627 heap.unpause();
4628 let dropped = std::rc::Rc::new(Cell::new(false));
4629 let _gc = heap.allocate_uncollected(Tracked(DropFlag(dropped.clone())));
4630
4631 heap.full_collect(&OneRoot(None));
4634 assert!(
4635 !dropped.get(),
4636 "allocate_uncollected box must survive a full collection while the heap is alive"
4637 );
4638
4639 drop(heap);
4640 assert!(
4641 dropped.get(),
4642 "allocate_uncollected box must be freed once its heap drops (issue #249)"
4643 );
4644 }
4645
4646 #[test]
4647 fn bootstrapping_routes_allocate_to_the_uncollected_list() {
4648 let heap = Heap::new();
4649 heap.unpause();
4650 assert!(!heap.is_bootstrapping());
4651
4652 heap.begin_bootstrap();
4653 let dropped = std::rc::Rc::new(Cell::new(false));
4654 let _gc = heap.allocate(Tracked(DropFlag(dropped.clone())));
4655
4656 assert_eq!(
4659 heap.allgc_count(),
4660 0,
4661 "a bootstrap allocation must not join the normal collectable list"
4662 );
4663 heap.full_collect(&OneRoot(None));
4664 assert!(
4665 !dropped.get(),
4666 "a bootstrap allocation must survive collection while bootstrapping is active"
4667 );
4668
4669 heap.end_bootstrap();
4670 drop(heap);
4671 assert!(
4672 dropped.get(),
4673 "a bootstrap allocation must still be freed when the heap drops"
4674 );
4675 }
4676
4677 #[test]
4684 fn ownership_flags_distinguish_all_three_allocation_paths() {
4685 let heap = Heap::new();
4686 let tracked = heap.allocate(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4687 assert!(tracked.is_heap_owned());
4688 assert!(tracked.is_heap_tracked());
4689
4690 let bootstrap =
4691 heap.allocate_uncollected(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4692 assert!(
4693 bootstrap.is_heap_owned(),
4694 "bootstrap boxes die in drop_all — strict-guard hazard checks must see them"
4695 );
4696 assert!(!bootstrap.is_heap_tracked());
4697
4698 let detached = Gc::new_uncollected(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4699 assert!(!detached.is_heap_owned());
4700 assert!(!detached.is_heap_tracked());
4701 }
4702
4703 #[test]
4704 fn end_bootstrap_restores_normal_sweepable_allocation() {
4705 let heap = Heap::new();
4706 heap.unpause();
4707 heap.begin_bootstrap();
4708 heap.end_bootstrap();
4709
4710 let dropped = std::rc::Rc::new(Cell::new(false));
4711 let _gc = heap.allocate(Tracked(DropFlag(dropped.clone())));
4712 assert_eq!(heap.allgc_count(), 1);
4713
4714 heap.full_collect(&OneRoot(None));
4715 assert_eq!(
4721 heap.allgc_count(),
4722 0,
4723 "after end_bootstrap, an unreachable allocation must be swept normally"
4724 );
4725 }
4726
4727 struct AllocOnDrop {
4734 flag: std::rc::Rc<Cell<bool>>,
4735 inner: std::rc::Rc<Cell<bool>>,
4736 }
4737 impl Trace for AllocOnDrop {
4738 fn trace(&self, _m: &mut Marker) {}
4739 }
4740 impl Drop for AllocOnDrop {
4741 fn drop(&mut self) {
4742 self.flag.set(true);
4743 with_current_heap(|heap| {
4744 if let Some(heap) = heap {
4745 let _ = heap.allocate(Tracked(DropFlag(self.inner.clone())));
4746 }
4747 });
4748 }
4749 }
4750
4751 #[test]
4752 fn close_frees_objects_while_outer_guard_alive() {
4753 let heap = Heap::new();
4754 heap.unpause();
4755 let guard = HeapGuard::push(&heap);
4756
4757 let dropped = std::rc::Rc::new(Cell::new(false));
4758 let _gc = heap.allocate(Tracked(DropFlag(dropped.clone())));
4759 assert!(!dropped.get());
4760
4761 heap.drop_all();
4762 assert!(
4763 dropped.get(),
4764 "drop_all must run the object's destructor during the close call, \
4765 while the outer guard is still alive"
4766 );
4767 assert!(heap.is_closed());
4768 assert_eq!(heap.bytes_used(), 0);
4769 drop(guard);
4770 }
4771
4772 #[test]
4773 #[should_panic(expected = "closed heap")]
4774 fn allocate_into_closed_heap_panics() {
4775 let heap = Heap::new();
4776 heap.unpause();
4777 let _guard = HeapGuard::push(&heap);
4778 heap.drop_all();
4779 let _ = heap.allocate(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4780 }
4781
4782 #[test]
4783 fn destructor_allocating_during_teardown_does_not_leak() {
4784 let heap = Heap::new();
4785 heap.unpause();
4786 let _guard = HeapGuard::push(&heap);
4787
4788 let outer = std::rc::Rc::new(Cell::new(false));
4789 let inner = std::rc::Rc::new(Cell::new(false));
4790 let _gc = heap.allocate(AllocOnDrop {
4791 flag: outer.clone(),
4792 inner: inner.clone(),
4793 });
4794
4795 heap.drop_all();
4796 assert!(outer.get(), "the destructor itself must have run");
4797 assert!(
4798 inner.get(),
4799 "the box the destructor allocated mid-teardown must be freed by a \
4800 later drain pass, not leaked"
4801 );
4802 assert_eq!(heap.bytes_used(), 0);
4803 }
4804
4805 #[test]
4806 fn weak_handles_dead_immediately_after_close() {
4807 let heap = Heap::new();
4808 heap.unpause();
4809 let _guard = HeapGuard::push(&heap);
4810
4811 let gc = heap.allocate(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4812 let identity = gc.identity();
4813 let token = heap.register_allocation_token(identity);
4814 let weak = HeapRef::from_heap(&heap);
4815 assert!(
4816 weak.contains_allocation(identity, token),
4817 "weak handle upgrades while the box is live"
4818 );
4819
4820 heap.drop_all();
4821
4822 assert!(
4823 !weak.contains_allocation(identity, token),
4824 "weak handle must fail to upgrade the instant close clears the \
4825 allocation tokens, while the heap Rc is still alive"
4826 );
4827 }
4828
4829 struct BarrierOnDrop {
4836 flag: std::rc::Rc<Cell<bool>>,
4837 inner: std::rc::Rc<Cell<bool>>,
4838 }
4839 impl Trace for BarrierOnDrop {
4840 fn trace(&self, _m: &mut Marker) {}
4841 }
4842 impl Drop for BarrierOnDrop {
4843 fn drop(&mut self) {
4844 self.flag.set(true);
4845 with_current_heap(|heap| {
4846 if let Some(heap) = heap {
4847 let fresh = heap.allocate(Tracked(DropFlag(self.inner.clone())));
4848 heap.remember_minor_revisit(fresh.as_trace_ptr());
4849 }
4850 });
4851 }
4852 }
4853
4854 #[test]
4855 fn teardown_barrier_cannot_repopulate_grayagain() {
4856 let heap = Heap::new();
4857 heap.unpause();
4858 let _guard = HeapGuard::push(&heap);
4859
4860 let outer = std::rc::Rc::new(Cell::new(false));
4861 let inner = std::rc::Rc::new(Cell::new(false));
4862 let _gc = heap.allocate(BarrierOnDrop {
4863 flag: outer.clone(),
4864 inner: inner.clone(),
4865 });
4866
4867 heap.drop_all();
4868 assert!(outer.get(), "the destructor itself must have run");
4869 assert!(
4870 inner.get(),
4871 "the box the destructor allocated must still be drained"
4872 );
4873 assert_eq!(
4874 heap.grayagain_count(),
4875 0,
4876 "a barrier fired during teardown must not park a soon-freed \
4877 pointer in grayagain"
4878 );
4879
4880 heap.drop_all();
4881 assert_eq!(heap.grayagain_count(), 0);
4882 assert_eq!(heap.bytes_used(), 0);
4883 }
4884
4885 struct CollectOnDrop {
4893 flag: std::rc::Rc<Cell<bool>>,
4894 inner: std::rc::Rc<Cell<bool>>,
4895 }
4896 struct TrackedRoot(Option<Gc<Tracked>>);
4897 impl Trace for TrackedRoot {
4898 fn trace(&self, m: &mut Marker) {
4899 if let Some(g) = self.0 {
4900 m.mark(g);
4901 }
4902 }
4903 }
4904 impl Trace for CollectOnDrop {
4905 fn trace(&self, _m: &mut Marker) {}
4906 }
4907 impl Drop for CollectOnDrop {
4908 fn drop(&mut self) {
4909 self.flag.set(true);
4910 with_current_heap(|heap| {
4911 if let Some(heap) = heap {
4912 let fresh = heap.allocate(Tracked(DropFlag(self.inner.clone())));
4913 let root = TrackedRoot(Some(fresh));
4914 heap.minor_collect_with_post_mark(&root, |_: &mut Marker| {});
4915 heap.full_collect(&root);
4916 heap.step(&root);
4917 }
4918 });
4919 }
4920 }
4921
4922 #[test]
4923 fn collections_invoked_by_teardown_destructor_are_inert() {
4924 let heap = Heap::new();
4925 heap.unpause();
4926 let _guard = HeapGuard::push(&heap);
4927
4928 let outer = std::rc::Rc::new(Cell::new(false));
4929 let inner = std::rc::Rc::new(Cell::new(false));
4930 let _gc = heap.allocate(CollectOnDrop {
4931 flag: outer.clone(),
4932 inner: inner.clone(),
4933 });
4934
4935 heap.drop_all();
4936 assert!(outer.get(), "the destructor itself must have run");
4937 assert!(
4938 inner.get(),
4939 "the box the destructor allocated must still be drained"
4940 );
4941 assert_eq!(
4942 heap.minor_collections(),
4943 0,
4944 "a minor collection invoked from a teardown destructor must be inert"
4945 );
4946 assert_eq!(
4947 heap.full_collections(),
4948 0,
4949 "a full collection invoked from a teardown destructor must be inert"
4950 );
4951 assert_eq!(heap.grayagain_count(), 0);
4952
4953 heap.drop_all();
4954 assert_eq!(heap.grayagain_count(), 0);
4955 assert_eq!(heap.bytes_used(), 0);
4956 assert!(!heap.would_collect());
4957 }
4958
4959 #[test]
4964 fn post_close_token_minting_is_refused() {
4965 let heap = Heap::new();
4966 heap.unpause();
4967 let _guard = HeapGuard::push(&heap);
4968
4969 let gc = heap.allocate(Tracked(DropFlag(std::rc::Rc::new(Cell::new(false)))));
4970 let identity = gc.identity();
4971
4972 heap.drop_all();
4973
4974 let token = heap.register_allocation_token(identity);
4975 assert_eq!(token, 0, "a closed heap must refuse to mint a token");
4976 assert!(
4977 !heap.contains_allocation(identity, token),
4978 "the refused token must never validate"
4979 );
4980 assert!(
4981 heap.allocation_token(identity).is_none(),
4982 "the refusal must not have touched the token map"
4983 );
4984 }
4985}