1use std::cell::{Cell, RefCell};
41use std::collections::{HashMap, HashSet};
42use std::hash::{BuildHasherDefault, Hasher};
43use std::marker::PhantomData;
44use std::ptr::NonNull;
45
46#[derive(Default)]
47struct IdentityHasher {
48 value: u64,
49}
50
51impl Hasher for IdentityHasher {
52 #[inline]
53 fn write(&mut self, bytes: &[u8]) {
54 const PRIME: u64 = 0x0000_0100_0000_01b3;
55 for &byte in bytes {
56 self.value ^= u64::from(byte);
57 self.value = self.value.wrapping_mul(PRIME);
58 }
59 }
60
61 #[inline]
62 fn write_usize(&mut self, i: usize) {
63 self.value = i as u64;
64 }
65
66 #[inline]
67 fn write_u64(&mut self, i: u64) {
68 self.value = i;
69 }
70
71 #[inline]
72 fn finish(&self) -> u64 {
73 let mut x = self.value;
74 x ^= x >> 30;
75 x = x.wrapping_mul(0xbf58_476d_1ce4_e5b9);
76 x ^= x >> 27;
77 x = x.wrapping_mul(0x94d0_49bb_1331_11eb);
78 x ^ (x >> 31)
79 }
80}
81
82type IdentityBuildHasher = BuildHasherDefault<IdentityHasher>;
83type IdentityHashSet = HashSet<usize, IdentityBuildHasher>;
84type IdentityHashMap<V> = HashMap<usize, V, IdentityBuildHasher>;
85
86thread_local! {
100 static CURRENT_HEAP_STACK: RefCell<Vec<NonNull<Heap>>> = const { RefCell::new(Vec::new()) };
101}
102
103pub struct HeapGuard {
107 _private: (),
110}
111
112impl HeapGuard {
113 pub fn push(heap: &Heap) -> Self {
122 let ptr = NonNull::from(heap);
123 CURRENT_HEAP_STACK.with(|stack| stack.borrow_mut().push(ptr));
124 HeapGuard { _private: () }
125 }
126}
127
128impl Drop for HeapGuard {
129 fn drop(&mut self) {
130 CURRENT_HEAP_STACK.with(|stack| {
131 let popped = stack.borrow_mut().pop();
132 debug_assert!(popped.is_some(), "HeapGuard::drop with empty stack");
133 });
134 }
135}
136
137pub fn with_current_heap<R>(f: impl for<'a> FnOnce(Option<&'a Heap>) -> R) -> R {
144 CURRENT_HEAP_STACK.with(|stack| {
145 let ptr = stack.borrow().last().copied();
146 let heap = ptr.map(|ptr| unsafe { &*ptr.as_ptr() });
150 f(heap)
151 })
152}
153
154#[derive(Copy, Clone, Debug)]
155pub struct HeapRef {
156 ptr: NonNull<Heap>,
157}
158
159impl HeapRef {
160 pub fn from_heap(heap: &Heap) -> Self {
161 HeapRef {
162 ptr: NonNull::from(heap),
163 }
164 }
165
166 pub fn contains_allocation(self, identity: usize, token: usize) -> bool {
167 unsafe { self.ptr.as_ref() }.contains_allocation(identity, token)
172 }
173}
174
175#[derive(Copy, Clone, PartialEq, Eq, Debug)]
177pub enum Color {
178 White0,
182 White1,
184 Gray,
186 Black,
188}
189
190impl Color {
191 pub fn is_white(self) -> bool {
192 matches!(self, Color::White0 | Color::White1)
193 }
194
195 fn other_white(self) -> Self {
196 match self {
197 Color::White0 => Color::White1,
198 Color::White1 => Color::White0,
199 Color::Gray | Color::Black => self,
200 }
201 }
202}
203
204#[derive(Copy, Clone, PartialEq, Eq, Debug)]
208pub enum GcAge {
209 New,
210 Survival,
211 Old0,
212 Old1,
213 Old,
214 Touched1,
215 Touched2,
216}
217
218impl GcAge {
219 pub fn is_old(self) -> bool {
220 !matches!(self, GcAge::New | GcAge::Survival)
221 }
222
223 fn next_after_minor(self) -> Self {
224 match self {
225 GcAge::New => GcAge::Survival,
226 GcAge::Survival | GcAge::Old0 => GcAge::Old1,
227 GcAge::Old1 | GcAge::Old | GcAge::Touched2 => GcAge::Old,
228 GcAge::Touched1 => GcAge::Touched2,
229 }
230 }
231}
232
233pub trait Udata51Probe: std::any::Any {
249 fn is_alive(&self) -> bool;
253
254 fn as_any(&self) -> &dyn std::any::Any;
256}
257
258pub trait FinalizerEntry: Clone {
261 fn identity(&self) -> usize;
262 fn heap_ptr(&self) -> Option<NonNull<GcBox<dyn Trace>>> {
263 None
264 }
265 fn age(&self) -> GcAge;
266 fn is_finalized(&self) -> bool;
267 fn set_finalized(&self, finalized: bool);
268}
269
270pub trait WeakEntry: Clone {
272 type Strong: Clone;
273
274 fn identity(&self) -> usize;
275 fn list_kind(&self) -> WeakListKind;
276 fn upgrade(&self) -> Option<Self::Strong>;
277}
278
279#[derive(Copy, Clone, Debug, PartialEq, Eq)]
280pub enum WeakListKind {
281 WeakValues,
282 Ephemeron,
283 AllWeak,
284}
285
286#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
287pub struct WeakRegistryStats {
288 pub tracked: usize,
289 pub snapshot_live: usize,
290 pub snapshot_dead: usize,
291 pub retained: usize,
292 pub weak_values: usize,
293 pub ephemeron: usize,
294 pub all_weak: usize,
295}
296
297#[derive(Clone, Debug)]
298pub struct WeakRegistry<T: WeakEntry> {
299 weak_values: Vec<T>,
300 ephemeron: Vec<T>,
301 all_weak: Vec<T>,
302 last_stats: WeakRegistryStats,
303}
304
305#[derive(Clone, Debug, PartialEq, Eq)]
306pub struct WeakRegistrySnapshot<T> {
307 pub weak_values: Vec<T>,
308 pub ephemeron: Vec<T>,
309 pub all_weak: Vec<T>,
310}
311
312impl<T> Default for WeakRegistrySnapshot<T> {
313 fn default() -> Self {
314 Self {
315 weak_values: Vec::new(),
316 ephemeron: Vec::new(),
317 all_weak: Vec::new(),
318 }
319 }
320}
321
322impl<T> WeakRegistrySnapshot<T> {
323 pub fn len(&self) -> usize {
324 self.weak_values
325 .len()
326 .saturating_add(self.ephemeron.len())
327 .saturating_add(self.all_weak.len())
328 }
329
330 pub fn into_flat(self) -> Vec<T> {
331 self.weak_values
332 .into_iter()
333 .chain(self.ephemeron)
334 .chain(self.all_weak)
335 .collect()
336 }
337}
338
339impl<T: WeakEntry> Default for WeakRegistry<T> {
340 fn default() -> Self {
341 Self {
342 weak_values: Vec::new(),
343 ephemeron: Vec::new(),
344 all_weak: Vec::new(),
345 last_stats: WeakRegistryStats::default(),
346 }
347 }
348}
349
350impl<T: WeakEntry> WeakRegistry<T> {
351 pub fn len(&self) -> usize {
352 self.weak_values
353 .len()
354 .saturating_add(self.ephemeron.len())
355 .saturating_add(self.all_weak.len())
356 }
357
358 pub fn stats(&self) -> WeakRegistryStats {
359 self.last_stats
360 }
361
362 fn list_mut(&mut self, kind: WeakListKind) -> &mut Vec<T> {
363 match kind {
364 WeakListKind::WeakValues => &mut self.weak_values,
365 WeakListKind::Ephemeron => &mut self.ephemeron,
366 WeakListKind::AllWeak => &mut self.all_weak,
367 }
368 }
369
370 pub fn remove_identity(&mut self, id: usize) {
371 self.weak_values.retain(|entry| entry.identity() != id);
372 self.ephemeron.retain(|entry| entry.identity() != id);
373 self.all_weak.retain(|entry| entry.identity() != id);
374 self.last_stats.tracked = self.len();
375 self.last_stats.retained = self.len();
376 self.update_cohort_stats();
377 }
378
379 fn update_cohort_stats(&mut self) {
380 self.last_stats.weak_values = self.weak_values.len();
381 self.last_stats.ephemeron = self.ephemeron.len();
382 self.last_stats.all_weak = self.all_weak.len();
383 }
384
385 pub fn push_unique(&mut self, entry: T) {
386 let id = entry.identity();
387 self.remove_identity(id);
388 self.list_mut(entry.list_kind()).push(entry);
389 self.last_stats.tracked = self.len();
390 self.last_stats.retained = self.len();
391 self.update_cohort_stats();
392 }
393
394 pub fn live_snapshot_by_kind(&mut self) -> WeakRegistrySnapshot<T::Strong> {
395 let tracked_before = self.len();
396 let weak_values_capacity = self.weak_values.len();
397 let ephemeron_capacity = self.ephemeron.len();
398 let all_weak_capacity = self.all_weak.len();
399 let mut seen = std::collections::HashSet::<usize>::with_capacity(tracked_before);
400 let mut live = WeakRegistrySnapshot {
401 weak_values: Vec::with_capacity(weak_values_capacity),
402 ephemeron: Vec::with_capacity(ephemeron_capacity),
403 all_weak: Vec::with_capacity(all_weak_capacity),
404 };
405 let mut dead = 0usize;
406
407 let entries = std::mem::take(&mut self.weak_values)
408 .into_iter()
409 .chain(std::mem::take(&mut self.ephemeron))
410 .chain(std::mem::take(&mut self.all_weak));
411 for entry in entries {
412 if !seen.insert(entry.identity()) {
413 continue;
414 }
415 match entry.upgrade() {
416 Some(strong) => {
417 match entry.list_kind() {
418 WeakListKind::WeakValues => live.weak_values.push(strong),
419 WeakListKind::Ephemeron => live.ephemeron.push(strong),
420 WeakListKind::AllWeak => live.all_weak.push(strong),
421 }
422 self.list_mut(entry.list_kind()).push(entry);
423 }
424 None => dead += 1,
425 }
426 }
427
428 self.last_stats = WeakRegistryStats {
429 tracked: tracked_before,
430 snapshot_live: live.len(),
431 snapshot_dead: dead,
432 retained: self.len(),
433 weak_values: self.weak_values.len(),
434 ephemeron: self.ephemeron.len(),
435 all_weak: self.all_weak.len(),
436 };
437 live
438 }
439
440 pub fn live_snapshot(&mut self) -> Vec<T::Strong> {
441 self.live_snapshot_by_kind().into_flat()
442 }
443
444 pub fn retain_identities(&mut self, ids: &std::collections::HashSet<usize>) {
445 self.weak_values
446 .retain(|entry| ids.contains(&entry.identity()));
447 self.ephemeron
448 .retain(|entry| ids.contains(&entry.identity()));
449 self.all_weak
450 .retain(|entry| ids.contains(&entry.identity()));
451 self.last_stats.retained = self.len();
452 self.last_stats.tracked = self.len();
453 self.update_cohort_stats();
454 }
455}
456
457#[derive(Clone, Debug)]
458pub struct FinalizerRegistry<T: FinalizerEntry> {
459 pending: Vec<T>,
460 to_be_finalized: Vec<T>,
461 pending_reallyold: usize,
462 pending_old1: usize,
463 pending_survival: usize,
464}
465
466#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
467pub struct FinalizerRegistryStats {
468 pub pending_young: usize,
469 pub pending_old: usize,
470 pub to_be_finalized_young: usize,
471 pub to_be_finalized_old: usize,
472 pub finobj_new: usize,
473 pub finobj_survival: usize,
474 pub finobj_old1: usize,
475 pub finobj_reallyold: usize,
476 pub finobj_minor_scan: usize,
477}
478
479impl<T: FinalizerEntry> Default for FinalizerRegistry<T> {
480 fn default() -> Self {
481 Self {
482 pending: Vec::new(),
483 to_be_finalized: Vec::new(),
484 pending_reallyold: 0,
485 pending_old1: 0,
486 pending_survival: 0,
487 }
488 }
489}
490
491impl<T: FinalizerEntry> FinalizerRegistry<T> {
492 fn pending_new_len(&self) -> usize {
493 self.pending.len().saturating_sub(
494 self.pending_reallyold
495 .saturating_add(self.pending_old1)
496 .saturating_add(self.pending_survival),
497 )
498 }
499
500 fn minor_scan_start(&self) -> usize {
501 self.pending_reallyold.saturating_add(self.pending_old1)
502 }
503
504 fn debug_assert_pending_cohorts(&self) {
505 debug_assert!(
506 self.pending_reallyold
507 .saturating_add(self.pending_old1)
508 .saturating_add(self.pending_survival)
509 <= self.pending.len()
510 );
511 }
512
513 pub fn pending(&self) -> &[T] {
514 &self.pending
515 }
516
517 pub fn pending_snapshot(&self) -> Vec<T> {
518 self.pending.clone()
519 }
520
521 pub fn pending_minor_snapshot(&self) -> Vec<T> {
522 self.pending[self.minor_scan_start().min(self.pending.len())..].to_vec()
523 }
524
525 pub fn to_be_finalized(&self) -> &[T] {
526 &self.to_be_finalized
527 }
528
529 pub fn pending_len(&self) -> usize {
530 self.pending.len()
531 }
532
533 pub fn to_be_finalized_len(&self) -> usize {
534 self.to_be_finalized.len()
535 }
536
537 pub fn has_to_be_finalized(&self) -> bool {
538 !self.to_be_finalized.is_empty()
539 }
540
541 pub fn stats(&self) -> FinalizerRegistryStats {
542 fn count_by_age<T: FinalizerEntry>(objects: &[T]) -> (usize, usize) {
543 objects
544 .iter()
545 .fold((0usize, 0usize), |(young, old), object| {
546 if object.age().is_old() {
547 (young, old + 1)
548 } else {
549 (young + 1, old)
550 }
551 })
552 }
553 let (pending_young, pending_old) = count_by_age(&self.pending);
554 let (to_be_finalized_young, to_be_finalized_old) = count_by_age(&self.to_be_finalized);
555 FinalizerRegistryStats {
556 pending_young,
557 pending_old,
558 to_be_finalized_young,
559 to_be_finalized_old,
560 finobj_new: self.pending_new_len(),
561 finobj_survival: self.pending_survival,
562 finobj_old1: self.pending_old1,
563 finobj_reallyold: self.pending_reallyold,
564 finobj_minor_scan: self.pending.len().saturating_sub(self.minor_scan_start()),
565 }
566 }
567
568 pub fn push_pending_unique(&mut self, object: T) -> bool {
569 if object.is_finalized() {
570 return false;
571 }
572 let id = object.identity();
573 if !self.pending.iter().any(|o| o.identity() == id) {
574 object.set_finalized(true);
575 self.pending.push(object);
576 self.debug_assert_pending_cohorts();
577 true
578 } else {
579 false
580 }
581 }
582
583 pub fn take_pending(&mut self) -> Vec<T> {
584 self.pending_reallyold = 0;
585 self.pending_old1 = 0;
586 self.pending_survival = 0;
587 std::mem::take(&mut self.pending)
588 }
589
590 fn retain_pending_not_in(&mut self, ids: &std::collections::HashSet<usize>) {
591 if ids.is_empty() {
592 return;
593 }
594 let original_reallyold = self.pending_reallyold;
595 let original_old1 = self.pending_old1;
596 let original_survival = self.pending_survival;
597 let mut retained_reallyold = original_reallyold;
598 let mut retained_old1 = original_old1;
599 let mut retained_survival = original_survival;
600 let mut retained = Vec::with_capacity(self.pending.len());
601 for (index, object) in std::mem::take(&mut self.pending).into_iter().enumerate() {
602 if ids.contains(&object.identity()) {
603 if index < original_reallyold {
604 retained_reallyold -= 1;
605 } else if index < original_reallyold + original_old1 {
606 retained_old1 -= 1;
607 } else if index < original_reallyold + original_old1 + original_survival {
608 retained_survival -= 1;
609 }
610 } else {
611 retained.push(object);
612 }
613 }
614 self.pending = retained;
615 self.pending_reallyold = retained_reallyold;
616 self.pending_old1 = retained_old1;
617 self.pending_survival = retained_survival;
618 self.debug_assert_pending_cohorts();
619 }
620
621 pub fn push_to_be_finalized(&mut self, object: T) {
622 object.set_finalized(true);
623 self.to_be_finalized.push(object);
624 }
625
626 fn extend_to_be_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
627 let drain_order: Vec<T> = objects.into_iter().rev().collect();
628 for object in drain_order.iter().cloned() {
629 self.push_to_be_finalized(object);
630 }
631 drain_order
632 }
633
634 pub fn promote_pending_to_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
635 if objects.is_empty() {
636 return Vec::new();
637 }
638 let mut ids: std::collections::HashSet<usize> =
639 std::collections::HashSet::with_capacity(objects.len());
640 ids.extend(objects.iter().map(|object| object.identity()));
641 self.retain_pending_not_in(&ids);
642 self.extend_to_be_finalized(objects)
643 }
644
645 pub fn promote_all_pending_to_old(&mut self) {
646 self.pending_reallyold = self.pending.len();
647 self.pending_old1 = 0;
648 self.pending_survival = 0;
649 }
650
651 pub fn reset_generation_boundaries(&mut self) {
652 self.pending_reallyold = 0;
653 self.pending_old1 = 0;
654 self.pending_survival = 0;
655 }
656
657 pub fn finish_minor_collection(&mut self) {
658 let new = self.pending_new_len();
659 self.pending_reallyold = self.pending_reallyold.saturating_add(self.pending_old1);
660 self.pending_old1 = self.pending_survival;
661 self.pending_survival = new;
662 self.debug_assert_pending_cohorts();
663 }
664
665 pub fn pop_to_be_finalized(&mut self) -> Option<T> {
666 let object = if self.to_be_finalized.is_empty() {
667 None
668 } else {
669 Some(self.to_be_finalized.remove(0))
670 };
671 if let Some(ref object) = object {
672 object.set_finalized(false);
673 }
674 object
675 }
676}
677
678#[repr(C)]
680pub struct GcHeader {
681 color: Cell<Color>,
688 age: Cell<GcAge>,
689 flags: Cell<u8>,
697 size: Cell<u32>,
704 next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
706 gray_next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
708}
709
710const HDR_FINALIZED: u8 = 1;
711const HDR_COLLECTED: u8 = 2;
712const HDR_GRAY_LISTED: u8 = 4;
713const HDR_FREED: u8 = 8;
718
719impl GcHeader {
720 fn new_white(size: usize, color: Color, flags: u8) -> Self {
721 Self {
722 color: Cell::new(color),
723 age: Cell::new(GcAge::New),
724 flags: Cell::new(flags),
725 size: Cell::new(size.min(u32::MAX as usize) as u32),
726 next: Cell::new(None),
727 gray_next: Cell::new(None),
728 }
729 }
730
731 fn flag(&self, bit: u8) -> bool {
732 self.flags.get() & bit != 0
733 }
734
735 fn set_flag(&self, bit: u8, on: bool) {
736 let f = self.flags.get();
737 self.flags.set(if on { f | bit } else { f & !bit });
738 }
739
740 pub fn finalized(&self) -> bool {
741 self.flag(HDR_FINALIZED)
742 }
743
744 pub fn set_finalized(&self, finalized: bool) {
745 self.set_flag(HDR_FINALIZED, finalized);
746 }
747
748 pub fn collected(&self) -> bool {
749 self.flag(HDR_COLLECTED)
750 }
751
752 pub fn gray_listed(&self) -> bool {
753 self.flag(HDR_GRAY_LISTED)
754 }
755
756 pub fn set_gray_listed(&self, listed: bool) {
757 self.set_flag(HDR_GRAY_LISTED, listed);
758 }
759
760 pub fn size(&self) -> usize {
761 self.size.get() as usize
762 }
763
764 pub fn set_size(&self, size: usize) {
765 self.size.set(size.min(u32::MAX as usize) as u32);
766 }
767}
768
769#[repr(C)]
771pub struct GcBox<T: ?Sized> {
772 header: GcHeader,
773 value: T,
774}
775
776impl<T: ?Sized> GcBox<T> {
777 pub fn header(&self) -> &GcHeader {
778 &self.header
779 }
780 pub fn value(&self) -> &T {
781 &self.value
782 }
783}
784
785pub struct Gc<T: ?Sized> {
787 ptr: NonNull<GcBox<T>>,
788 _marker: PhantomData<T>,
790}
791
792impl<T: ?Sized> Copy for Gc<T> {}
796impl<T: ?Sized> Clone for Gc<T> {
797 fn clone(&self) -> Self {
798 *self
799 }
800}
801
802impl<T: ?Sized> PartialEq for Gc<T> {
803 fn eq(&self, other: &Self) -> bool {
804 std::ptr::addr_eq(self.ptr.as_ptr(), other.ptr.as_ptr())
805 }
806}
807impl<T: ?Sized> Eq for Gc<T> {}
808
809impl<T: ?Sized> std::hash::Hash for Gc<T> {
810 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
811 self.ptr.as_ptr().hash(state)
812 }
813}
814
815impl<T: ?Sized> std::fmt::Debug for Gc<T> {
816 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
817 write!(f, "Gc({:p})", self.ptr.as_ptr())
818 }
819}
820
821impl<T: Trace + 'static> Gc<T> {
822 pub fn new_uncollected(value: T) -> Self {
828 let size = std::mem::size_of::<T>();
829 let boxed = Box::new(GcBox {
830 header: GcHeader::new_white(size, Color::White0, 0),
831 value,
832 });
833 Gc {
834 ptr: NonNull::new(Box::into_raw(boxed)).expect("Box::into_raw is non-null"),
835 _marker: PhantomData,
836 }
837 }
838
839 pub fn as_trace_ptr(self) -> NonNull<GcBox<dyn Trace>> {
841 self.ptr
842 }
843}
844
845impl<T: ?Sized> Gc<T> {
846 pub fn ptr_eq(a: Self, b: Self) -> bool {
848 std::ptr::addr_eq(a.ptr.as_ptr(), b.ptr.as_ptr())
849 }
850
851 pub fn identity(self) -> usize {
854 self.ptr.as_ptr() as *const () as usize
855 }
856
857 fn as_box(&self) -> &GcBox<T> {
861 let bx = unsafe { self.ptr.as_ref() };
867 debug_assert!(
868 !bx.header.flag(HDR_FREED),
869 "use-after-sweep: Gc<{}> dereferenced after the collector swept it \
870 (caught by LUA_RS_GC_QUARANTINE; this is a rooting bug — the object \
871 was reachable by execution but not by the root trace)",
872 std::any::type_name::<T>()
873 );
874 bx
875 }
876
877 fn header(&self) -> &GcHeader {
878 &self.as_box().header
879 }
880
881 pub fn color(self) -> Color {
882 self.header().color.get()
883 }
884
885 pub fn set_color(self, color: Color) {
886 self.header().color.set(color);
887 }
888
889 pub fn age(self) -> GcAge {
890 self.header().age.get()
891 }
892
893 pub fn set_age(self, age: GcAge) {
894 self.header().age.set(age);
895 }
896
897 pub fn is_finalized(self) -> bool {
898 self.header().finalized()
899 }
900
901 pub fn set_finalized(self, finalized: bool) {
902 self.header().set_finalized(finalized);
903 }
904
905 pub fn account_buffer(&self, heap: &Heap, delta: isize) {
915 if delta == 0 {
916 return;
917 }
918 let header = self.header();
919 if !header.collected() {
920 return;
921 }
922 if delta >= 0 {
923 header.set_size(header.size().saturating_add(delta as usize));
924 } else {
925 header.set_size(header.size().saturating_sub((-delta) as usize));
926 }
927 heap.adjust_bytes(delta);
928 }
929}
930
931impl<T: ?Sized> std::ops::Deref for Gc<T> {
932 type Target = T;
933 fn deref(&self) -> &T {
934 &self.as_box().value
935 }
936}
937
938impl<T: ?Sized> AsRef<T> for Gc<T> {
939 fn as_ref(&self) -> &T {
940 &self.as_box().value
941 }
942}
943
944pub trait Trace {
961 fn trace(&self, m: &mut Marker);
962
963 fn type_name(&self) -> &'static str {
969 "unknown"
970 }
971}
972
973impl<T: Trace> Trace for Vec<T> {
975 fn trace(&self, m: &mut Marker) {
976 for item in self.iter() {
977 item.trace(m);
978 }
979 }
980}
981
982impl<T: Trace> Trace for Option<T> {
983 fn trace(&self, m: &mut Marker) {
984 if let Some(v) = self {
985 v.trace(m);
986 }
987 }
988}
989
990impl<T: Trace + ?Sized> Trace for Box<T> {
991 fn trace(&self, m: &mut Marker) {
992 (**self).trace(m);
993 }
994}
995
996impl<T: Trace + ?Sized> Trace for std::rc::Rc<T> {
997 fn trace(&self, m: &mut Marker) {
998 (**self).trace(m);
999 }
1000}
1001
1002impl<T: Trace> Trace for RefCell<T> {
1003 fn trace(&self, m: &mut Marker) {
1004 self.borrow().trace(m);
1005 }
1006}
1007
1008impl<T: Trace + 'static> Trace for Gc<T> {
1010 fn trace(&self, m: &mut Marker) {
1011 m.mark(*self);
1012 }
1013}
1014
1015macro_rules! trace_noop {
1017 ($($t:ty),*) => {
1018 $(impl Trace for $t {
1019 fn trace(&self, _m: &mut Marker) {}
1020 })*
1021 };
1022}
1023trace_noop!(
1024 bool, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, char, String,
1025 str
1026);
1027
1028impl<T> Trace for std::marker::PhantomData<T> {
1029 fn trace(&self, _m: &mut Marker) {}
1030}
1031
1032#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1037pub struct MarkerStats {
1038 pub marked: usize,
1039 pub marked_young: usize,
1040 pub marked_old: usize,
1041 pub traced: usize,
1042 pub traced_young: usize,
1043 pub traced_old: usize,
1044}
1045
1046#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1048pub struct SweepStats {
1049 pub visited: usize,
1050 pub visited_young: usize,
1051 pub visited_old: usize,
1052 pub revisit: usize,
1053 pub freed: usize,
1054 pub freed_bytes: usize,
1055}
1056
1057impl SweepStats {
1058 fn record_visit(&mut self, age: GcAge) {
1059 self.visited += 1;
1060 if age.is_old() {
1061 self.visited_old += 1;
1062 } else {
1063 self.visited_young += 1;
1064 }
1065 }
1066
1067 fn record_free(&mut self, bytes: usize) {
1068 self.freed += 1;
1069 self.freed_bytes += bytes;
1070 }
1071
1072 fn add(&mut self, other: Self) {
1073 self.visited += other.visited;
1074 self.visited_young += other.visited_young;
1075 self.visited_old += other.visited_old;
1076 self.revisit += other.revisit;
1077 self.freed += other.freed;
1078 self.freed_bytes += other.freed_bytes;
1079 }
1080}
1081
1082struct OldRevisitTracker {
1083 old_revisit_ids: Vec<usize>,
1084 processed_ids: Vec<usize>,
1085}
1086
1087impl OldRevisitTracker {
1088 fn new(old_revisit: &[NonNull<GcBox<dyn Trace>>]) -> Option<Self> {
1089 if old_revisit.is_empty() {
1090 return None;
1091 }
1092 let mut old_revisit_ids: Vec<usize> = old_revisit
1093 .iter()
1094 .map(|ptr| ptr.as_ptr() as *const () as usize)
1095 .collect();
1096 old_revisit_ids.sort_unstable();
1097 old_revisit_ids.dedup();
1098 Some(Self {
1099 old_revisit_ids,
1100 processed_ids: Vec::new(),
1101 })
1102 }
1103
1104 #[inline(always)]
1105 fn record_processed(&mut self, id: usize) {
1106 if self.old_revisit_ids.binary_search(&id).is_ok() {
1107 self.processed_ids.push(id);
1108 }
1109 }
1110
1111 fn finish(&mut self) {
1112 self.processed_ids.sort_unstable();
1113 self.processed_ids.dedup();
1114 }
1115
1116 #[inline(always)]
1117 fn was_processed(&self, id: usize) -> bool {
1118 self.processed_ids.binary_search(&id).is_ok()
1119 }
1120}
1121
1122#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1124pub struct AllGcCohortStats {
1125 pub new: usize,
1126 pub survival: usize,
1127 pub old1: usize,
1128 pub old: usize,
1129}
1130
1131#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1132enum MarkerMode {
1133 Full,
1134 Minor,
1135}
1136
1137pub struct Marker {
1139 gray_queue: Vec<NonNull<GcBox<dyn Trace>>>,
1140 visited: IdentityHashSet,
1141 stats: MarkerStats,
1142 mode: MarkerMode,
1143}
1144
1145impl Marker {
1146 fn new_with_capacity(mode: MarkerMode, capacity: usize) -> Self {
1147 Self {
1148 gray_queue: Vec::with_capacity(256),
1149 visited: IdentityHashSet::with_capacity_and_hasher(
1150 capacity,
1151 IdentityBuildHasher::default(),
1152 ),
1153 stats: MarkerStats::default(),
1154 mode,
1155 }
1156 }
1157
1158 fn should_trace_age(&self, age: GcAge) -> bool {
1159 match self.mode {
1160 MarkerMode::Full => true,
1161 MarkerMode::Minor => !matches!(age, GcAge::Old),
1162 }
1163 }
1164
1165 pub fn mark<T: Trace + 'static>(&mut self, gc: Gc<T>) {
1178 let ptr: NonNull<GcBox<dyn Trace>> = gc.ptr;
1179 self.mark_box(ptr, gc.header(), gc.identity());
1180 }
1181
1182 fn mark_box(&mut self, ptr: NonNull<GcBox<dyn Trace>>, header: &GcHeader, id: usize) {
1183 debug_assert!(
1184 !header.flag(HDR_FREED),
1185 "GC marker reached a quarantined (swept) object at {id:#x} — a root \
1186 traced a stale GcRef (caught by LUA_RS_GC_QUARANTINE; bug-B class: \
1187 garbage slot fed into the marker)"
1188 );
1189 if self.visited.insert(id) {
1190 let age = header.age.get();
1191 self.stats.marked += 1;
1192 if age.is_old() {
1193 self.stats.marked_old += 1;
1194 } else {
1195 self.stats.marked_young += 1;
1196 }
1197 if self.should_trace_age(age) {
1198 header.color.set(Color::Gray);
1199 self.gray_queue.push(ptr);
1200 }
1201 }
1202 }
1203
1204 pub fn try_visit(&mut self, addr: usize) -> bool {
1209 self.visited.insert(addr)
1210 }
1211
1212 pub fn is_visited(&self, id: usize) -> bool {
1217 self.visited.contains(&id)
1218 }
1219
1220 pub fn is_marked_or_old<T: Trace + 'static>(&self, gc: Gc<T>) -> bool {
1223 self.is_visited(gc.identity())
1224 || (matches!(self.mode, MarkerMode::Minor) && gc.age().is_old())
1225 }
1226
1227 pub fn visited_count(&self) -> usize {
1231 self.visited.len()
1232 }
1233
1234 pub fn stats(&self) -> MarkerStats {
1236 self.stats
1237 }
1238
1239 pub fn drain_gray_queue(&mut self) {
1248 while let Some(gray_ptr) = self.gray_queue.pop() {
1249 unsafe {
1250 let bx = gray_ptr.as_ref();
1251 self.stats.traced += 1;
1252 if bx.header.age.get().is_old() {
1253 self.stats.traced_old += 1;
1254 } else {
1255 self.stats.traced_young += 1;
1256 }
1257 bx.header.color.set(Color::Black);
1258 bx.value.trace(self);
1259 }
1260 }
1261 }
1262}
1263
1264#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1283pub enum GcState {
1284 Pause,
1285 Propagate,
1286 EnterAtomic,
1287 Atomic,
1288 SweepAllGc,
1289 SweepFinObj,
1290 SweepToBeFnz,
1291 SweepEnd,
1292 CallFin,
1293}
1294
1295impl GcState {
1296 pub fn is_pause(self) -> bool {
1297 matches!(self, GcState::Pause)
1298 }
1299 pub fn is_propagate(self) -> bool {
1300 matches!(self, GcState::Propagate)
1301 }
1302 pub fn is_invariant(self) -> bool {
1303 matches!(
1304 self,
1305 GcState::Propagate | GcState::EnterAtomic | GcState::Atomic
1306 )
1307 }
1308 pub fn is_sweep(self) -> bool {
1309 matches!(
1310 self,
1311 GcState::SweepAllGc | GcState::SweepFinObj | GcState::SweepToBeFnz | GcState::SweepEnd
1312 )
1313 }
1314}
1315
1316#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1318pub enum StepOutcome {
1319 Paused,
1321 InProgress,
1324 SkippedStopped,
1327}
1328
1329#[derive(Copy, Clone, Debug)]
1337pub struct StepBudget {
1338 pub remaining_work: isize,
1339 pub max_credit: isize,
1340}
1341
1342impl StepBudget {
1343 pub fn from_work(work: isize) -> Self {
1345 Self {
1346 remaining_work: work.max(1),
1347 max_credit: work.max(1),
1348 }
1349 }
1350}
1351
1352const GC_MIN_THRESHOLD: usize = 1024 * 1024;
1367
1368pub struct Heap {
1369 head: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1372 finobj: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1374 tobefnz: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1376 survival: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1379 old1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1382 reallyold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1385 firstold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1388 finobjsur: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1390 finobjold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1392 finobjrold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1394 bytes: Cell<usize>,
1396 objects: Cell<usize>,
1398 current_white: Cell<Color>,
1400 allocation_tokens: RefCell<IdentityHashMap<usize>>,
1404 next_allocation_token: Cell<usize>,
1406 threshold: Cell<usize>,
1408 stress: bool,
1415 quarantine: bool,
1426 quarantined: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1430 pause_multiplier: Cell<usize>,
1432 state: Cell<GcState>,
1434 paused: Cell<bool>,
1437 collections: Cell<usize>,
1439 minor_collections: Cell<usize>,
1441 full_collections: Cell<usize>,
1443 last_mark_stats: Cell<MarkerStats>,
1445 last_sweep_stats: Cell<SweepStats>,
1447 grayagain: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1451 marker: RefCell<Option<Marker>>,
1454 marker_pool: RefCell<Option<(Vec<NonNull<GcBox<dyn Trace>>>, IdentityHashSet)>>,
1460 sweep_prev_next: Cell<Option<NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>>>,
1465 v51_udata_roster: RefCell<Vec<std::rc::Rc<dyn Udata51Probe>>>,
1472}
1473
1474impl Default for Heap {
1475 fn default() -> Self {
1476 Self::new()
1477 }
1478}
1479
1480impl Heap {
1481 pub fn new() -> Self {
1482 Self {
1483 head: Cell::new(None),
1484 finobj: Cell::new(None),
1485 tobefnz: Cell::new(None),
1486 survival: Cell::new(None),
1487 old1: Cell::new(None),
1488 reallyold: Cell::new(None),
1489 firstold1: Cell::new(None),
1490 finobjsur: Cell::new(None),
1491 finobjold1: Cell::new(None),
1492 finobjrold: Cell::new(None),
1493 bytes: Cell::new(0),
1494 objects: Cell::new(0),
1495 current_white: Cell::new(Color::White0),
1496 allocation_tokens: RefCell::new(IdentityHashMap::default()),
1497 next_allocation_token: Cell::new(1),
1498 threshold: Cell::new(64 * 1024), stress: std::env::var_os("LUA_RS_GC_STRESS").is_some_and(|v| v == "1"),
1500 quarantine: std::env::var_os("LUA_RS_GC_QUARANTINE").is_some_and(|v| v == "1"),
1501 quarantined: Cell::new(None),
1502 pause_multiplier: Cell::new(200), state: Cell::new(GcState::Pause),
1504 paused: Cell::new(true), collections: Cell::new(0),
1506 minor_collections: Cell::new(0),
1507 full_collections: Cell::new(0),
1508 last_mark_stats: Cell::new(MarkerStats::default()),
1509 last_sweep_stats: Cell::new(SweepStats::default()),
1510 grayagain: Cell::new(None),
1511 marker: RefCell::new(None),
1512 marker_pool: RefCell::new(None),
1513 sweep_prev_next: Cell::new(None),
1514 v51_udata_roster: RefCell::new(Vec::new()),
1515 }
1516 }
1517
1518 pub fn unpause(&self) {
1521 self.paused.set(false);
1522 }
1523
1524 pub fn is_paused(&self) -> bool {
1525 self.paused.get()
1526 }
1527
1528 pub fn allocate<T: Trace + 'static>(&self, value: T) -> Gc<T> {
1530 let size = std::mem::size_of::<GcBox<T>>();
1531 let boxed = Box::new(GcBox {
1532 header: GcHeader::new_white(size, self.current_white.get(), HDR_COLLECTED),
1533 value,
1534 });
1535 boxed.header.next.set(self.head.get());
1536 let raw: *mut GcBox<T> = Box::into_raw(boxed);
1537 let ptr: NonNull<GcBox<T>> = NonNull::new(raw).expect("Box::into_raw is non-null");
1538 let dyn_ptr: NonNull<GcBox<dyn Trace>> = ptr;
1539 self.head.set(Some(dyn_ptr));
1540 self.bytes.set(self.bytes.get() + size);
1541 self.objects.set(self.objects.get() + 1);
1542 Gc {
1543 ptr,
1544 _marker: PhantomData,
1545 }
1546 }
1547
1548 pub fn bytes_used(&self) -> usize {
1550 self.bytes.get()
1551 }
1552
1553 pub fn adjust_bytes(&self, delta: isize) {
1558 if delta >= 0 {
1559 self.bytes
1560 .set(self.bytes.get().saturating_add(delta as usize));
1561 } else {
1562 self.bytes
1563 .set(self.bytes.get().saturating_sub((-delta) as usize));
1564 }
1565 }
1566
1567 pub fn threshold_bytes(&self) -> usize {
1572 self.threshold.get()
1573 }
1574
1575 pub fn set_threshold_bytes(&self, threshold: usize) {
1581 self.threshold.set(threshold.max(1));
1582 }
1583
1584 pub fn would_collect(&self) -> bool {
1588 if self.stress {
1589 return !self.paused.get();
1590 }
1591 !self.paused.get() && self.bytes.get() >= self.threshold.get()
1592 }
1593
1594 pub fn collections(&self) -> usize {
1595 self.collections.get()
1596 }
1597
1598 pub fn minor_collections(&self) -> usize {
1599 self.minor_collections.get()
1600 }
1601
1602 pub fn full_collections(&self) -> usize {
1603 self.full_collections.get()
1604 }
1605
1606 pub fn last_mark_stats(&self) -> MarkerStats {
1607 self.last_mark_stats.get()
1608 }
1609
1610 pub fn last_sweep_stats(&self) -> SweepStats {
1611 self.last_sweep_stats.get()
1612 }
1613
1614 pub fn allgc_cohort_stats(&self) -> AllGcCohortStats {
1615 let survival = self.survival.get();
1616 let old1 = self.old1.get();
1617 let reallyold = self.reallyold.get();
1618 let mut stats = AllGcCohortStats::default();
1619 let mut cursor = self.head.get();
1620 let mut seen = IdentityHashSet::default();
1621 let mut cohort = 0u8;
1622 while let Some(ptr) = cursor {
1623 let id = ptr.as_ptr() as *const () as usize;
1624 if !seen.insert(id) {
1625 break;
1626 }
1627 if Some(ptr) == reallyold {
1628 cohort = 3;
1629 } else if Some(ptr) == old1 {
1630 cohort = 2;
1631 } else if Some(ptr) == survival {
1632 cohort = 1;
1633 }
1634 match cohort {
1635 0 => stats.new += 1,
1636 1 => stats.survival += 1,
1637 2 => stats.old1 += 1,
1638 _ => stats.old += 1,
1639 }
1640 cursor = self.header_from_ptr(ptr).next.get();
1641 }
1642 stats
1643 }
1644
1645 fn next_token(&self) -> usize {
1646 let token = self.next_allocation_token.get().max(1);
1647 let next = token.checked_add(1).unwrap_or(1).max(1);
1648 self.next_allocation_token.set(next);
1649 token
1650 }
1651
1652 fn current_white(&self) -> Color {
1653 self.current_white.get()
1654 }
1655
1656 fn other_white(&self) -> Color {
1657 self.current_white.get().other_white()
1658 }
1659
1660 fn flip_current_white(&self) {
1661 self.current_white.set(self.other_white());
1662 }
1663
1664 fn for_each_list_header(
1665 &self,
1666 head: Option<NonNull<GcBox<dyn Trace>>>,
1667 f: &mut impl FnMut(&GcHeader),
1668 ) {
1669 let mut cursor = head;
1670 while let Some(ptr) = cursor {
1671 let header = self.header_from_ptr(ptr);
1672 cursor = header.next.get();
1673 f(header);
1674 }
1675 }
1676
1677 fn for_each_header(&self, mut f: impl FnMut(&GcHeader)) {
1678 self.for_each_list_header(self.head.get(), &mut f);
1679 self.for_each_list_header(self.finobj.get(), &mut f);
1680 self.for_each_list_header(self.tobefnz.get(), &mut f);
1681 }
1682
1683 fn header_from_ptr<'a>(&'a self, ptr: NonNull<GcBox<dyn Trace>>) -> &'a GcHeader {
1684 unsafe { &(*ptr.as_ptr()).header }
1685 }
1686
1687 fn release_box(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1694 if self.quarantine {
1695 let header = self.header_from_ptr(ptr);
1696 header.set_flag(HDR_FREED, true);
1697 header.next.set(self.quarantined.get());
1698 self.quarantined.set(Some(ptr));
1699 } else {
1700 unsafe {
1704 let _ = Box::from_raw(ptr.as_ptr());
1705 }
1706 }
1707 }
1708
1709 fn clear_generation_cursors(&self) {
1710 self.survival.set(None);
1711 self.old1.set(None);
1712 self.reallyold.set(None);
1713 self.firstold1.set(None);
1714 self.finobjsur.set(None);
1715 self.finobjold1.set(None);
1716 self.finobjrold.set(None);
1717 self.clear_grayagain();
1718 }
1719
1720 fn set_all_cursors_to_head(&self) {
1721 let head = self.head.get();
1722 self.survival.set(head);
1723 self.old1.set(head);
1724 self.reallyold.set(head);
1725 self.firstold1.set(None);
1726 let finobj = self.finobj.get();
1727 self.finobjsur.set(finobj);
1728 self.finobjold1.set(finobj);
1729 self.finobjrold.set(finobj);
1730 self.clear_grayagain();
1731 }
1732
1733 fn correct_generation_pointers(
1734 &self,
1735 removed: NonNull<GcBox<dyn Trace>>,
1736 next: Option<NonNull<GcBox<dyn Trace>>>,
1737 ) {
1738 if self.survival.get() == Some(removed) {
1739 self.survival.set(next);
1740 }
1741 if self.old1.get() == Some(removed) {
1742 self.old1.set(next);
1743 }
1744 if self.reallyold.get() == Some(removed) {
1745 self.reallyold.set(next);
1746 }
1747 if self.firstold1.get() == Some(removed) {
1748 self.firstold1.set(next);
1749 }
1750 if self.finobjsur.get() == Some(removed) {
1751 self.finobjsur.set(next);
1752 }
1753 if self.finobjold1.get() == Some(removed) {
1754 self.finobjold1.set(next);
1755 }
1756 if self.finobjrold.get() == Some(removed) {
1757 self.finobjrold.set(next);
1758 }
1759 if self.header_from_ptr(removed).gray_listed() {
1760 self.unlink_grayagain(removed);
1761 }
1762 }
1763
1764 fn unlink_from_list(
1765 &self,
1766 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1767 ptr: NonNull<GcBox<dyn Trace>>,
1768 ) -> bool {
1769 let mut prev_cell = list;
1770 loop {
1771 let Some(current) = prev_cell.get() else {
1772 return false;
1773 };
1774 let header = self.header_from_ptr(current);
1775 let next = header.next.get();
1776 if std::ptr::addr_eq(current.as_ptr(), ptr.as_ptr()) {
1777 prev_cell.set(next);
1778 let prev_next_ptr = NonNull::from(prev_cell);
1779 let removed_next_ptr = NonNull::from(&self.header_from_ptr(ptr).next);
1780 if self.sweep_prev_next.get() == Some(removed_next_ptr) {
1781 self.sweep_prev_next.set(Some(prev_next_ptr));
1782 }
1783 self.correct_generation_pointers(ptr, next);
1784 header.next.set(None);
1785 return true;
1786 }
1787 prev_cell = &header.next;
1788 }
1789 }
1790
1791 fn link_to_head(
1792 &self,
1793 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1794 ptr: NonNull<GcBox<dyn Trace>>,
1795 ) {
1796 let header = self.header_from_ptr(ptr);
1797 header.next.set(list.get());
1798 list.set(Some(ptr));
1799 }
1800
1801 fn link_to_tail(
1802 &self,
1803 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1804 ptr: NonNull<GcBox<dyn Trace>>,
1805 ) {
1806 let mut last_cell = list;
1807 loop {
1808 let Some(current) = last_cell.get() else {
1809 let header = self.header_from_ptr(ptr);
1810 header.next.set(None);
1811 last_cell.set(Some(ptr));
1812 return;
1813 };
1814 last_cell = &self.header_from_ptr(current).next;
1815 }
1816 }
1817
1818 pub fn move_allgc_to_finobj(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1819 let header = self.header_from_ptr(ptr);
1820 if !header.collected() {
1821 return false;
1822 }
1823 if !self.unlink_from_list(&self.head, ptr) {
1824 return false;
1825 }
1826 if self.state.get().is_sweep() {
1827 header.color.set(self.current_white());
1828 }
1829 self.link_to_head(&self.finobj, ptr);
1830 true
1831 }
1832
1833 pub fn move_finobj_to_tobefnz(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1834 if !self.unlink_from_list(&self.finobj, ptr) {
1835 return false;
1836 }
1837 self.link_to_tail(&self.tobefnz, ptr);
1838 true
1839 }
1840
1841 pub fn move_tobefnz_to_allgc(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1842 let header = self.header_from_ptr(ptr);
1843 if !self.unlink_from_list(&self.tobefnz, ptr) {
1844 return false;
1845 }
1846 if self.state.get().is_sweep() {
1847 header.color.set(self.current_white());
1848 }
1849 self.link_to_head(&self.head, ptr);
1850 if header.age.get() == GcAge::Old1 {
1851 self.firstold1.set(Some(ptr));
1852 }
1853 true
1854 }
1855
1856 fn remember_minor_revisit(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1857 let header = self.header_from_ptr(ptr);
1858 if header.gray_listed() {
1859 return;
1860 }
1861 header.gray_next.set(self.grayagain.get());
1862 header.set_gray_listed(true);
1863 self.grayagain.set(Some(ptr));
1864 }
1865
1866 fn mark_minor_revisit_objects(&self, marker: &mut Marker) {
1867 let mut cursor = self.grayagain.get();
1868 while let Some(ptr) = cursor {
1869 let header = self.header_from_ptr(ptr);
1870 cursor = header.gray_next.get();
1871 let id = ptr.as_ptr() as *const () as usize;
1872 marker.mark_box(ptr, header, id);
1873 }
1874 }
1875
1876 fn clear_grayagain(&self) {
1877 let mut cursor = self.grayagain.get();
1878 self.grayagain.set(None);
1879 while let Some(ptr) = cursor {
1880 let header = self.header_from_ptr(ptr);
1881 cursor = header.gray_next.get();
1882 header.gray_next.set(None);
1883 header.set_gray_listed(false);
1884 }
1885 }
1886
1887 fn take_grayagain(&self) -> Vec<NonNull<GcBox<dyn Trace>>> {
1888 let mut objects = Vec::new();
1889 let mut cursor = self.grayagain.get();
1890 self.grayagain.set(None);
1891 while let Some(ptr) = cursor {
1892 let header = self.header_from_ptr(ptr);
1893 cursor = header.gray_next.get();
1894 header.gray_next.set(None);
1895 header.set_gray_listed(false);
1896 objects.push(ptr);
1897 }
1898 objects
1899 }
1900
1901 fn replace_grayagain(&self, objects: Vec<NonNull<GcBox<dyn Trace>>>) {
1902 self.clear_grayagain();
1903 for ptr in objects.into_iter().rev() {
1904 self.remember_minor_revisit(ptr);
1905 }
1906 }
1907
1908 fn unlink_grayagain(&self, removed: NonNull<GcBox<dyn Trace>>) {
1909 let keep = self
1910 .take_grayagain()
1911 .into_iter()
1912 .filter(|ptr| !std::ptr::addr_eq(ptr.as_ptr(), removed.as_ptr()))
1913 .collect();
1914 self.replace_grayagain(keep);
1915 }
1916
1917 pub fn grayagain_count(&self) -> usize {
1918 let mut count = 0usize;
1919 let mut cursor = self.grayagain.get();
1920 while let Some(ptr) = cursor {
1921 count += 1;
1922 cursor = self.header_from_ptr(ptr).gray_next.get();
1923 }
1924 count
1925 }
1926
1927 pub fn register_v51_udata(&self, probe: std::rc::Rc<dyn Udata51Probe>) {
1934 self.v51_udata_roster.borrow_mut().push(probe);
1935 }
1936
1937 pub fn scan_v51_finalizable(&self) -> Vec<std::rc::Rc<dyn Udata51Probe>> {
1947 let mut roster = self.v51_udata_roster.borrow_mut();
1948 roster.retain(|probe| probe.is_alive());
1949 roster.clone()
1950 }
1951
1952 pub fn allocation_token(&self, identity: usize) -> Option<usize> {
1960 self.allocation_tokens.borrow().get(&identity).copied()
1961 }
1962
1963 pub fn register_allocation_token(&self, identity: usize) -> usize {
1981 let mut tokens = self.allocation_tokens.borrow_mut();
1982 if let Some(token) = tokens.get(&identity) {
1983 return *token;
1984 }
1985 let token = self.next_token();
1986 tokens.insert(identity, token);
1987 token
1988 }
1989
1990 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
1995 self.allocation_token(identity) == Some(token)
1996 }
1997
1998 pub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2008 where
2009 P: Trace + 'static,
2010 C: Trace + 'static,
2011 {
2012 if self.paused.get() || self.state.get().is_pause() {
2013 return;
2014 }
2015 if parent.header().color.get() != Color::Black {
2016 return;
2017 }
2018 if !child.header().color.get().is_white() {
2019 return;
2020 }
2021 child.header().color.set(Color::Gray);
2022 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
2023 if let Some(m) = m_opt.as_mut() {
2024 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
2025 m.gray_queue.push(ptr);
2026 m.visited.insert(child.identity());
2027 }
2028 }
2029 }
2030
2031 pub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2034 where
2035 P: Trace + 'static,
2036 C: Trace + 'static,
2037 {
2038 if self.paused.get() || self.state.get().is_pause() {
2039 return;
2040 }
2041 if parent.header().color.get() != Color::Black {
2042 return;
2043 }
2044 if !child.header().color.get().is_white() {
2045 return;
2046 }
2047 parent.header().color.set(Color::Gray);
2048 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
2049 if let Some(m) = m_opt.as_mut() {
2050 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
2051 m.gray_queue.push(ptr);
2052 m.visited.insert(parent.identity());
2053 }
2054 }
2055 }
2056
2057 pub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2062 where
2063 P: Trace + 'static,
2064 C: Trace + 'static,
2065 {
2066 if parent.age().is_old() && !child.age().is_old() {
2067 child.set_age(GcAge::Old0);
2068 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
2069 self.remember_minor_revisit(ptr);
2070 }
2071 self.barrier(parent, child);
2072 }
2073
2074 pub fn generational_backward_barrier<P>(&self, parent: Gc<P>)
2078 where
2079 P: Trace + 'static,
2080 {
2081 if parent.age().is_old() {
2082 parent.set_color(Color::Gray);
2083 parent.set_age(GcAge::Touched1);
2084 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
2085 self.remember_minor_revisit(ptr);
2086 }
2087 }
2088
2089 pub fn step(&self, roots: &dyn Trace) {
2093 self.step_with_post_mark(roots, |_: &mut Marker| {});
2094 }
2095
2096 pub fn step_with_post_mark<F: FnMut(&mut Marker)>(&self, roots: &dyn Trace, post_mark: F) {
2101 if self.paused.get() {
2102 return;
2103 }
2104 if !self.stress && self.bytes.get() < self.threshold.get() {
2105 return;
2106 }
2107 self.full_collect_with_post_mark(roots, post_mark);
2108 }
2109
2110 pub fn full_collect(&self, roots: &dyn Trace) {
2113 self.full_collect_with_post_mark(roots, |_: &mut Marker| {});
2114 }
2115
2116 pub fn mark_only_with_post_mark<F: FnMut(&mut Marker)>(
2121 &self,
2122 roots: &dyn Trace,
2123 mut post_mark: F,
2124 ) {
2125 if self.paused.get() {
2126 return;
2127 }
2128 let mut marker = self.marker_from_pool(MarkerMode::Full);
2129 roots.trace(&mut marker);
2130 marker.drain_gray_queue();
2131 post_mark(&mut marker);
2132 marker.drain_gray_queue();
2133 self.last_mark_stats.set(marker.stats());
2134 self.recycle_marker(marker);
2135 }
2136
2137 pub fn promote_all_to_old(&self) {
2140 self.for_each_header(|header| {
2141 header.age.set(GcAge::Old);
2142 header.color.set(Color::Black);
2143 });
2144 self.set_all_cursors_to_head();
2145 }
2146
2147 pub fn reset_all_ages(&self) {
2150 let current_white = self.current_white();
2151 self.for_each_header(|header| {
2152 header.age.set(GcAge::New);
2153 header.color.set(current_white);
2154 });
2155 self.clear_generation_cursors();
2156 }
2157
2158 pub fn minor_collect_with_post_mark<F: FnMut(&mut Marker)>(
2165 &self,
2166 roots: &dyn Trace,
2167 mut post_mark: F,
2168 ) {
2169 if self.paused.get() {
2170 return;
2171 }
2172 if !self.state.get().is_pause() {
2173 self.abort_cycle();
2174 }
2175
2176 self.state.set(GcState::Propagate);
2177 let mut marker = self.marker_from_pool(MarkerMode::Minor);
2178 self.last_sweep_stats.set(SweepStats::default());
2179 self.mark_minor_revisit_objects(&mut marker);
2180 roots.trace(&mut marker);
2181 marker.drain_gray_queue();
2182
2183 self.state.set(GcState::EnterAtomic);
2184 self.state.set(GcState::Atomic);
2185 post_mark(&mut marker);
2186 marker.drain_gray_queue();
2187 self.last_mark_stats.set(marker.stats());
2188 self.recycle_marker(marker);
2189
2190 self.state.set(GcState::SweepAllGc);
2191 self.sweep_young();
2192 self.recycle_marker_cell();
2193 self.sweep_prev_next.set(None);
2194 self.state.set(GcState::Pause);
2195 self.collections.set(self.collections.get() + 1);
2196 self.minor_collections.set(self.minor_collections.get() + 1);
2197 }
2198
2199 pub fn full_collect_with_post_mark<F: FnMut(&mut Marker)>(
2206 &self,
2207 roots: &dyn Trace,
2208 mut post_mark: F,
2209 ) {
2210 if self.paused.get() {
2211 return;
2212 }
2213 if !self.state.get().is_pause() {
2214 self.abort_cycle();
2215 }
2216 self.full_collections.set(self.full_collections.get() + 1);
2217 let unlimited = StepBudget {
2218 remaining_work: isize::MAX,
2219 max_credit: isize::MAX,
2220 };
2221 loop {
2222 let outcome = self.incremental_step_with_post_mark(roots, unlimited, &mut post_mark);
2223 if matches!(outcome, StepOutcome::Paused | StepOutcome::SkippedStopped) {
2224 break;
2225 }
2226 }
2227 }
2228
2229 pub fn incremental_step_with_post_mark<F: FnMut(&mut Marker)>(
2244 &self,
2245 roots: &dyn Trace,
2246 mut budget: StepBudget,
2247 mut post_mark: F,
2248 ) -> StepOutcome {
2249 if self.paused.get() {
2250 return StepOutcome::SkippedStopped;
2251 }
2252 self.run_budgeted(roots, &mut budget, &mut post_mark);
2253 if self.state.get().is_pause() {
2254 StepOutcome::Paused
2255 } else {
2256 StepOutcome::InProgress
2257 }
2258 }
2259
2260 fn run_budgeted(
2261 &self,
2262 roots: &dyn Trace,
2263 budget: &mut StepBudget,
2264 post_mark: &mut dyn FnMut(&mut Marker),
2265 ) -> bool {
2266 self.run_budgeted_until(roots, budget, post_mark, None)
2267 }
2268
2269 fn run_budgeted_until(
2270 &self,
2271 roots: &dyn Trace,
2272 budget: &mut StepBudget,
2273 post_mark: &mut dyn FnMut(&mut Marker),
2274 stop_at: Option<GcState>,
2275 ) -> bool {
2276 let mut did_work = false;
2277 loop {
2278 if stop_at == Some(self.state.get()) {
2279 return did_work;
2280 }
2281 if budget.remaining_work <= -budget.max_credit {
2282 return did_work;
2283 }
2284 match self.state.get() {
2285 GcState::Pause => {
2286 self.start_cycle(roots);
2287 self.state.set(GcState::Propagate);
2288 budget.remaining_work -= 1;
2289 did_work = true;
2290 if stop_at == Some(GcState::Propagate) {
2291 return did_work;
2292 }
2293 }
2294 GcState::Propagate => {
2295 let work = self.drain_gray_budgeted(budget.remaining_work.max(1));
2296 budget.remaining_work -= work as isize;
2297 did_work = did_work || work > 0;
2298 let empty = {
2299 let m = self.marker.borrow();
2300 m.as_ref().map(|m| m.gray_queue.is_empty()).unwrap_or(true)
2301 };
2302 if empty {
2303 self.state.set(GcState::EnterAtomic);
2304 if stop_at == Some(GcState::EnterAtomic) {
2305 return did_work;
2306 }
2307 } else if budget.remaining_work <= 0 {
2308 return did_work;
2309 }
2310 }
2311 GcState::EnterAtomic => {
2312 self.state.set(GcState::Atomic);
2313 budget.remaining_work -= 1;
2314 did_work = true;
2315 if stop_at == Some(GcState::Atomic) || budget.remaining_work <= 0 {
2316 return did_work;
2317 }
2318 }
2319 GcState::Atomic => {
2320 self.run_atomic(post_mark);
2321 self.state.set(GcState::SweepAllGc);
2322 budget.remaining_work -= 1;
2323 did_work = true;
2324 if stop_at == Some(GcState::SweepAllGc) {
2325 return did_work;
2326 }
2327 }
2328 GcState::SweepAllGc => {
2329 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2330 budget.remaining_work -= work as isize;
2331 did_work = did_work || work > 0;
2332 if self.sweep_prev_next.get().is_none() {
2333 self.state.set(GcState::SweepFinObj);
2334 self.sweep_prev_next.set(Some(NonNull::from(&self.finobj)));
2335 if stop_at == Some(GcState::SweepFinObj) {
2336 return did_work;
2337 }
2338 } else if budget.remaining_work <= 0 {
2339 return did_work;
2340 }
2341 }
2342 GcState::SweepFinObj => {
2343 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2344 budget.remaining_work -= work as isize;
2345 did_work = did_work || work > 0;
2346 if self.sweep_prev_next.get().is_none() {
2347 self.state.set(GcState::SweepToBeFnz);
2348 self.sweep_prev_next.set(Some(NonNull::from(&self.tobefnz)));
2349 if stop_at == Some(GcState::SweepToBeFnz) {
2350 return did_work;
2351 }
2352 } else if budget.remaining_work <= 0 {
2353 return did_work;
2354 }
2355 }
2356 GcState::SweepToBeFnz => {
2357 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2358 budget.remaining_work -= work as isize;
2359 did_work = did_work || work > 0;
2360 if self.sweep_prev_next.get().is_none() {
2361 self.state.set(GcState::SweepEnd);
2362 if stop_at == Some(GcState::SweepEnd) {
2363 return did_work;
2364 }
2365 } else if budget.remaining_work <= 0 {
2366 return did_work;
2367 }
2368 }
2369 GcState::SweepEnd => {
2370 self.state.set(GcState::CallFin);
2371 budget.remaining_work -= 1;
2372 did_work = true;
2373 if stop_at == Some(GcState::CallFin) || budget.remaining_work <= 0 {
2374 return did_work;
2375 }
2376 }
2377 GcState::CallFin => {
2378 self.finish_cycle();
2379 self.state.set(GcState::Pause);
2380 if stop_at == Some(GcState::Pause) {
2381 return did_work;
2382 }
2383 return did_work;
2384 }
2385 }
2386 }
2387 }
2388
2389 pub fn incremental_run_until_state_with_post_mark<F: FnMut(&mut Marker)>(
2394 &self,
2395 roots: &dyn Trace,
2396 target: GcState,
2397 max_work: isize,
2398 mut post_mark: F,
2399 ) -> StepOutcome {
2400 if self.paused.get() {
2401 return StepOutcome::SkippedStopped;
2402 }
2403 let work = max_work.max(1);
2404 let mut budget = StepBudget {
2405 remaining_work: work,
2406 max_credit: work,
2407 };
2408 self.run_budgeted_until(roots, &mut budget, &mut post_mark, Some(target));
2409 if self.state.get().is_pause() {
2410 StepOutcome::Paused
2411 } else {
2412 StepOutcome::InProgress
2413 }
2414 }
2415
2416 fn marker_from_pool(&self, mode: MarkerMode) -> Marker {
2419 match self.marker_pool.borrow_mut().take() {
2420 Some((gray_queue, visited)) => Marker {
2421 gray_queue,
2422 visited,
2423 stats: MarkerStats::default(),
2424 mode,
2425 },
2426 None => Marker::new_with_capacity(mode, self.objects.get()),
2427 }
2428 }
2429
2430 fn recycle_marker(&self, marker: Marker) {
2431 let Marker {
2432 mut gray_queue,
2433 mut visited,
2434 ..
2435 } = marker;
2436 gray_queue.clear();
2437 visited.clear();
2438 *self.marker_pool.borrow_mut() = Some((gray_queue, visited));
2439 }
2440
2441 fn recycle_marker_cell(&self) {
2442 if let Some(marker) = self.marker.borrow_mut().take() {
2443 self.recycle_marker(marker);
2444 }
2445 }
2446
2447 fn start_cycle(&self, roots: &dyn Trace) {
2448 self.flip_current_white();
2449 let dead_white = self.other_white();
2450 self.for_each_header(|header| {
2451 header.color.set(dead_white);
2452 });
2453 let mut marker = self.marker_from_pool(MarkerMode::Full);
2454 roots.trace(&mut marker);
2455 *self.marker.borrow_mut() = Some(marker);
2456 self.sweep_prev_next.set(None);
2457 }
2458
2459 fn drain_gray_budgeted(&self, max_units: isize) -> usize {
2460 let mut m_opt = self.marker.borrow_mut();
2461 let marker = match m_opt.as_mut() {
2462 Some(m) => m,
2463 None => return 0,
2464 };
2465 let mut work = 0usize;
2466 let mut budget = max_units;
2467 while budget > 0 {
2468 let next = match marker.gray_queue.pop() {
2469 Some(p) => p,
2470 None => break,
2471 };
2472 unsafe {
2473 let bx = next.as_ref();
2474 marker.stats.traced += 1;
2475 if bx.header.age.get().is_old() {
2476 marker.stats.traced_old += 1;
2477 } else {
2478 marker.stats.traced_young += 1;
2479 }
2480 bx.header.color.set(Color::Black);
2481 bx.value.trace(marker);
2482 }
2483 work += 1;
2484 budget -= 1;
2485 }
2486 work
2487 }
2488
2489 fn run_atomic(&self, post_mark: &mut dyn FnMut(&mut Marker)) {
2490 let mut m_opt = self.marker.borrow_mut();
2491 if let Some(marker) = m_opt.as_mut() {
2492 marker.drain_gray_queue();
2493 post_mark(marker);
2494 marker.drain_gray_queue();
2495 }
2496 self.sweep_prev_next.set(Some(NonNull::from(&self.head)));
2497 self.last_sweep_stats.set(SweepStats::default());
2498 }
2499
2500 fn sweep_budgeted(&self, max_units: isize) -> usize {
2501 let mut work = 0usize;
2502 let mut budget = max_units;
2503 let mut freed_bytes = 0usize;
2504 let mut stats = SweepStats::default();
2505 let current_white = self.current_white();
2506 let dead_white = self.other_white();
2507 let mut prev_next_ptr = match self.sweep_prev_next.get() {
2508 Some(p) => p,
2509 None => return 0,
2510 };
2511 while budget > 0 {
2512 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2513 let cursor = prev_cell.get();
2514 let ptr = match cursor {
2515 Some(p) => p,
2516 None => {
2517 self.sweep_prev_next.set(None);
2518 break;
2519 }
2520 };
2521 let header = self.header_from_ptr(ptr);
2522 let next = header.next.get();
2523 let age = header.age.get();
2524 stats.record_visit(age);
2525 let color = header.color.get();
2526 if color == dead_white {
2527 prev_cell.set(next);
2528 let size = header.size();
2529 freed_bytes += size;
2530 stats.record_free(size);
2531 self.correct_generation_pointers(ptr, next);
2532 self.allocation_tokens
2533 .borrow_mut()
2534 .remove(&(ptr.as_ptr() as *const () as usize));
2535 self.objects.set(self.objects.get().saturating_sub(1));
2536 self.release_box(ptr);
2537 } else {
2538 if matches!(color, Color::Black | Color::Gray) {
2539 header.color.set(current_white);
2540 }
2541 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2542 self.sweep_prev_next.set(Some(prev_next_ptr));
2543 }
2544 work += 1;
2545 budget -= 1;
2546 }
2547 if freed_bytes > 0 {
2548 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2549 }
2550 if stats.visited > 0 {
2551 let mut total = self.last_sweep_stats.get();
2552 total.add(stats);
2553 self.last_sweep_stats.set(total);
2554 }
2555 work
2556 }
2557
2558 fn push_next_revisit(
2559 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2560 seen: &mut IdentityHashSet,
2561 ptr: NonNull<GcBox<dyn Trace>>,
2562 age: GcAge,
2563 ) {
2564 if matches!(
2565 age,
2566 GcAge::Old0 | GcAge::Old1 | GcAge::Touched1 | GcAge::Touched2
2567 ) {
2568 let id = ptr.as_ptr() as *const () as usize;
2569 if seen.insert(id) {
2570 next_revisit.push(ptr);
2571 }
2572 }
2573 }
2574
2575 fn sweep_young_range(
2576 &self,
2577 mut prev_next_ptr: NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2578 limit: Option<NonNull<GcBox<dyn Trace>>>,
2579 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2580 next_revisit_seen: &mut IdentityHashSet,
2581 processed: &mut Option<OldRevisitTracker>,
2582 firstold1: &mut Option<NonNull<GcBox<dyn Trace>>>,
2583 freed_bytes: &mut usize,
2584 stats: &mut SweepStats,
2585 ) -> (
2586 NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2587 Option<NonNull<GcBox<dyn Trace>>>,
2588 ) {
2589 let current_white = self.current_white();
2590 loop {
2591 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2592 let Some(ptr) = prev_cell.get() else {
2593 return (prev_next_ptr, None);
2594 };
2595 if Some(ptr) == limit {
2596 return (prev_next_ptr, Some(ptr));
2597 }
2598 let header = self.header_from_ptr(ptr);
2599 let next = header.next.get();
2600 let age = header.age.get();
2601 stats.record_visit(age);
2602 if let Some(processed) = processed.as_mut() {
2603 processed.record_processed(ptr.as_ptr() as *const () as usize);
2604 }
2605 if header.color.get().is_white() && !age.is_old() {
2606 prev_cell.set(next);
2607 let size = header.size();
2608 *freed_bytes += size;
2609 stats.record_free(size);
2610 self.correct_generation_pointers(ptr, next);
2611 self.allocation_tokens
2612 .borrow_mut()
2613 .remove(&(ptr.as_ptr() as *const () as usize));
2614 self.objects.set(self.objects.get().saturating_sub(1));
2615 self.release_box(ptr);
2616 continue;
2617 }
2618
2619 if !header.color.get().is_white() {
2620 let next_age = age.next_after_minor();
2621 header.age.set(next_age);
2622 if next_age == GcAge::Old1 && firstold1.is_none() {
2623 *firstold1 = Some(ptr);
2624 }
2625 match age {
2626 GcAge::New => header.color.set(current_white),
2627 GcAge::Touched1 | GcAge::Touched2 => header.color.set(Color::Black),
2628 _ => {}
2629 }
2630 Self::push_next_revisit(next_revisit, next_revisit_seen, ptr, next_age);
2631 }
2632 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2633 }
2634 }
2635
2636 fn sweep_young(&self) {
2637 let mut freed_bytes = 0usize;
2638 let mut next_revisit = Vec::new();
2639 let mut next_revisit_seen = IdentityHashSet::default();
2640 let mut firstold1 = None;
2641 let mut stats = SweepStats::default();
2642 let old_revisit = self.take_grayagain();
2643 let mut processed = OldRevisitTracker::new(&old_revisit);
2644 let survival = self.survival.get();
2645 let old1 = self.old1.get();
2646
2647 let (psurvival, new_old1) = self.sweep_young_range(
2648 NonNull::from(&self.head),
2649 survival,
2650 &mut next_revisit,
2651 &mut next_revisit_seen,
2652 &mut processed,
2653 &mut firstold1,
2654 &mut freed_bytes,
2655 &mut stats,
2656 );
2657 self.sweep_young_range(
2658 psurvival,
2659 old1,
2660 &mut next_revisit,
2661 &mut next_revisit_seen,
2662 &mut processed,
2663 &mut firstold1,
2664 &mut freed_bytes,
2665 &mut stats,
2666 );
2667
2668 let finobjsur = self.finobjsur.get();
2669 let finobjold1 = self.finobjold1.get();
2670 let mut dummy_firstold1 = None;
2671 let (pfinobjsur, new_finobjold1) = self.sweep_young_range(
2672 NonNull::from(&self.finobj),
2673 finobjsur,
2674 &mut next_revisit,
2675 &mut next_revisit_seen,
2676 &mut processed,
2677 &mut dummy_firstold1,
2678 &mut freed_bytes,
2679 &mut stats,
2680 );
2681 self.sweep_young_range(
2682 pfinobjsur,
2683 finobjold1,
2684 &mut next_revisit,
2685 &mut next_revisit_seen,
2686 &mut processed,
2687 &mut dummy_firstold1,
2688 &mut freed_bytes,
2689 &mut stats,
2690 );
2691 self.sweep_young_range(
2692 NonNull::from(&self.tobefnz),
2693 None,
2694 &mut next_revisit,
2695 &mut next_revisit_seen,
2696 &mut processed,
2697 &mut dummy_firstold1,
2698 &mut freed_bytes,
2699 &mut stats,
2700 );
2701
2702 if let Some(processed) = processed.as_mut() {
2703 processed.finish();
2704 }
2705
2706 for ptr in old_revisit {
2707 let id = ptr.as_ptr() as *const () as usize;
2708 if processed
2709 .as_ref()
2710 .is_some_and(|processed| processed.was_processed(id))
2711 {
2712 continue;
2713 }
2714 stats.revisit += 1;
2715 let header = self.header_from_ptr(ptr);
2716 if header.color.get().is_white() {
2717 continue;
2718 }
2719 let age = header.age.get();
2720 let next_age = age.next_after_minor();
2721 header.age.set(next_age);
2722 if next_age == GcAge::Old1 && firstold1.is_none() {
2723 firstold1 = Some(ptr);
2724 }
2725 if matches!(age, GcAge::Touched1 | GcAge::Touched2) {
2726 header.color.set(Color::Black);
2727 }
2728 Self::push_next_revisit(&mut next_revisit, &mut next_revisit_seen, ptr, next_age);
2729 }
2730
2731 if freed_bytes > 0 {
2732 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2733 }
2734 self.replace_grayagain(next_revisit);
2735 self.reallyold.set(old1);
2736 self.old1.set(new_old1);
2737 self.survival.set(self.head.get());
2738 self.firstold1.set(firstold1);
2739 self.finobjrold.set(finobjold1);
2740 self.finobjold1.set(new_finobjold1);
2741 self.finobjsur.set(self.finobj.get());
2742 self.last_sweep_stats.set(stats);
2743 }
2744
2745 fn finish_cycle(&self) {
2746 let stats = self
2747 .marker
2748 .borrow()
2749 .as_ref()
2750 .map(|marker| marker.stats())
2751 .unwrap_or_default();
2752 self.last_mark_stats.set(stats);
2753 self.recycle_marker_cell();
2754 self.sweep_prev_next.set(None);
2755 let next = self.bytes.get().saturating_mul(self.pause_multiplier.get()) / 100;
2756 self.threshold.set(next.max(GC_MIN_THRESHOLD));
2757 self.collections.set(self.collections.get() + 1);
2758 }
2759
2760 pub fn finish_callfin_phase(&self) -> bool {
2763 if self.state.get() != GcState::CallFin {
2764 return false;
2765 }
2766 self.finish_cycle();
2767 self.state.set(GcState::Pause);
2768 true
2769 }
2770
2771 fn abort_cycle(&self) {
2772 if !self.state.get().is_pause() {
2773 self.recycle_marker_cell();
2774 self.sweep_prev_next.set(None);
2775 let current_white = self.current_white();
2776 self.for_each_header(|header| {
2777 header.color.set(current_white);
2778 });
2779 self.state.set(GcState::Pause);
2780 }
2781 }
2782
2783 pub fn gc_state(&self) -> GcState {
2785 self.state.get()
2786 }
2787
2788 pub fn allgc_count(&self) -> usize {
2790 self.objects.get()
2791 }
2792
2793 pub fn type_name_count(&self, mut predicate: impl FnMut(&'static str) -> bool) -> usize {
2797 let mut count = 0usize;
2798 for head in [self.head.get(), self.finobj.get(), self.tobefnz.get()] {
2799 let mut cursor = head;
2800 while let Some(ptr) = cursor {
2801 let bx = unsafe { ptr.as_ref() };
2802 cursor = bx.header.next.get();
2803 if predicate(bx.value().type_name()) {
2804 count += 1;
2805 }
2806 }
2807 }
2808 count
2809 }
2810
2811 pub fn drop_all(&self) {
2815 self.recycle_marker_cell();
2816 self.sweep_prev_next.set(None);
2817 self.clear_generation_cursors();
2818 self.state.set(GcState::Pause);
2819 self.allocation_tokens.borrow_mut().clear();
2820 self.drop_list(&self.head);
2821 self.drop_list(&self.finobj);
2822 self.drop_list(&self.tobefnz);
2823 self.drop_list(&self.quarantined);
2824 self.bytes.set(0);
2825 self.objects.set(0);
2826 }
2827
2828 fn drop_list(&self, list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>) {
2829 let mut cursor = list.get();
2830 list.set(None);
2831 while let Some(ptr) = cursor {
2832 let next = unsafe {
2834 let next = (*ptr.as_ptr()).header.next.get();
2835 let _ = Box::from_raw(ptr.as_ptr());
2836 next
2837 };
2838 cursor = next;
2839 }
2840 }
2841}
2842
2843impl Drop for Heap {
2844 fn drop(&mut self) {
2845 self.drop_all();
2846 }
2847}
2848
2849#[cfg(test)]
2854mod tests {
2855 use super::*;
2856
2857 struct Cell0 {
2859 next: Cell<Option<Gc<Cell0>>>,
2860 marker_calls: Cell<usize>,
2861 }
2862
2863 impl Trace for Cell0 {
2864 fn trace(&self, m: &mut Marker) {
2865 self.marker_calls.set(self.marker_calls.get() + 1);
2866 if let Some(n) = self.next.get() {
2867 m.mark(n);
2868 }
2869 }
2870 }
2871
2872 struct OneRoot(Option<Gc<Cell0>>);
2874 impl Trace for OneRoot {
2875 fn trace(&self, m: &mut Marker) {
2876 if let Some(g) = self.0 {
2877 m.mark(g);
2878 }
2879 }
2880 }
2881
2882 struct TwoRoots {
2883 first: Option<Gc<Cell0>>,
2884 second: Option<Gc<Cell0>>,
2885 }
2886
2887 impl Trace for TwoRoots {
2888 fn trace(&self, m: &mut Marker) {
2889 if let Some(g) = self.first {
2890 m.mark(g);
2891 }
2892 if let Some(g) = self.second {
2893 m.mark(g);
2894 }
2895 }
2896 }
2897
2898 #[derive(Clone)]
2899 struct FinalizerCell {
2900 id: usize,
2901 age: GcAge,
2902 finalized: std::rc::Rc<Cell<bool>>,
2903 }
2904
2905 impl FinalizerCell {
2906 fn new(id: usize) -> Self {
2907 Self {
2908 id,
2909 age: GcAge::New,
2910 finalized: std::rc::Rc::new(Cell::new(false)),
2911 }
2912 }
2913 }
2914
2915 impl FinalizerEntry for FinalizerCell {
2916 fn identity(&self) -> usize {
2917 self.id
2918 }
2919
2920 fn age(&self) -> GcAge {
2921 self.age
2922 }
2923
2924 fn is_finalized(&self) -> bool {
2925 self.finalized.get()
2926 }
2927
2928 fn set_finalized(&self, finalized: bool) {
2929 self.finalized.set(finalized);
2930 }
2931 }
2932
2933 fn finalizer_ids(objects: &[FinalizerCell]) -> Vec<usize> {
2934 objects.iter().map(|object| object.id).collect()
2935 }
2936
2937 #[derive(Clone)]
2938 struct WeakCell {
2939 id: usize,
2940 live: bool,
2941 kind: WeakListKind,
2942 }
2943
2944 impl WeakEntry for WeakCell {
2945 type Strong = usize;
2946
2947 fn identity(&self) -> usize {
2948 self.id
2949 }
2950
2951 fn list_kind(&self) -> WeakListKind {
2952 self.kind
2953 }
2954
2955 fn upgrade(&self) -> Option<Self::Strong> {
2956 self.live.then_some(self.id)
2957 }
2958 }
2959
2960 #[test]
2961 fn weak_registry_dedups_snapshots_and_retains_live_ids() {
2962 let mut registry = WeakRegistry::default();
2963 registry.push_unique(WeakCell {
2964 id: 1,
2965 live: true,
2966 kind: WeakListKind::WeakValues,
2967 });
2968 registry.push_unique(WeakCell {
2969 id: 1,
2970 live: true,
2971 kind: WeakListKind::Ephemeron,
2972 });
2973 registry.push_unique(WeakCell {
2974 id: 2,
2975 live: false,
2976 kind: WeakListKind::AllWeak,
2977 });
2978 registry.push_unique(WeakCell {
2979 id: 3,
2980 live: true,
2981 kind: WeakListKind::WeakValues,
2982 });
2983
2984 let stats = registry.stats();
2985 assert_eq!(stats.weak_values, 1);
2986 assert_eq!(stats.ephemeron, 1);
2987 assert_eq!(stats.all_weak, 1);
2988
2989 let snapshot = registry.live_snapshot();
2990 assert_eq!(snapshot, vec![3, 1]);
2991 assert_eq!(
2992 registry.stats(),
2993 WeakRegistryStats {
2994 tracked: 3,
2995 snapshot_live: 2,
2996 snapshot_dead: 1,
2997 retained: 2,
2998 weak_values: 1,
2999 ephemeron: 1,
3000 all_weak: 0,
3001 }
3002 );
3003
3004 let keep: std::collections::HashSet<usize> = [3usize].into_iter().collect();
3005 registry.retain_identities(&keep);
3006 assert_eq!(registry.len(), 1);
3007 assert_eq!(registry.stats().retained, 1);
3008 assert_eq!(registry.stats().weak_values, 1);
3009 registry.remove_identity(3);
3010 assert_eq!(registry.len(), 0);
3011 }
3012
3013 #[test]
3014 fn finalizer_registry_tracks_generational_cohorts() {
3015 let mut registry = FinalizerRegistry::default();
3016 registry.push_pending_unique(FinalizerCell::new(1));
3017 registry.push_pending_unique(FinalizerCell::new(2));
3018
3019 let stats = registry.stats();
3020 assert_eq!(stats.finobj_new, 2);
3021 assert_eq!(stats.finobj_survival, 0);
3022 assert_eq!(stats.finobj_old1, 0);
3023 assert_eq!(stats.finobj_reallyold, 0);
3024 assert_eq!(stats.finobj_minor_scan, 2);
3025
3026 registry.finish_minor_collection();
3027 let stats = registry.stats();
3028 assert_eq!(stats.finobj_new, 0);
3029 assert_eq!(stats.finobj_survival, 2);
3030 assert_eq!(stats.finobj_old1, 0);
3031 assert_eq!(stats.finobj_reallyold, 0);
3032 assert_eq!(stats.finobj_minor_scan, 2);
3033
3034 registry.push_pending_unique(FinalizerCell::new(3));
3035 registry.finish_minor_collection();
3036 let stats = registry.stats();
3037 assert_eq!(stats.finobj_new, 0);
3038 assert_eq!(stats.finobj_survival, 1);
3039 assert_eq!(stats.finobj_old1, 2);
3040 assert_eq!(stats.finobj_reallyold, 0);
3041 assert_eq!(stats.finobj_minor_scan, 1);
3042
3043 registry.finish_minor_collection();
3044 let stats = registry.stats();
3045 assert_eq!(stats.finobj_new, 0);
3046 assert_eq!(stats.finobj_survival, 0);
3047 assert_eq!(stats.finobj_old1, 1);
3048 assert_eq!(stats.finobj_reallyold, 2);
3049 assert_eq!(stats.finobj_minor_scan, 0);
3050 }
3051
3052 #[test]
3053 fn finalizer_registry_minor_snapshot_uses_cohort_boundaries() {
3054 let mut registry = FinalizerRegistry::default();
3055 registry.push_pending_unique(FinalizerCell::new(1));
3056 registry.push_pending_unique(FinalizerCell::new(2));
3057 registry.push_pending_unique(FinalizerCell::new(3));
3058 registry.finish_minor_collection();
3059 registry.finish_minor_collection();
3060 registry.push_pending_unique(FinalizerCell::new(4));
3061 registry.push_pending_unique(FinalizerCell::new(5));
3062
3063 assert_eq!(
3064 finalizer_ids(®istry.pending_minor_snapshot()),
3065 vec![4, 5],
3066 "minor finalizer scan must skip the old1/reallyold prefix"
3067 );
3068
3069 registry.push_to_be_finalized(FinalizerCell::new(99));
3070 registry.promote_pending_to_finalized(vec![
3071 FinalizerCell::new(1),
3072 FinalizerCell::new(2),
3073 FinalizerCell::new(4),
3074 ]);
3075
3076 let stats = registry.stats();
3077 assert_eq!(stats.finobj_old1, 1);
3078 assert_eq!(stats.finobj_new, 1);
3079 assert_eq!(stats.finobj_minor_scan, 1);
3080 assert_eq!(finalizer_ids(registry.pending()), vec![3, 5]);
3081 assert_eq!(
3082 finalizer_ids(registry.to_be_finalized()),
3083 vec![99, 4, 2, 1],
3084 "new to-be-finalized batches append behind older queued finalizers"
3085 );
3086 }
3087
3088 #[test]
3089 fn finalizer_registry_marks_and_clears_finalized_bit() {
3090 let mut registry = FinalizerRegistry::default();
3091 let object = FinalizerCell::new(1);
3092
3093 assert!(!object.is_finalized());
3094 registry.push_pending_unique(object.clone());
3095 assert!(object.is_finalized());
3096
3097 registry.push_pending_unique(object.clone());
3098 assert_eq!(registry.pending_len(), 1);
3099
3100 registry.promote_pending_to_finalized(vec![object.clone()]);
3101 assert!(object.is_finalized());
3102 assert_eq!(registry.pending_len(), 0);
3103 assert_eq!(registry.to_be_finalized_len(), 1);
3104
3105 let popped = registry.pop_to_be_finalized().unwrap();
3106 assert_eq!(popped.id, 1);
3107 assert!(!object.is_finalized());
3108
3109 registry.push_pending_unique(object.clone());
3110 assert!(object.is_finalized());
3111 assert_eq!(registry.pending_len(), 1);
3112 }
3113
3114 #[test]
3115 fn alloc_and_drop_all() {
3116 let heap = Heap::new();
3117 heap.unpause();
3118 let _a = heap.allocate(Cell0 {
3119 next: Cell::new(None),
3120 marker_calls: Cell::new(0),
3121 });
3122 let _b = heap.allocate(Cell0 {
3123 next: Cell::new(None),
3124 marker_calls: Cell::new(0),
3125 });
3126 assert!(heap.bytes_used() > 0);
3127 heap.drop_all();
3128 assert_eq!(heap.bytes_used(), 0);
3129 }
3130
3131 fn list_len(heap: &Heap, mut cursor: Option<NonNull<GcBox<dyn Trace>>>) -> usize {
3132 let mut count = 0usize;
3133 while let Some(ptr) = cursor {
3134 count += 1;
3135 cursor = heap.header_from_ptr(ptr).next.get();
3136 }
3137 count
3138 }
3139
3140 #[test]
3141 fn finalizer_intrusive_lists_sweep_and_drop() {
3142 let heap = Heap::new();
3143 heap.unpause();
3144 let _normal = heap.allocate(Cell0 {
3145 next: Cell::new(None),
3146 marker_calls: Cell::new(0),
3147 });
3148 let finobj = heap.allocate(Cell0 {
3149 next: Cell::new(None),
3150 marker_calls: Cell::new(0),
3151 });
3152 let tobefnz = heap.allocate(Cell0 {
3153 next: Cell::new(None),
3154 marker_calls: Cell::new(0),
3155 });
3156
3157 assert!(heap.move_allgc_to_finobj(finobj.as_trace_ptr()));
3158 assert!(heap.move_allgc_to_finobj(tobefnz.as_trace_ptr()));
3159 assert!(heap.move_finobj_to_tobefnz(tobefnz.as_trace_ptr()));
3160 assert_eq!(list_len(&heap, heap.head.get()), 1);
3161 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3162 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3163 assert_eq!(heap.allgc_count(), 3);
3164
3165 heap.full_collect(&TwoRoots {
3166 first: Some(finobj),
3167 second: Some(tobefnz),
3168 });
3169 assert_eq!(list_len(&heap, heap.head.get()), 0);
3170 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3171 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3172 assert_eq!(heap.allgc_count(), 2);
3173
3174 assert!(heap.move_tobefnz_to_allgc(tobefnz.as_trace_ptr()));
3175 heap.full_collect(&OneRoot(Some(tobefnz)));
3176 assert_eq!(list_len(&heap, heap.head.get()), 1);
3177 assert_eq!(list_len(&heap, heap.finobj.get()), 0);
3178 assert_eq!(list_len(&heap, heap.tobefnz.get()), 0);
3179 assert_eq!(heap.allgc_count(), 1);
3180
3181 heap.drop_all();
3182 assert_eq!(heap.bytes_used(), 0);
3183 }
3184
3185 #[test]
3186 fn account_buffer_refunded_on_sweep() {
3187 let heap = Heap::new();
3188 heap.unpause();
3189 let baseline = heap.bytes_used();
3190 {
3191 let a = heap.allocate(Cell0 {
3192 next: Cell::new(None),
3193 marker_calls: Cell::new(0),
3194 });
3195 assert!(heap.bytes_used() > baseline);
3196 a.account_buffer(&heap, 4096);
3197 assert_eq!(
3198 a.header().size(),
3199 std::mem::size_of::<GcBox<Cell0>>() + 4096
3200 );
3201 }
3202 heap.full_collect(&OneRoot(None));
3205 assert_eq!(
3206 heap.bytes_used(),
3207 baseline,
3208 "account_buffer charge must be fully refunded by sweep"
3209 );
3210 }
3211
3212 #[test]
3213 fn account_buffer_noop_on_uncollected() {
3214 let heap = Heap::new();
3215 heap.unpause();
3216 let g = Gc::new_uncollected(Cell0 {
3217 next: Cell::new(None),
3218 marker_calls: Cell::new(0),
3219 });
3220 let before = heap.bytes_used();
3221 g.account_buffer(&heap, 8192);
3222 assert_eq!(
3223 heap.bytes_used(),
3224 before,
3225 "uncollected box must not charge the pacer"
3226 );
3227 }
3228
3229 #[test]
3230 fn collect_unreachable_frees_bytes() {
3231 let heap = Heap::new();
3232 heap.unpause();
3233 let _a = heap.allocate(Cell0 {
3234 next: Cell::new(None),
3235 marker_calls: Cell::new(0),
3236 });
3237 let bytes_before = heap.bytes_used();
3238 assert!(bytes_before > 0);
3239 heap.full_collect(&OneRoot(None));
3241 assert_eq!(heap.bytes_used(), 0);
3242 assert_eq!(heap.collections(), 1);
3243 }
3244
3245 #[test]
3246 fn collect_keeps_reachable() {
3247 let heap = Heap::new();
3248 heap.unpause();
3249 let root = heap.allocate(Cell0 {
3250 next: Cell::new(None),
3251 marker_calls: Cell::new(0),
3252 });
3253 let bytes_before = heap.bytes_used();
3254 heap.full_collect(&OneRoot(Some(root)));
3255 assert_eq!(heap.bytes_used(), bytes_before);
3256 assert_eq!(root.marker_calls.get(), 1);
3257 }
3258
3259 #[test]
3260 fn allocations_start_new_and_white() {
3261 let heap = Heap::new();
3262 heap.unpause();
3263 let obj = heap.allocate(Cell0 {
3264 next: Cell::new(None),
3265 marker_calls: Cell::new(0),
3266 });
3267 assert_eq!(obj.age(), GcAge::New);
3268 assert!(obj.color().is_white());
3269 }
3270
3271 #[test]
3272 fn allocation_tokens_track_exact_live_box() {
3273 let heap = Heap::new();
3274 heap.unpause();
3275 let obj = heap.allocate(Cell0 {
3276 next: Cell::new(None),
3277 marker_calls: Cell::new(0),
3278 });
3279 let id = obj.identity();
3280
3281 assert_eq!(
3282 heap.allocation_token(id),
3283 None,
3284 "lazy registration: a freshly allocated box has no token until it is downgraded"
3285 );
3286
3287 let token = heap.register_allocation_token(id);
3288 assert_eq!(
3289 heap.register_allocation_token(id),
3290 token,
3291 "registration is get-or-insert: re-registering a live identity returns the same token"
3292 );
3293 assert_eq!(heap.allocation_token(id), Some(token));
3294 assert!(heap.contains_allocation(id, token));
3295 assert!(!heap.contains_allocation(id, token + 1));
3296
3297 heap.full_collect(&OneRoot(None));
3298 assert_eq!(heap.allocation_token(id), None);
3299 assert!(!heap.contains_allocation(id, token));
3300 }
3301
3302 #[test]
3303 fn registering_after_sweep_yields_a_distinct_token_on_address_reuse() {
3304 let heap = Heap::new();
3305 heap.unpause();
3306 let first = heap.allocate(Cell0 {
3307 next: Cell::new(None),
3308 marker_calls: Cell::new(0),
3309 });
3310 let id = first.identity();
3311 let first_token = heap.register_allocation_token(id);
3312
3313 heap.full_collect(&OneRoot(None));
3314 assert_eq!(
3315 heap.allocation_token(id),
3316 None,
3317 "sweep removes the token for the dead identity"
3318 );
3319
3320 let second_token = heap.register_allocation_token(id);
3321 assert_ne!(
3322 first_token, second_token,
3323 "monotonic tokens keep address reuse from resurrecting a stale weak handle"
3324 );
3325 assert!(!heap.contains_allocation(id, first_token));
3326 assert!(heap.contains_allocation(id, second_token));
3327 }
3328
3329 #[test]
3330 fn allocation_during_incremental_sweep_survives_current_cycle() {
3331 let heap = Heap::new();
3332 heap.unpause();
3333 let old_dead = heap.allocate(Cell0 {
3334 next: Cell::new(None),
3335 marker_calls: Cell::new(0),
3336 });
3337 let old_id = old_dead.identity();
3338 heap.register_allocation_token(old_id);
3339
3340 let outcome = heap.incremental_run_until_state_with_post_mark(
3341 &OneRoot(None),
3342 GcState::SweepAllGc,
3343 1024,
3344 |_| {},
3345 );
3346 assert_eq!(outcome, StepOutcome::InProgress);
3347 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3348
3349 let new_during_sweep = heap.allocate(Cell0 {
3350 next: Cell::new(None),
3351 marker_calls: Cell::new(0),
3352 });
3353 let new_id = new_during_sweep.identity();
3354 heap.register_allocation_token(new_id);
3355
3356 loop {
3357 let outcome = heap.incremental_step_with_post_mark(
3358 &OneRoot(None),
3359 StepBudget::from_work(64),
3360 |_| {},
3361 );
3362 if matches!(outcome, StepOutcome::Paused) {
3363 break;
3364 }
3365 }
3366
3367 assert_eq!(heap.allocation_token(old_id), None);
3368 assert!(heap.allocation_token(new_id).is_some());
3369 assert_eq!(heap.allgc_count(), 1);
3370
3371 heap.full_collect(&OneRoot(None));
3372 assert_eq!(heap.allocation_token(new_id), None);
3373 assert_eq!(heap.allgc_count(), 0);
3374 }
3375
3376 #[test]
3377 fn promote_and_reset_all_ages() {
3378 let heap = Heap::new();
3379 heap.unpause();
3380 let a = heap.allocate(Cell0 {
3381 next: Cell::new(None),
3382 marker_calls: Cell::new(0),
3383 });
3384 let b = heap.allocate(Cell0 {
3385 next: Cell::new(None),
3386 marker_calls: Cell::new(0),
3387 });
3388
3389 heap.promote_all_to_old();
3390 assert_eq!(a.age(), GcAge::Old);
3391 assert_eq!(b.age(), GcAge::Old);
3392 assert_eq!(a.color(), Color::Black);
3393 assert_eq!(b.color(), Color::Black);
3394
3395 heap.reset_all_ages();
3396 assert_eq!(a.age(), GcAge::New);
3397 assert_eq!(b.age(), GcAge::New);
3398 assert!(a.color().is_white());
3399 assert!(b.color().is_white());
3400 }
3401
3402 #[test]
3403 fn generational_barriers_update_ages() {
3404 let heap = Heap::new();
3405 heap.unpause();
3406 let parent = heap.allocate(Cell0 {
3407 next: Cell::new(None),
3408 marker_calls: Cell::new(0),
3409 });
3410 let child = heap.allocate(Cell0 {
3411 next: Cell::new(None),
3412 marker_calls: Cell::new(0),
3413 });
3414
3415 parent.set_age(GcAge::Old);
3416 parent.set_color(Color::Black);
3417 heap.generational_backward_barrier(parent);
3418 assert_eq!(parent.age(), GcAge::Touched1);
3419 assert_eq!(parent.color(), Color::Gray);
3420
3421 heap.generational_forward_barrier(parent, child);
3422 assert_eq!(child.age(), GcAge::Old0);
3423 }
3424
3425 #[test]
3426 fn minor_collect_frees_young_and_keeps_old() {
3427 let heap = Heap::new();
3428 heap.unpause();
3429 let old_unreachable = heap.allocate(Cell0 {
3430 next: Cell::new(None),
3431 marker_calls: Cell::new(0),
3432 });
3433 old_unreachable.set_age(GcAge::Old);
3434 old_unreachable.set_color(Color::Black);
3435 let _young_unreachable = heap.allocate(Cell0 {
3436 next: Cell::new(None),
3437 marker_calls: Cell::new(0),
3438 });
3439 let young_survivor = heap.allocate(Cell0 {
3440 next: Cell::new(None),
3441 marker_calls: Cell::new(0),
3442 });
3443
3444 heap.minor_collect_with_post_mark(&OneRoot(Some(young_survivor)), |_| {});
3445
3446 assert_eq!(heap.allgc_count(), 2);
3447 assert_eq!(old_unreachable.age(), GcAge::Old);
3448 assert_eq!(young_survivor.age(), GcAge::Survival);
3449 assert!(young_survivor.color().is_white());
3450 }
3451
3452 #[test]
3453 fn minor_collect_skips_untouched_old_root_scan_work() {
3454 let heap = Heap::new();
3455 heap.unpause();
3456 let old_root = heap.allocate(Cell0 {
3457 next: Cell::new(None),
3458 marker_calls: Cell::new(0),
3459 });
3460 old_root.set_age(GcAge::Old);
3461 old_root.set_color(Color::Black);
3462 let young_root = heap.allocate(Cell0 {
3463 next: Cell::new(None),
3464 marker_calls: Cell::new(0),
3465 });
3466
3467 heap.minor_collect_with_post_mark(
3468 &TwoRoots {
3469 first: Some(old_root),
3470 second: Some(young_root),
3471 },
3472 |_| {},
3473 );
3474
3475 let stats = heap.last_mark_stats();
3476 assert_eq!(stats.marked, 2);
3477 assert_eq!(stats.marked_old, 1);
3478 assert_eq!(stats.marked_young, 1);
3479 assert_eq!(stats.traced, 1);
3480 assert_eq!(stats.traced_old, 0);
3481 assert_eq!(stats.traced_young, 1);
3482 assert_eq!(old_root.marker_calls.get(), 0);
3483 assert_eq!(young_root.marker_calls.get(), 1);
3484 assert_eq!(old_root.age(), GcAge::Old);
3485 assert_eq!(young_root.age(), GcAge::Survival);
3486 }
3487
3488 #[test]
3489 fn minor_collect_traces_touched_old_parent() {
3490 let heap = Heap::new();
3491 heap.unpause();
3492 let old_root = heap.allocate(Cell0 {
3493 next: Cell::new(None),
3494 marker_calls: Cell::new(0),
3495 });
3496 old_root.set_age(GcAge::Old);
3497 old_root.set_color(Color::Black);
3498 let young_child = heap.allocate(Cell0 {
3499 next: Cell::new(None),
3500 marker_calls: Cell::new(0),
3501 });
3502 old_root.next.set(Some(young_child));
3503 heap.generational_backward_barrier(old_root);
3504
3505 heap.minor_collect_with_post_mark(&OneRoot(Some(old_root)), |_| {});
3506
3507 let stats = heap.last_mark_stats();
3508 assert_eq!(stats.marked, 2);
3509 assert_eq!(stats.marked_old, 1);
3510 assert_eq!(stats.marked_young, 1);
3511 assert_eq!(stats.traced, 2);
3512 assert_eq!(stats.traced_old, 1);
3513 assert_eq!(stats.traced_young, 1);
3514 assert_eq!(old_root.marker_calls.get(), 1);
3515 assert_eq!(young_child.marker_calls.get(), 1);
3516 assert_eq!(old_root.age(), GcAge::Touched2);
3517 assert_eq!(young_child.age(), GcAge::Survival);
3518 }
3519
3520 #[test]
3521 fn minor_sweep_uses_generation_cursors_to_skip_old_tail() {
3522 let heap = Heap::new();
3523 heap.unpause();
3524 let mut old_objects = Vec::new();
3525 for _ in 0..64 {
3526 old_objects.push(heap.allocate(Cell0 {
3527 next: Cell::new(None),
3528 marker_calls: Cell::new(0),
3529 }));
3530 }
3531 heap.promote_all_to_old();
3532 let young_root = heap.allocate(Cell0 {
3533 next: Cell::new(None),
3534 marker_calls: Cell::new(0),
3535 });
3536
3537 heap.minor_collect_with_post_mark(&OneRoot(Some(young_root)), |_| {});
3538
3539 let stats = heap.last_sweep_stats();
3540 assert_eq!(stats.visited, 1);
3541 assert_eq!(stats.visited_young, 1);
3542 assert_eq!(stats.visited_old, 0);
3543 assert_eq!(heap.allgc_count(), old_objects.len() + 1);
3544 assert_eq!(young_root.age(), GcAge::Survival);
3545 }
3546
3547 #[test]
3548 fn full_sweep_corrects_generation_cursors_when_cursor_object_is_freed() {
3549 let heap = Heap::new();
3550 heap.unpause();
3551 let _old = heap.allocate(Cell0 {
3552 next: Cell::new(None),
3553 marker_calls: Cell::new(0),
3554 });
3555 heap.promote_all_to_old();
3556 assert!(heap.survival.get().is_some());
3557 assert!(heap.old1.get().is_some());
3558 assert!(heap.reallyold.get().is_some());
3559
3560 heap.full_collect(&OneRoot(None));
3561
3562 assert_eq!(heap.allgc_count(), 0);
3563 assert_eq!(heap.survival.get(), None);
3564 assert_eq!(heap.old1.get(), None);
3565 assert_eq!(heap.reallyold.get(), None);
3566 assert_eq!(heap.firstold1.get(), None);
3567 assert_eq!(heap.last_sweep_stats().freed, 1);
3568 }
3569
3570 #[test]
3571 fn full_sweep_unlinks_freed_grayagain_entries() {
3572 let heap = Heap::new();
3573 heap.unpause();
3574 let parent = heap.allocate(Cell0 {
3575 next: Cell::new(None),
3576 marker_calls: Cell::new(0),
3577 });
3578 heap.promote_all_to_old();
3579 heap.generational_backward_barrier(parent);
3580 assert_eq!(heap.grayagain_count(), 1);
3581
3582 heap.full_collect(&OneRoot(None));
3583
3584 assert_eq!(heap.allgc_count(), 0);
3585 assert_eq!(heap.grayagain_count(), 0);
3586
3587 let young = heap.allocate(Cell0 {
3588 next: Cell::new(None),
3589 marker_calls: Cell::new(0),
3590 });
3591 heap.minor_collect_with_post_mark(&OneRoot(Some(young)), |_| {});
3592 assert_eq!(young.age(), GcAge::Survival);
3593 }
3594
3595 #[test]
3596 fn grayagain_links_object_once() {
3597 let heap = Heap::new();
3598 heap.unpause();
3599 let parent = heap.allocate(Cell0 {
3600 next: Cell::new(None),
3601 marker_calls: Cell::new(0),
3602 });
3603 parent.set_age(GcAge::Old);
3604 parent.set_color(Color::Black);
3605
3606 heap.generational_backward_barrier(parent);
3607 heap.generational_backward_barrier(parent);
3608
3609 assert_eq!(heap.grayagain_count(), 1);
3610 }
3611
3612 #[test]
3613 fn grayagain_list_carries_old1_until_old() {
3614 let heap = Heap::new();
3615 heap.unpause();
3616 let survivor = heap.allocate(Cell0 {
3617 next: Cell::new(None),
3618 marker_calls: Cell::new(0),
3619 });
3620
3621 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3622 assert_eq!(survivor.age(), GcAge::Survival);
3623
3624 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3625 assert_eq!(survivor.age(), GcAge::Old1);
3626
3627 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3628 assert_eq!(survivor.age(), GcAge::Old);
3629 assert_eq!(heap.allgc_count(), 1);
3630 }
3631
3632 #[test]
3633 fn grayagain_list_carries_touched2_until_old() {
3634 let heap = Heap::new();
3635 heap.unpause();
3636 let parent = heap.allocate(Cell0 {
3637 next: Cell::new(None),
3638 marker_calls: Cell::new(0),
3639 });
3640 parent.set_age(GcAge::Old);
3641 parent.set_color(Color::Black);
3642 heap.generational_backward_barrier(parent);
3643
3644 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3645 assert_eq!(parent.age(), GcAge::Touched2);
3646
3647 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3648 assert_eq!(parent.age(), GcAge::Old);
3649 assert_eq!(parent.marker_calls.get(), 2);
3650 }
3651
3652 #[test]
3653 fn collect_traverses_cycles() {
3654 let heap = Heap::new();
3655 heap.unpause();
3656 let a = heap.allocate(Cell0 {
3657 next: Cell::new(None),
3658 marker_calls: Cell::new(0),
3659 });
3660 let b = heap.allocate(Cell0 {
3661 next: Cell::new(Some(a)),
3662 marker_calls: Cell::new(0),
3663 });
3664 a.next.set(Some(b)); heap.full_collect(&OneRoot(Some(a)));
3667 assert_eq!(a.marker_calls.get(), 1);
3668 assert_eq!(b.marker_calls.get(), 1);
3669 heap.full_collect(&OneRoot(None));
3673 assert_eq!(heap.bytes_used(), 0);
3674 }
3675
3676 #[test]
3677 fn heap_guard_stacks() {
3678 assert!(
3679 with_current_heap(|heap| heap.is_none()),
3680 "no guard initially"
3681 );
3682 let h1 = Heap::new();
3683 h1.unpause();
3684 {
3685 let _g1 = HeapGuard::push(&h1);
3686 assert!(with_current_heap(|heap| heap.is_some()));
3687 let h2 = Heap::new();
3688 h2.unpause();
3689 {
3690 let _g2 = HeapGuard::push(&h2);
3691 with_current_heap(|heap| {
3693 assert!(std::ptr::addr_eq(
3694 heap.unwrap() as *const _,
3695 &h2 as *const _,
3696 ));
3697 });
3698 }
3699 with_current_heap(|heap| {
3701 assert!(std::ptr::addr_eq(
3702 heap.unwrap() as *const _,
3703 &h1 as *const _,
3704 ));
3705 });
3706 }
3707 assert!(
3708 with_current_heap(|heap| heap.is_none()),
3709 "stack empty after all guards drop"
3710 );
3711 }
3712
3713 #[test]
3714 fn step_threshold_triggers_collect() {
3715 let heap = Heap::new();
3716 heap.unpause();
3717 let mut keeps = Vec::new();
3719 for _ in 0..64 {
3720 keeps.push(heap.allocate(Cell0 {
3724 next: Cell::new(None),
3725 marker_calls: Cell::new(0),
3726 }));
3727 }
3728 struct ManyRoots<'a>(&'a [Gc<Cell0>]);
3730 impl<'a> Trace for ManyRoots<'a> {
3731 fn trace(&self, m: &mut Marker) {
3732 for g in self.0.iter() {
3733 m.mark(*g);
3734 }
3735 }
3736 }
3737 heap.step(&ManyRoots(&keeps));
3738 assert!(heap.bytes_used() > 0);
3740 }
3741
3742 #[test]
3743 fn threshold_floored_after_collecting_tiny_heap() {
3744 let heap = Heap::new();
3745 heap.unpause();
3746 struct NoRoots;
3747 impl Trace for NoRoots {
3748 fn trace(&self, _m: &mut Marker) {}
3749 }
3750 for _ in 0..200 {
3751 heap.allocate(Cell0 {
3752 next: Cell::new(None),
3753 marker_calls: Cell::new(0),
3754 });
3755 }
3756 heap.full_collect(&NoRoots);
3757 assert!(
3758 heap.threshold_bytes() >= GC_MIN_THRESHOLD,
3759 "threshold {} collapsed below floor {}; a churning program would full-collect per allocation",
3760 heap.threshold_bytes(),
3761 GC_MIN_THRESHOLD
3762 );
3763 }
3764
3765 fn build_chain(heap: &Heap, len: usize) -> Gc<Cell0> {
3766 let head = heap.allocate(Cell0 {
3767 next: Cell::new(None),
3768 marker_calls: Cell::new(0),
3769 });
3770 let mut cur = head;
3771 for _ in 1..len {
3772 let n = heap.allocate(Cell0 {
3773 next: Cell::new(None),
3774 marker_calls: Cell::new(0),
3775 });
3776 cur.next.set(Some(n));
3777 cur = n;
3778 }
3779 head
3780 }
3781
3782 #[test]
3783 fn budget_zero_does_some_work() {
3784 let heap = Heap::new();
3785 heap.unpause();
3786 let head = build_chain(&heap, 8);
3787 let roots = OneRoot(Some(head));
3788 let budget = StepBudget::from_work(0);
3789 let outcome = heap.incremental_step_with_post_mark(&roots, budget, |_| {});
3790 assert_ne!(outcome, StepOutcome::SkippedStopped);
3791 assert_ne!(heap.gc_state(), GcState::Pause);
3792 }
3793
3794 #[test]
3795 fn run_until_state_stops_before_next_phase_work() {
3796 let heap = Heap::new();
3797 heap.unpause();
3798 let head = build_chain(&heap, 8);
3799 let roots = OneRoot(Some(head));
3800 let atomic_calls = Cell::new(0);
3801
3802 let outcome =
3803 heap.incremental_run_until_state_with_post_mark(&roots, GcState::Atomic, 1024, |_| {
3804 atomic_calls.set(atomic_calls.get() + 1)
3805 });
3806 assert_eq!(outcome, StepOutcome::InProgress);
3807 assert_eq!(heap.gc_state(), GcState::Atomic);
3808 assert_eq!(
3809 atomic_calls.get(),
3810 0,
3811 "atomic hook must not run before inspection"
3812 );
3813
3814 let outcome = heap.incremental_run_until_state_with_post_mark(
3815 &roots,
3816 GcState::SweepAllGc,
3817 1024,
3818 |_| atomic_calls.set(atomic_calls.get() + 1),
3819 );
3820 assert_eq!(outcome, StepOutcome::InProgress);
3821 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3822 assert_eq!(
3823 atomic_calls.get(),
3824 1,
3825 "entering sweep must run the atomic hook once"
3826 );
3827 }
3828
3829 #[test]
3830 fn larger_budget_drains_more_gray_than_smaller() {
3831 let small_heap = Heap::new();
3832 small_heap.unpause();
3833 let h1 = build_chain(&small_heap, 64);
3834 let r1 = OneRoot(Some(h1));
3835 let mut small_calls = 0;
3836 loop {
3837 small_calls += 1;
3838 let outcome =
3839 small_heap.incremental_step_with_post_mark(&r1, StepBudget::from_work(2), |_| {});
3840 if outcome == StepOutcome::Paused {
3841 break;
3842 }
3843 assert!(small_calls < 10_000, "did not converge");
3844 }
3845
3846 let big_heap = Heap::new();
3847 big_heap.unpause();
3848 let h2 = build_chain(&big_heap, 64);
3849 let r2 = OneRoot(Some(h2));
3850 let mut big_calls = 0;
3851 loop {
3852 big_calls += 1;
3853 let outcome =
3854 big_heap.incremental_step_with_post_mark(&r2, StepBudget::from_work(64), |_| {});
3855 if outcome == StepOutcome::Paused {
3856 break;
3857 }
3858 assert!(big_calls < 10_000, "did not converge");
3859 }
3860
3861 assert!(
3862 big_calls < small_calls,
3863 "expected big_calls={} < small_calls={}",
3864 big_calls,
3865 small_calls
3866 );
3867 }
3868
3869 #[test]
3870 fn sweep_can_pause_and_resume() {
3871 let heap = Heap::new();
3872 heap.unpause();
3873 for _ in 0..16 {
3874 let _ = heap.allocate(Cell0 {
3875 next: Cell::new(None),
3876 marker_calls: Cell::new(0),
3877 });
3878 }
3879 let roots = OneRoot(None);
3880 let bytes_before = heap.bytes_used();
3881 assert!(bytes_before > 0);
3882 let mut step_count = 0;
3883 let mut saw_in_progress_during_sweep = false;
3884 loop {
3885 step_count += 1;
3886 let outcome =
3887 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {});
3888 if heap.gc_state().is_sweep() && outcome == StepOutcome::InProgress {
3889 saw_in_progress_during_sweep = true;
3890 }
3891 if outcome == StepOutcome::Paused {
3892 break;
3893 }
3894 assert!(step_count < 10_000, "did not converge");
3895 }
3896 assert!(saw_in_progress_during_sweep, "sweep never paused mid-list");
3897 assert_eq!(heap.bytes_used(), 0);
3898 }
3899
3900 #[test]
3901 fn post_mark_runs_once_per_atomic() {
3902 let heap = Heap::new();
3903 heap.unpause();
3904 for _ in 0..32 {
3905 let _ = heap.allocate(Cell0 {
3906 next: Cell::new(None),
3907 marker_calls: Cell::new(0),
3908 });
3909 }
3910 let roots = OneRoot(None);
3911 let call_count = std::cell::Cell::new(0);
3912 let mut step_count = 0;
3913 loop {
3914 step_count += 1;
3915 let outcome =
3916 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {
3917 call_count.set(call_count.get() + 1);
3918 });
3919 if outcome == StepOutcome::Paused {
3920 break;
3921 }
3922 assert!(step_count < 10_000, "did not converge");
3923 }
3924 assert_eq!(
3925 call_count.get(),
3926 1,
3927 "post_mark must run exactly once per cycle"
3928 );
3929 }
3930
3931 #[test]
3932 fn full_collect_equivalent_to_incremental_to_pause() {
3933 let h1 = Heap::new();
3934 h1.unpause();
3935 let head1 = h1.allocate(Cell0 {
3936 next: Cell::new(None),
3937 marker_calls: Cell::new(0),
3938 });
3939 let _orphan1 = h1.allocate(Cell0 {
3940 next: Cell::new(None),
3941 marker_calls: Cell::new(0),
3942 });
3943 let _orphan2 = h1.allocate(Cell0 {
3944 next: Cell::new(None),
3945 marker_calls: Cell::new(0),
3946 });
3947 let roots1 = OneRoot(Some(head1));
3948 h1.full_collect(&roots1);
3949 let bytes_full = h1.bytes_used();
3950
3951 let h2 = Heap::new();
3952 h2.unpause();
3953 let head2 = h2.allocate(Cell0 {
3954 next: Cell::new(None),
3955 marker_calls: Cell::new(0),
3956 });
3957 let _orphan3 = h2.allocate(Cell0 {
3958 next: Cell::new(None),
3959 marker_calls: Cell::new(0),
3960 });
3961 let _orphan4 = h2.allocate(Cell0 {
3962 next: Cell::new(None),
3963 marker_calls: Cell::new(0),
3964 });
3965 let roots2 = OneRoot(Some(head2));
3966 loop {
3967 let outcome =
3968 h2.incremental_step_with_post_mark(&roots2, StepBudget::from_work(1), |_| {});
3969 if outcome == StepOutcome::Paused {
3970 break;
3971 }
3972 }
3973 assert_eq!(h2.bytes_used(), bytes_full);
3974 }
3975}
3976
3977