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 FinalizerEntry: Clone {
236 fn identity(&self) -> usize;
237 fn heap_ptr(&self) -> Option<NonNull<GcBox<dyn Trace>>> {
238 None
239 }
240 fn age(&self) -> GcAge;
241 fn is_finalized(&self) -> bool;
242 fn set_finalized(&self, finalized: bool);
243}
244
245pub trait WeakEntry: Clone {
247 type Strong: Clone;
248
249 fn identity(&self) -> usize;
250 fn list_kind(&self) -> WeakListKind;
251 fn upgrade(&self) -> Option<Self::Strong>;
252}
253
254#[derive(Copy, Clone, Debug, PartialEq, Eq)]
255pub enum WeakListKind {
256 WeakValues,
257 Ephemeron,
258 AllWeak,
259}
260
261#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
262pub struct WeakRegistryStats {
263 pub tracked: usize,
264 pub snapshot_live: usize,
265 pub snapshot_dead: usize,
266 pub retained: usize,
267 pub weak_values: usize,
268 pub ephemeron: usize,
269 pub all_weak: usize,
270}
271
272#[derive(Clone, Debug)]
273pub struct WeakRegistry<T: WeakEntry> {
274 weak_values: Vec<T>,
275 ephemeron: Vec<T>,
276 all_weak: Vec<T>,
277 last_stats: WeakRegistryStats,
278}
279
280#[derive(Clone, Debug, PartialEq, Eq)]
281pub struct WeakRegistrySnapshot<T> {
282 pub weak_values: Vec<T>,
283 pub ephemeron: Vec<T>,
284 pub all_weak: Vec<T>,
285}
286
287impl<T> Default for WeakRegistrySnapshot<T> {
288 fn default() -> Self {
289 Self {
290 weak_values: Vec::new(),
291 ephemeron: Vec::new(),
292 all_weak: Vec::new(),
293 }
294 }
295}
296
297impl<T> WeakRegistrySnapshot<T> {
298 pub fn len(&self) -> usize {
299 self.weak_values
300 .len()
301 .saturating_add(self.ephemeron.len())
302 .saturating_add(self.all_weak.len())
303 }
304
305 pub fn into_flat(self) -> Vec<T> {
306 self.weak_values
307 .into_iter()
308 .chain(self.ephemeron)
309 .chain(self.all_weak)
310 .collect()
311 }
312}
313
314impl<T: WeakEntry> Default for WeakRegistry<T> {
315 fn default() -> Self {
316 Self {
317 weak_values: Vec::new(),
318 ephemeron: Vec::new(),
319 all_weak: Vec::new(),
320 last_stats: WeakRegistryStats::default(),
321 }
322 }
323}
324
325impl<T: WeakEntry> WeakRegistry<T> {
326 pub fn len(&self) -> usize {
327 self.weak_values
328 .len()
329 .saturating_add(self.ephemeron.len())
330 .saturating_add(self.all_weak.len())
331 }
332
333 pub fn stats(&self) -> WeakRegistryStats {
334 self.last_stats
335 }
336
337 fn list_mut(&mut self, kind: WeakListKind) -> &mut Vec<T> {
338 match kind {
339 WeakListKind::WeakValues => &mut self.weak_values,
340 WeakListKind::Ephemeron => &mut self.ephemeron,
341 WeakListKind::AllWeak => &mut self.all_weak,
342 }
343 }
344
345 pub fn remove_identity(&mut self, id: usize) {
346 self.weak_values.retain(|entry| entry.identity() != id);
347 self.ephemeron.retain(|entry| entry.identity() != id);
348 self.all_weak.retain(|entry| entry.identity() != id);
349 self.last_stats.tracked = self.len();
350 self.last_stats.retained = self.len();
351 self.update_cohort_stats();
352 }
353
354 fn update_cohort_stats(&mut self) {
355 self.last_stats.weak_values = self.weak_values.len();
356 self.last_stats.ephemeron = self.ephemeron.len();
357 self.last_stats.all_weak = self.all_weak.len();
358 }
359
360 pub fn push_unique(&mut self, entry: T) {
361 let id = entry.identity();
362 self.remove_identity(id);
363 self.list_mut(entry.list_kind()).push(entry);
364 self.last_stats.tracked = self.len();
365 self.last_stats.retained = self.len();
366 self.update_cohort_stats();
367 }
368
369 pub fn live_snapshot_by_kind(&mut self) -> WeakRegistrySnapshot<T::Strong> {
370 let tracked_before = self.len();
371 let weak_values_capacity = self.weak_values.len();
372 let ephemeron_capacity = self.ephemeron.len();
373 let all_weak_capacity = self.all_weak.len();
374 let mut seen = std::collections::HashSet::<usize>::with_capacity(tracked_before);
375 let mut live = WeakRegistrySnapshot {
376 weak_values: Vec::with_capacity(weak_values_capacity),
377 ephemeron: Vec::with_capacity(ephemeron_capacity),
378 all_weak: Vec::with_capacity(all_weak_capacity),
379 };
380 let mut dead = 0usize;
381
382 let entries = std::mem::take(&mut self.weak_values)
383 .into_iter()
384 .chain(std::mem::take(&mut self.ephemeron))
385 .chain(std::mem::take(&mut self.all_weak));
386 for entry in entries {
387 if !seen.insert(entry.identity()) {
388 continue;
389 }
390 match entry.upgrade() {
391 Some(strong) => {
392 match entry.list_kind() {
393 WeakListKind::WeakValues => live.weak_values.push(strong),
394 WeakListKind::Ephemeron => live.ephemeron.push(strong),
395 WeakListKind::AllWeak => live.all_weak.push(strong),
396 }
397 self.list_mut(entry.list_kind()).push(entry);
398 }
399 None => dead += 1,
400 }
401 }
402
403 self.last_stats = WeakRegistryStats {
404 tracked: tracked_before,
405 snapshot_live: live.len(),
406 snapshot_dead: dead,
407 retained: self.len(),
408 weak_values: self.weak_values.len(),
409 ephemeron: self.ephemeron.len(),
410 all_weak: self.all_weak.len(),
411 };
412 live
413 }
414
415 pub fn live_snapshot(&mut self) -> Vec<T::Strong> {
416 self.live_snapshot_by_kind().into_flat()
417 }
418
419 pub fn retain_identities(&mut self, ids: &std::collections::HashSet<usize>) {
420 self.weak_values
421 .retain(|entry| ids.contains(&entry.identity()));
422 self.ephemeron
423 .retain(|entry| ids.contains(&entry.identity()));
424 self.all_weak
425 .retain(|entry| ids.contains(&entry.identity()));
426 self.last_stats.retained = self.len();
427 self.last_stats.tracked = self.len();
428 self.update_cohort_stats();
429 }
430}
431
432#[derive(Clone, Debug)]
433pub struct FinalizerRegistry<T: FinalizerEntry> {
434 pending: Vec<T>,
435 to_be_finalized: Vec<T>,
436 pending_reallyold: usize,
437 pending_old1: usize,
438 pending_survival: usize,
439}
440
441#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
442pub struct FinalizerRegistryStats {
443 pub pending_young: usize,
444 pub pending_old: usize,
445 pub to_be_finalized_young: usize,
446 pub to_be_finalized_old: usize,
447 pub finobj_new: usize,
448 pub finobj_survival: usize,
449 pub finobj_old1: usize,
450 pub finobj_reallyold: usize,
451 pub finobj_minor_scan: usize,
452}
453
454impl<T: FinalizerEntry> Default for FinalizerRegistry<T> {
455 fn default() -> Self {
456 Self {
457 pending: Vec::new(),
458 to_be_finalized: Vec::new(),
459 pending_reallyold: 0,
460 pending_old1: 0,
461 pending_survival: 0,
462 }
463 }
464}
465
466impl<T: FinalizerEntry> FinalizerRegistry<T> {
467 fn pending_new_len(&self) -> usize {
468 self.pending.len().saturating_sub(
469 self.pending_reallyold
470 .saturating_add(self.pending_old1)
471 .saturating_add(self.pending_survival),
472 )
473 }
474
475 fn minor_scan_start(&self) -> usize {
476 self.pending_reallyold.saturating_add(self.pending_old1)
477 }
478
479 fn debug_assert_pending_cohorts(&self) {
480 debug_assert!(
481 self.pending_reallyold
482 .saturating_add(self.pending_old1)
483 .saturating_add(self.pending_survival)
484 <= self.pending.len()
485 );
486 }
487
488 pub fn pending(&self) -> &[T] {
489 &self.pending
490 }
491
492 pub fn pending_snapshot(&self) -> Vec<T> {
493 self.pending.clone()
494 }
495
496 pub fn pending_minor_snapshot(&self) -> Vec<T> {
497 self.pending[self.minor_scan_start().min(self.pending.len())..].to_vec()
498 }
499
500 pub fn to_be_finalized(&self) -> &[T] {
501 &self.to_be_finalized
502 }
503
504 pub fn pending_len(&self) -> usize {
505 self.pending.len()
506 }
507
508 pub fn to_be_finalized_len(&self) -> usize {
509 self.to_be_finalized.len()
510 }
511
512 pub fn has_to_be_finalized(&self) -> bool {
513 !self.to_be_finalized.is_empty()
514 }
515
516 pub fn stats(&self) -> FinalizerRegistryStats {
517 fn count_by_age<T: FinalizerEntry>(objects: &[T]) -> (usize, usize) {
518 objects
519 .iter()
520 .fold((0usize, 0usize), |(young, old), object| {
521 if object.age().is_old() {
522 (young, old + 1)
523 } else {
524 (young + 1, old)
525 }
526 })
527 }
528 let (pending_young, pending_old) = count_by_age(&self.pending);
529 let (to_be_finalized_young, to_be_finalized_old) = count_by_age(&self.to_be_finalized);
530 FinalizerRegistryStats {
531 pending_young,
532 pending_old,
533 to_be_finalized_young,
534 to_be_finalized_old,
535 finobj_new: self.pending_new_len(),
536 finobj_survival: self.pending_survival,
537 finobj_old1: self.pending_old1,
538 finobj_reallyold: self.pending_reallyold,
539 finobj_minor_scan: self.pending.len().saturating_sub(self.minor_scan_start()),
540 }
541 }
542
543 pub fn push_pending_unique(&mut self, object: T) -> bool {
544 if object.is_finalized() {
545 return false;
546 }
547 let id = object.identity();
548 if !self.pending.iter().any(|o| o.identity() == id) {
549 object.set_finalized(true);
550 self.pending.push(object);
551 self.debug_assert_pending_cohorts();
552 true
553 } else {
554 false
555 }
556 }
557
558 pub fn take_pending(&mut self) -> Vec<T> {
559 self.pending_reallyold = 0;
560 self.pending_old1 = 0;
561 self.pending_survival = 0;
562 std::mem::take(&mut self.pending)
563 }
564
565 fn retain_pending_not_in(&mut self, ids: &std::collections::HashSet<usize>) {
566 if ids.is_empty() {
567 return;
568 }
569 let original_reallyold = self.pending_reallyold;
570 let original_old1 = self.pending_old1;
571 let original_survival = self.pending_survival;
572 let mut retained_reallyold = original_reallyold;
573 let mut retained_old1 = original_old1;
574 let mut retained_survival = original_survival;
575 let mut retained = Vec::with_capacity(self.pending.len());
576 for (index, object) in std::mem::take(&mut self.pending).into_iter().enumerate() {
577 if ids.contains(&object.identity()) {
578 if index < original_reallyold {
579 retained_reallyold -= 1;
580 } else if index < original_reallyold + original_old1 {
581 retained_old1 -= 1;
582 } else if index < original_reallyold + original_old1 + original_survival {
583 retained_survival -= 1;
584 }
585 } else {
586 retained.push(object);
587 }
588 }
589 self.pending = retained;
590 self.pending_reallyold = retained_reallyold;
591 self.pending_old1 = retained_old1;
592 self.pending_survival = retained_survival;
593 self.debug_assert_pending_cohorts();
594 }
595
596 pub fn push_to_be_finalized(&mut self, object: T) {
597 object.set_finalized(true);
598 self.to_be_finalized.push(object);
599 }
600
601 fn extend_to_be_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
602 let drain_order: Vec<T> = objects.into_iter().rev().collect();
603 for object in drain_order.iter().cloned() {
604 self.push_to_be_finalized(object);
605 }
606 drain_order
607 }
608
609 pub fn promote_pending_to_finalized(&mut self, objects: Vec<T>) -> Vec<T> {
610 if objects.is_empty() {
611 return Vec::new();
612 }
613 let mut ids: std::collections::HashSet<usize> =
614 std::collections::HashSet::with_capacity(objects.len());
615 ids.extend(objects.iter().map(|object| object.identity()));
616 self.retain_pending_not_in(&ids);
617 self.extend_to_be_finalized(objects)
618 }
619
620 pub fn promote_all_pending_to_old(&mut self) {
621 self.pending_reallyold = self.pending.len();
622 self.pending_old1 = 0;
623 self.pending_survival = 0;
624 }
625
626 pub fn reset_generation_boundaries(&mut self) {
627 self.pending_reallyold = 0;
628 self.pending_old1 = 0;
629 self.pending_survival = 0;
630 }
631
632 pub fn finish_minor_collection(&mut self) {
633 let new = self.pending_new_len();
634 self.pending_reallyold = self.pending_reallyold.saturating_add(self.pending_old1);
635 self.pending_old1 = self.pending_survival;
636 self.pending_survival = new;
637 self.debug_assert_pending_cohorts();
638 }
639
640 pub fn pop_to_be_finalized(&mut self) -> Option<T> {
641 let object = if self.to_be_finalized.is_empty() {
642 None
643 } else {
644 Some(self.to_be_finalized.remove(0))
645 };
646 if let Some(ref object) = object {
647 object.set_finalized(false);
648 }
649 object
650 }
651}
652
653#[repr(C)]
655pub struct GcHeader {
656 color: Cell<Color>,
663 age: Cell<GcAge>,
664 flags: Cell<u8>,
672 size: Cell<u32>,
679 next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
681 gray_next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
683}
684
685const HDR_FINALIZED: u8 = 1;
686const HDR_COLLECTED: u8 = 2;
687const HDR_GRAY_LISTED: u8 = 4;
688const HDR_FREED: u8 = 8;
693
694impl GcHeader {
695 fn new_white(size: usize, color: Color, flags: u8) -> Self {
696 Self {
697 color: Cell::new(color),
698 age: Cell::new(GcAge::New),
699 flags: Cell::new(flags),
700 size: Cell::new(size.min(u32::MAX as usize) as u32),
701 next: Cell::new(None),
702 gray_next: Cell::new(None),
703 }
704 }
705
706 fn flag(&self, bit: u8) -> bool {
707 self.flags.get() & bit != 0
708 }
709
710 fn set_flag(&self, bit: u8, on: bool) {
711 let f = self.flags.get();
712 self.flags.set(if on { f | bit } else { f & !bit });
713 }
714
715 pub fn finalized(&self) -> bool {
716 self.flag(HDR_FINALIZED)
717 }
718
719 pub fn set_finalized(&self, finalized: bool) {
720 self.set_flag(HDR_FINALIZED, finalized);
721 }
722
723 pub fn collected(&self) -> bool {
724 self.flag(HDR_COLLECTED)
725 }
726
727 pub fn gray_listed(&self) -> bool {
728 self.flag(HDR_GRAY_LISTED)
729 }
730
731 pub fn set_gray_listed(&self, listed: bool) {
732 self.set_flag(HDR_GRAY_LISTED, listed);
733 }
734
735 pub fn size(&self) -> usize {
736 self.size.get() as usize
737 }
738
739 pub fn set_size(&self, size: usize) {
740 self.size.set(size.min(u32::MAX as usize) as u32);
741 }
742}
743
744#[repr(C)]
746pub struct GcBox<T: ?Sized> {
747 header: GcHeader,
748 value: T,
749}
750
751impl<T: ?Sized> GcBox<T> {
752 pub fn header(&self) -> &GcHeader {
753 &self.header
754 }
755 pub fn value(&self) -> &T {
756 &self.value
757 }
758}
759
760pub struct Gc<T: ?Sized> {
762 ptr: NonNull<GcBox<T>>,
763 _marker: PhantomData<T>,
765}
766
767impl<T: ?Sized> Copy for Gc<T> {}
771impl<T: ?Sized> Clone for Gc<T> {
772 fn clone(&self) -> Self {
773 *self
774 }
775}
776
777impl<T: ?Sized> PartialEq for Gc<T> {
778 fn eq(&self, other: &Self) -> bool {
779 std::ptr::addr_eq(self.ptr.as_ptr(), other.ptr.as_ptr())
780 }
781}
782impl<T: ?Sized> Eq for Gc<T> {}
783
784impl<T: ?Sized> std::hash::Hash for Gc<T> {
785 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
786 self.ptr.as_ptr().hash(state)
787 }
788}
789
790impl<T: ?Sized> std::fmt::Debug for Gc<T> {
791 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
792 write!(f, "Gc({:p})", self.ptr.as_ptr())
793 }
794}
795
796impl<T: Trace + 'static> Gc<T> {
797 pub fn new_uncollected(value: T) -> Self {
803 let size = std::mem::size_of::<T>();
804 let boxed = Box::new(GcBox {
805 header: GcHeader::new_white(size, Color::White0, 0),
806 value,
807 });
808 Gc {
809 ptr: NonNull::new(Box::into_raw(boxed)).expect("Box::into_raw is non-null"),
810 _marker: PhantomData,
811 }
812 }
813
814 pub fn as_trace_ptr(self) -> NonNull<GcBox<dyn Trace>> {
816 self.ptr
817 }
818}
819
820impl<T: ?Sized> Gc<T> {
821 pub fn ptr_eq(a: Self, b: Self) -> bool {
823 std::ptr::addr_eq(a.ptr.as_ptr(), b.ptr.as_ptr())
824 }
825
826 pub fn identity(self) -> usize {
829 self.ptr.as_ptr() as *const () as usize
830 }
831
832 fn as_box(&self) -> &GcBox<T> {
836 let bx = unsafe { self.ptr.as_ref() };
842 debug_assert!(
843 !bx.header.flag(HDR_FREED),
844 "use-after-sweep: Gc<{}> dereferenced after the collector swept it \
845 (caught by LUA_RS_GC_QUARANTINE; this is a rooting bug — the object \
846 was reachable by execution but not by the root trace)",
847 std::any::type_name::<T>()
848 );
849 bx
850 }
851
852 fn header(&self) -> &GcHeader {
853 &self.as_box().header
854 }
855
856 pub fn color(self) -> Color {
857 self.header().color.get()
858 }
859
860 pub fn set_color(self, color: Color) {
861 self.header().color.set(color);
862 }
863
864 pub fn age(self) -> GcAge {
865 self.header().age.get()
866 }
867
868 pub fn set_age(self, age: GcAge) {
869 self.header().age.set(age);
870 }
871
872 pub fn is_finalized(self) -> bool {
873 self.header().finalized()
874 }
875
876 pub fn set_finalized(self, finalized: bool) {
877 self.header().set_finalized(finalized);
878 }
879
880 pub fn account_buffer(&self, heap: &Heap, delta: isize) {
890 if delta == 0 {
891 return;
892 }
893 let header = self.header();
894 if !header.collected() {
895 return;
896 }
897 if delta >= 0 {
898 header.set_size(header.size().saturating_add(delta as usize));
899 } else {
900 header.set_size(header.size().saturating_sub((-delta) as usize));
901 }
902 heap.adjust_bytes(delta);
903 }
904}
905
906impl<T: ?Sized> std::ops::Deref for Gc<T> {
907 type Target = T;
908 fn deref(&self) -> &T {
909 &self.as_box().value
910 }
911}
912
913impl<T: ?Sized> AsRef<T> for Gc<T> {
914 fn as_ref(&self) -> &T {
915 &self.as_box().value
916 }
917}
918
919pub trait Trace {
936 fn trace(&self, m: &mut Marker);
937
938 fn type_name(&self) -> &'static str {
944 "unknown"
945 }
946}
947
948impl<T: Trace> Trace for Vec<T> {
950 fn trace(&self, m: &mut Marker) {
951 for item in self.iter() {
952 item.trace(m);
953 }
954 }
955}
956
957impl<T: Trace> Trace for Option<T> {
958 fn trace(&self, m: &mut Marker) {
959 if let Some(v) = self {
960 v.trace(m);
961 }
962 }
963}
964
965impl<T: Trace + ?Sized> Trace for Box<T> {
966 fn trace(&self, m: &mut Marker) {
967 (**self).trace(m);
968 }
969}
970
971impl<T: Trace + ?Sized> Trace for std::rc::Rc<T> {
972 fn trace(&self, m: &mut Marker) {
973 (**self).trace(m);
974 }
975}
976
977impl<T: Trace> Trace for RefCell<T> {
978 fn trace(&self, m: &mut Marker) {
979 self.borrow().trace(m);
980 }
981}
982
983impl<T: Trace + 'static> Trace for Gc<T> {
985 fn trace(&self, m: &mut Marker) {
986 m.mark(*self);
987 }
988}
989
990macro_rules! trace_noop {
992 ($($t:ty),*) => {
993 $(impl Trace for $t {
994 fn trace(&self, _m: &mut Marker) {}
995 })*
996 };
997}
998trace_noop!(
999 bool, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, char, String,
1000 str
1001);
1002
1003impl<T> Trace for std::marker::PhantomData<T> {
1004 fn trace(&self, _m: &mut Marker) {}
1005}
1006
1007#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1012pub struct MarkerStats {
1013 pub marked: usize,
1014 pub marked_young: usize,
1015 pub marked_old: usize,
1016 pub traced: usize,
1017 pub traced_young: usize,
1018 pub traced_old: usize,
1019}
1020
1021#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1023pub struct SweepStats {
1024 pub visited: usize,
1025 pub visited_young: usize,
1026 pub visited_old: usize,
1027 pub revisit: usize,
1028 pub freed: usize,
1029 pub freed_bytes: usize,
1030}
1031
1032impl SweepStats {
1033 fn record_visit(&mut self, age: GcAge) {
1034 self.visited += 1;
1035 if age.is_old() {
1036 self.visited_old += 1;
1037 } else {
1038 self.visited_young += 1;
1039 }
1040 }
1041
1042 fn record_free(&mut self, bytes: usize) {
1043 self.freed += 1;
1044 self.freed_bytes += bytes;
1045 }
1046
1047 fn add(&mut self, other: Self) {
1048 self.visited += other.visited;
1049 self.visited_young += other.visited_young;
1050 self.visited_old += other.visited_old;
1051 self.revisit += other.revisit;
1052 self.freed += other.freed;
1053 self.freed_bytes += other.freed_bytes;
1054 }
1055}
1056
1057struct OldRevisitTracker {
1058 old_revisit_ids: Vec<usize>,
1059 processed_ids: Vec<usize>,
1060}
1061
1062impl OldRevisitTracker {
1063 fn new(old_revisit: &[NonNull<GcBox<dyn Trace>>]) -> Option<Self> {
1064 if old_revisit.is_empty() {
1065 return None;
1066 }
1067 let mut old_revisit_ids: Vec<usize> = old_revisit
1068 .iter()
1069 .map(|ptr| ptr.as_ptr() as *const () as usize)
1070 .collect();
1071 old_revisit_ids.sort_unstable();
1072 old_revisit_ids.dedup();
1073 Some(Self {
1074 old_revisit_ids,
1075 processed_ids: Vec::new(),
1076 })
1077 }
1078
1079 #[inline(always)]
1080 fn record_processed(&mut self, id: usize) {
1081 if self.old_revisit_ids.binary_search(&id).is_ok() {
1082 self.processed_ids.push(id);
1083 }
1084 }
1085
1086 fn finish(&mut self) {
1087 self.processed_ids.sort_unstable();
1088 self.processed_ids.dedup();
1089 }
1090
1091 #[inline(always)]
1092 fn was_processed(&self, id: usize) -> bool {
1093 self.processed_ids.binary_search(&id).is_ok()
1094 }
1095}
1096
1097#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1099pub struct AllGcCohortStats {
1100 pub new: usize,
1101 pub survival: usize,
1102 pub old1: usize,
1103 pub old: usize,
1104}
1105
1106#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1107enum MarkerMode {
1108 Full,
1109 Minor,
1110}
1111
1112pub struct Marker {
1114 gray_queue: Vec<NonNull<GcBox<dyn Trace>>>,
1115 visited: IdentityHashSet,
1116 stats: MarkerStats,
1117 mode: MarkerMode,
1118}
1119
1120impl Marker {
1121 fn new_with_capacity(mode: MarkerMode, capacity: usize) -> Self {
1122 Self {
1123 gray_queue: Vec::with_capacity(256),
1124 visited: IdentityHashSet::with_capacity_and_hasher(
1125 capacity,
1126 IdentityBuildHasher::default(),
1127 ),
1128 stats: MarkerStats::default(),
1129 mode,
1130 }
1131 }
1132
1133 fn should_trace_age(&self, age: GcAge) -> bool {
1134 match self.mode {
1135 MarkerMode::Full => true,
1136 MarkerMode::Minor => !matches!(age, GcAge::Old),
1137 }
1138 }
1139
1140 pub fn mark<T: Trace + 'static>(&mut self, gc: Gc<T>) {
1153 let ptr: NonNull<GcBox<dyn Trace>> = gc.ptr;
1154 self.mark_box(ptr, gc.header(), gc.identity());
1155 }
1156
1157 fn mark_box(&mut self, ptr: NonNull<GcBox<dyn Trace>>, header: &GcHeader, id: usize) {
1158 debug_assert!(
1159 !header.flag(HDR_FREED),
1160 "GC marker reached a quarantined (swept) object at {id:#x} — a root \
1161 traced a stale GcRef (caught by LUA_RS_GC_QUARANTINE; bug-B class: \
1162 garbage slot fed into the marker)"
1163 );
1164 if self.visited.insert(id) {
1165 let age = header.age.get();
1166 self.stats.marked += 1;
1167 if age.is_old() {
1168 self.stats.marked_old += 1;
1169 } else {
1170 self.stats.marked_young += 1;
1171 }
1172 if self.should_trace_age(age) {
1173 header.color.set(Color::Gray);
1174 self.gray_queue.push(ptr);
1175 }
1176 }
1177 }
1178
1179 pub fn try_visit(&mut self, addr: usize) -> bool {
1184 self.visited.insert(addr)
1185 }
1186
1187 pub fn is_visited(&self, id: usize) -> bool {
1192 self.visited.contains(&id)
1193 }
1194
1195 pub fn is_marked_or_old<T: Trace + 'static>(&self, gc: Gc<T>) -> bool {
1198 self.is_visited(gc.identity())
1199 || (matches!(self.mode, MarkerMode::Minor) && gc.age().is_old())
1200 }
1201
1202 pub fn visited_count(&self) -> usize {
1206 self.visited.len()
1207 }
1208
1209 pub fn stats(&self) -> MarkerStats {
1211 self.stats
1212 }
1213
1214 pub fn drain_gray_queue(&mut self) {
1223 while let Some(gray_ptr) = self.gray_queue.pop() {
1224 unsafe {
1225 let bx = gray_ptr.as_ref();
1226 self.stats.traced += 1;
1227 if bx.header.age.get().is_old() {
1228 self.stats.traced_old += 1;
1229 } else {
1230 self.stats.traced_young += 1;
1231 }
1232 bx.header.color.set(Color::Black);
1233 bx.value.trace(self);
1234 }
1235 }
1236 }
1237}
1238
1239#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1258pub enum GcState {
1259 Pause,
1260 Propagate,
1261 EnterAtomic,
1262 Atomic,
1263 SweepAllGc,
1264 SweepFinObj,
1265 SweepToBeFnz,
1266 SweepEnd,
1267 CallFin,
1268}
1269
1270impl GcState {
1271 pub fn is_pause(self) -> bool {
1272 matches!(self, GcState::Pause)
1273 }
1274 pub fn is_propagate(self) -> bool {
1275 matches!(self, GcState::Propagate)
1276 }
1277 pub fn is_invariant(self) -> bool {
1278 matches!(
1279 self,
1280 GcState::Propagate | GcState::EnterAtomic | GcState::Atomic
1281 )
1282 }
1283 pub fn is_sweep(self) -> bool {
1284 matches!(
1285 self,
1286 GcState::SweepAllGc | GcState::SweepFinObj | GcState::SweepToBeFnz | GcState::SweepEnd
1287 )
1288 }
1289}
1290
1291#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1293pub enum StepOutcome {
1294 Paused,
1296 InProgress,
1299 SkippedStopped,
1302}
1303
1304#[derive(Copy, Clone, Debug)]
1312pub struct StepBudget {
1313 pub remaining_work: isize,
1314 pub max_credit: isize,
1315}
1316
1317impl StepBudget {
1318 pub fn from_work(work: isize) -> Self {
1320 Self {
1321 remaining_work: work.max(1),
1322 max_credit: work.max(1),
1323 }
1324 }
1325}
1326
1327const GC_MIN_THRESHOLD: usize = 1024 * 1024;
1342
1343pub struct Heap {
1344 head: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1347 finobj: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1349 tobefnz: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1351 survival: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1354 old1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1357 reallyold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1360 firstold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1363 finobjsur: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1365 finobjold1: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1367 finobjrold: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1369 bytes: Cell<usize>,
1371 objects: Cell<usize>,
1373 current_white: Cell<Color>,
1375 allocation_tokens: RefCell<IdentityHashMap<usize>>,
1379 next_allocation_token: Cell<usize>,
1381 threshold: Cell<usize>,
1383 stress: bool,
1390 quarantine: bool,
1401 quarantined: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1405 pause_multiplier: Cell<usize>,
1407 state: Cell<GcState>,
1409 paused: Cell<bool>,
1412 collections: Cell<usize>,
1414 minor_collections: Cell<usize>,
1416 full_collections: Cell<usize>,
1418 last_mark_stats: Cell<MarkerStats>,
1420 last_sweep_stats: Cell<SweepStats>,
1422 grayagain: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1426 marker: RefCell<Option<Marker>>,
1429 marker_pool: RefCell<Option<(Vec<NonNull<GcBox<dyn Trace>>>, IdentityHashSet)>>,
1435 sweep_prev_next: Cell<Option<NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>>>,
1440}
1441
1442impl Default for Heap {
1443 fn default() -> Self {
1444 Self::new()
1445 }
1446}
1447
1448impl Heap {
1449 pub fn new() -> Self {
1450 Self {
1451 head: Cell::new(None),
1452 finobj: Cell::new(None),
1453 tobefnz: Cell::new(None),
1454 survival: Cell::new(None),
1455 old1: Cell::new(None),
1456 reallyold: Cell::new(None),
1457 firstold1: Cell::new(None),
1458 finobjsur: Cell::new(None),
1459 finobjold1: Cell::new(None),
1460 finobjrold: Cell::new(None),
1461 bytes: Cell::new(0),
1462 objects: Cell::new(0),
1463 current_white: Cell::new(Color::White0),
1464 allocation_tokens: RefCell::new(IdentityHashMap::default()),
1465 next_allocation_token: Cell::new(1),
1466 threshold: Cell::new(64 * 1024), stress: std::env::var_os("LUA_RS_GC_STRESS").is_some_and(|v| v == "1"),
1468 quarantine: std::env::var_os("LUA_RS_GC_QUARANTINE").is_some_and(|v| v == "1"),
1469 quarantined: Cell::new(None),
1470 pause_multiplier: Cell::new(200), state: Cell::new(GcState::Pause),
1472 paused: Cell::new(true), collections: Cell::new(0),
1474 minor_collections: Cell::new(0),
1475 full_collections: Cell::new(0),
1476 last_mark_stats: Cell::new(MarkerStats::default()),
1477 last_sweep_stats: Cell::new(SweepStats::default()),
1478 grayagain: Cell::new(None),
1479 marker: RefCell::new(None),
1480 marker_pool: RefCell::new(None),
1481 sweep_prev_next: Cell::new(None),
1482 }
1483 }
1484
1485 pub fn unpause(&self) {
1488 self.paused.set(false);
1489 }
1490
1491 pub fn is_paused(&self) -> bool {
1492 self.paused.get()
1493 }
1494
1495 pub fn allocate<T: Trace + 'static>(&self, value: T) -> Gc<T> {
1497 let size = std::mem::size_of::<GcBox<T>>();
1498 let boxed = Box::new(GcBox {
1499 header: GcHeader::new_white(size, self.current_white.get(), HDR_COLLECTED),
1500 value,
1501 });
1502 boxed.header.next.set(self.head.get());
1503 let raw: *mut GcBox<T> = Box::into_raw(boxed);
1504 let ptr: NonNull<GcBox<T>> = NonNull::new(raw).expect("Box::into_raw is non-null");
1505 let dyn_ptr: NonNull<GcBox<dyn Trace>> = ptr;
1506 let identity = ptr.as_ptr() as *const () as usize;
1507 let token = self.next_token();
1508 self.allocation_tokens.borrow_mut().insert(identity, token);
1509 self.head.set(Some(dyn_ptr));
1510 self.bytes.set(self.bytes.get() + size);
1511 self.objects.set(self.objects.get() + 1);
1512 Gc {
1513 ptr,
1514 _marker: PhantomData,
1515 }
1516 }
1517
1518 pub fn bytes_used(&self) -> usize {
1520 self.bytes.get()
1521 }
1522
1523 pub fn adjust_bytes(&self, delta: isize) {
1528 if delta >= 0 {
1529 self.bytes
1530 .set(self.bytes.get().saturating_add(delta as usize));
1531 } else {
1532 self.bytes
1533 .set(self.bytes.get().saturating_sub((-delta) as usize));
1534 }
1535 }
1536
1537 pub fn threshold_bytes(&self) -> usize {
1542 self.threshold.get()
1543 }
1544
1545 pub fn set_threshold_bytes(&self, threshold: usize) {
1551 self.threshold.set(threshold.max(1));
1552 }
1553
1554 pub fn would_collect(&self) -> bool {
1558 if self.stress {
1559 return !self.paused.get();
1560 }
1561 !self.paused.get() && self.bytes.get() >= self.threshold.get()
1562 }
1563
1564 pub fn collections(&self) -> usize {
1565 self.collections.get()
1566 }
1567
1568 pub fn minor_collections(&self) -> usize {
1569 self.minor_collections.get()
1570 }
1571
1572 pub fn full_collections(&self) -> usize {
1573 self.full_collections.get()
1574 }
1575
1576 pub fn last_mark_stats(&self) -> MarkerStats {
1577 self.last_mark_stats.get()
1578 }
1579
1580 pub fn last_sweep_stats(&self) -> SweepStats {
1581 self.last_sweep_stats.get()
1582 }
1583
1584 pub fn allgc_cohort_stats(&self) -> AllGcCohortStats {
1585 let survival = self.survival.get();
1586 let old1 = self.old1.get();
1587 let reallyold = self.reallyold.get();
1588 let mut stats = AllGcCohortStats::default();
1589 let mut cursor = self.head.get();
1590 let mut seen = IdentityHashSet::default();
1591 let mut cohort = 0u8;
1592 while let Some(ptr) = cursor {
1593 let id = ptr.as_ptr() as *const () as usize;
1594 if !seen.insert(id) {
1595 break;
1596 }
1597 if Some(ptr) == reallyold {
1598 cohort = 3;
1599 } else if Some(ptr) == old1 {
1600 cohort = 2;
1601 } else if Some(ptr) == survival {
1602 cohort = 1;
1603 }
1604 match cohort {
1605 0 => stats.new += 1,
1606 1 => stats.survival += 1,
1607 2 => stats.old1 += 1,
1608 _ => stats.old += 1,
1609 }
1610 cursor = self.header_from_ptr(ptr).next.get();
1611 }
1612 stats
1613 }
1614
1615 fn next_token(&self) -> usize {
1616 let token = self.next_allocation_token.get().max(1);
1617 let next = token.checked_add(1).unwrap_or(1).max(1);
1618 self.next_allocation_token.set(next);
1619 token
1620 }
1621
1622 fn current_white(&self) -> Color {
1623 self.current_white.get()
1624 }
1625
1626 fn other_white(&self) -> Color {
1627 self.current_white.get().other_white()
1628 }
1629
1630 fn flip_current_white(&self) {
1631 self.current_white.set(self.other_white());
1632 }
1633
1634 fn for_each_list_header(
1635 &self,
1636 head: Option<NonNull<GcBox<dyn Trace>>>,
1637 f: &mut impl FnMut(&GcHeader),
1638 ) {
1639 let mut cursor = head;
1640 while let Some(ptr) = cursor {
1641 let header = self.header_from_ptr(ptr);
1642 cursor = header.next.get();
1643 f(header);
1644 }
1645 }
1646
1647 fn for_each_header(&self, mut f: impl FnMut(&GcHeader)) {
1648 self.for_each_list_header(self.head.get(), &mut f);
1649 self.for_each_list_header(self.finobj.get(), &mut f);
1650 self.for_each_list_header(self.tobefnz.get(), &mut f);
1651 }
1652
1653 fn header_from_ptr<'a>(&'a self, ptr: NonNull<GcBox<dyn Trace>>) -> &'a GcHeader {
1654 unsafe { &(*ptr.as_ptr()).header }
1655 }
1656
1657 fn release_box(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1664 if self.quarantine {
1665 let header = self.header_from_ptr(ptr);
1666 header.set_flag(HDR_FREED, true);
1667 header.next.set(self.quarantined.get());
1668 self.quarantined.set(Some(ptr));
1669 } else {
1670 unsafe {
1674 let _ = Box::from_raw(ptr.as_ptr());
1675 }
1676 }
1677 }
1678
1679 fn clear_generation_cursors(&self) {
1680 self.survival.set(None);
1681 self.old1.set(None);
1682 self.reallyold.set(None);
1683 self.firstold1.set(None);
1684 self.finobjsur.set(None);
1685 self.finobjold1.set(None);
1686 self.finobjrold.set(None);
1687 self.clear_grayagain();
1688 }
1689
1690 fn set_all_cursors_to_head(&self) {
1691 let head = self.head.get();
1692 self.survival.set(head);
1693 self.old1.set(head);
1694 self.reallyold.set(head);
1695 self.firstold1.set(None);
1696 let finobj = self.finobj.get();
1697 self.finobjsur.set(finobj);
1698 self.finobjold1.set(finobj);
1699 self.finobjrold.set(finobj);
1700 self.clear_grayagain();
1701 }
1702
1703 fn correct_generation_pointers(
1704 &self,
1705 removed: NonNull<GcBox<dyn Trace>>,
1706 next: Option<NonNull<GcBox<dyn Trace>>>,
1707 ) {
1708 if self.survival.get() == Some(removed) {
1709 self.survival.set(next);
1710 }
1711 if self.old1.get() == Some(removed) {
1712 self.old1.set(next);
1713 }
1714 if self.reallyold.get() == Some(removed) {
1715 self.reallyold.set(next);
1716 }
1717 if self.firstold1.get() == Some(removed) {
1718 self.firstold1.set(next);
1719 }
1720 if self.finobjsur.get() == Some(removed) {
1721 self.finobjsur.set(next);
1722 }
1723 if self.finobjold1.get() == Some(removed) {
1724 self.finobjold1.set(next);
1725 }
1726 if self.finobjrold.get() == Some(removed) {
1727 self.finobjrold.set(next);
1728 }
1729 if self.header_from_ptr(removed).gray_listed() {
1730 self.unlink_grayagain(removed);
1731 }
1732 }
1733
1734 fn unlink_from_list(
1735 &self,
1736 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1737 ptr: NonNull<GcBox<dyn Trace>>,
1738 ) -> bool {
1739 let mut prev_cell = list;
1740 loop {
1741 let Some(current) = prev_cell.get() else {
1742 return false;
1743 };
1744 let header = self.header_from_ptr(current);
1745 let next = header.next.get();
1746 if std::ptr::addr_eq(current.as_ptr(), ptr.as_ptr()) {
1747 prev_cell.set(next);
1748 let prev_next_ptr = NonNull::from(prev_cell);
1749 let removed_next_ptr = NonNull::from(&self.header_from_ptr(ptr).next);
1750 if self.sweep_prev_next.get() == Some(removed_next_ptr) {
1751 self.sweep_prev_next.set(Some(prev_next_ptr));
1752 }
1753 self.correct_generation_pointers(ptr, next);
1754 header.next.set(None);
1755 return true;
1756 }
1757 prev_cell = &header.next;
1758 }
1759 }
1760
1761 fn link_to_head(
1762 &self,
1763 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1764 ptr: NonNull<GcBox<dyn Trace>>,
1765 ) {
1766 let header = self.header_from_ptr(ptr);
1767 header.next.set(list.get());
1768 list.set(Some(ptr));
1769 }
1770
1771 fn link_to_tail(
1772 &self,
1773 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1774 ptr: NonNull<GcBox<dyn Trace>>,
1775 ) {
1776 let mut last_cell = list;
1777 loop {
1778 let Some(current) = last_cell.get() else {
1779 let header = self.header_from_ptr(ptr);
1780 header.next.set(None);
1781 last_cell.set(Some(ptr));
1782 return;
1783 };
1784 last_cell = &self.header_from_ptr(current).next;
1785 }
1786 }
1787
1788 pub fn move_allgc_to_finobj(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1789 let header = self.header_from_ptr(ptr);
1790 if !header.collected() {
1791 return false;
1792 }
1793 if !self.unlink_from_list(&self.head, ptr) {
1794 return false;
1795 }
1796 if self.state.get().is_sweep() {
1797 header.color.set(self.current_white());
1798 }
1799 self.link_to_head(&self.finobj, ptr);
1800 true
1801 }
1802
1803 pub fn move_finobj_to_tobefnz(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1804 if !self.unlink_from_list(&self.finobj, ptr) {
1805 return false;
1806 }
1807 self.link_to_tail(&self.tobefnz, ptr);
1808 true
1809 }
1810
1811 pub fn move_tobefnz_to_allgc(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1812 let header = self.header_from_ptr(ptr);
1813 if !self.unlink_from_list(&self.tobefnz, ptr) {
1814 return false;
1815 }
1816 if self.state.get().is_sweep() {
1817 header.color.set(self.current_white());
1818 }
1819 self.link_to_head(&self.head, ptr);
1820 if header.age.get() == GcAge::Old1 {
1821 self.firstold1.set(Some(ptr));
1822 }
1823 true
1824 }
1825
1826 fn remember_minor_revisit(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1827 let header = self.header_from_ptr(ptr);
1828 if header.gray_listed() {
1829 return;
1830 }
1831 header.gray_next.set(self.grayagain.get());
1832 header.set_gray_listed(true);
1833 self.grayagain.set(Some(ptr));
1834 }
1835
1836 fn mark_minor_revisit_objects(&self, marker: &mut Marker) {
1837 let mut cursor = self.grayagain.get();
1838 while let Some(ptr) = cursor {
1839 let header = self.header_from_ptr(ptr);
1840 cursor = header.gray_next.get();
1841 let id = ptr.as_ptr() as *const () as usize;
1842 marker.mark_box(ptr, header, id);
1843 }
1844 }
1845
1846 fn clear_grayagain(&self) {
1847 let mut cursor = self.grayagain.get();
1848 self.grayagain.set(None);
1849 while let Some(ptr) = cursor {
1850 let header = self.header_from_ptr(ptr);
1851 cursor = header.gray_next.get();
1852 header.gray_next.set(None);
1853 header.set_gray_listed(false);
1854 }
1855 }
1856
1857 fn take_grayagain(&self) -> Vec<NonNull<GcBox<dyn Trace>>> {
1858 let mut objects = Vec::new();
1859 let mut cursor = self.grayagain.get();
1860 self.grayagain.set(None);
1861 while let Some(ptr) = cursor {
1862 let header = self.header_from_ptr(ptr);
1863 cursor = header.gray_next.get();
1864 header.gray_next.set(None);
1865 header.set_gray_listed(false);
1866 objects.push(ptr);
1867 }
1868 objects
1869 }
1870
1871 fn replace_grayagain(&self, objects: Vec<NonNull<GcBox<dyn Trace>>>) {
1872 self.clear_grayagain();
1873 for ptr in objects.into_iter().rev() {
1874 self.remember_minor_revisit(ptr);
1875 }
1876 }
1877
1878 fn unlink_grayagain(&self, removed: NonNull<GcBox<dyn Trace>>) {
1879 let keep = self
1880 .take_grayagain()
1881 .into_iter()
1882 .filter(|ptr| !std::ptr::addr_eq(ptr.as_ptr(), removed.as_ptr()))
1883 .collect();
1884 self.replace_grayagain(keep);
1885 }
1886
1887 pub fn grayagain_count(&self) -> usize {
1888 let mut count = 0usize;
1889 let mut cursor = self.grayagain.get();
1890 while let Some(ptr) = cursor {
1891 count += 1;
1892 cursor = self.header_from_ptr(ptr).gray_next.get();
1893 }
1894 count
1895 }
1896
1897 pub fn allocation_token(&self, identity: usize) -> Option<usize> {
1902 self.allocation_tokens.borrow().get(&identity).copied()
1903 }
1904
1905 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
1910 self.allocation_token(identity) == Some(token)
1911 }
1912
1913 pub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1923 where
1924 P: Trace + 'static,
1925 C: Trace + 'static,
1926 {
1927 if self.paused.get() || self.state.get().is_pause() {
1928 return;
1929 }
1930 if parent.header().color.get() != Color::Black {
1931 return;
1932 }
1933 if !child.header().color.get().is_white() {
1934 return;
1935 }
1936 child.header().color.set(Color::Gray);
1937 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
1938 if let Some(m) = m_opt.as_mut() {
1939 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
1940 m.gray_queue.push(ptr);
1941 m.visited.insert(child.identity());
1942 }
1943 }
1944 }
1945
1946 pub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1949 where
1950 P: Trace + 'static,
1951 C: Trace + 'static,
1952 {
1953 if self.paused.get() || self.state.get().is_pause() {
1954 return;
1955 }
1956 if parent.header().color.get() != Color::Black {
1957 return;
1958 }
1959 if !child.header().color.get().is_white() {
1960 return;
1961 }
1962 parent.header().color.set(Color::Gray);
1963 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
1964 if let Some(m) = m_opt.as_mut() {
1965 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
1966 m.gray_queue.push(ptr);
1967 m.visited.insert(parent.identity());
1968 }
1969 }
1970 }
1971
1972 pub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1977 where
1978 P: Trace + 'static,
1979 C: Trace + 'static,
1980 {
1981 if parent.age().is_old() && !child.age().is_old() {
1982 child.set_age(GcAge::Old0);
1983 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
1984 self.remember_minor_revisit(ptr);
1985 }
1986 self.barrier(parent, child);
1987 }
1988
1989 pub fn generational_backward_barrier<P>(&self, parent: Gc<P>)
1993 where
1994 P: Trace + 'static,
1995 {
1996 if parent.age().is_old() {
1997 parent.set_color(Color::Gray);
1998 parent.set_age(GcAge::Touched1);
1999 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
2000 self.remember_minor_revisit(ptr);
2001 }
2002 }
2003
2004 pub fn step(&self, roots: &dyn Trace) {
2008 self.step_with_post_mark(roots, |_: &mut Marker| {});
2009 }
2010
2011 pub fn step_with_post_mark<F: FnMut(&mut Marker)>(&self, roots: &dyn Trace, post_mark: F) {
2016 if self.paused.get() {
2017 return;
2018 }
2019 if !self.stress && self.bytes.get() < self.threshold.get() {
2020 return;
2021 }
2022 self.full_collect_with_post_mark(roots, post_mark);
2023 }
2024
2025 pub fn full_collect(&self, roots: &dyn Trace) {
2028 self.full_collect_with_post_mark(roots, |_: &mut Marker| {});
2029 }
2030
2031 pub fn mark_only_with_post_mark<F: FnMut(&mut Marker)>(
2036 &self,
2037 roots: &dyn Trace,
2038 mut post_mark: F,
2039 ) {
2040 if self.paused.get() {
2041 return;
2042 }
2043 let mut marker = self.marker_from_pool(MarkerMode::Full);
2044 roots.trace(&mut marker);
2045 marker.drain_gray_queue();
2046 post_mark(&mut marker);
2047 marker.drain_gray_queue();
2048 self.last_mark_stats.set(marker.stats());
2049 self.recycle_marker(marker);
2050 }
2051
2052 pub fn promote_all_to_old(&self) {
2055 self.for_each_header(|header| {
2056 header.age.set(GcAge::Old);
2057 header.color.set(Color::Black);
2058 });
2059 self.set_all_cursors_to_head();
2060 }
2061
2062 pub fn reset_all_ages(&self) {
2065 let current_white = self.current_white();
2066 self.for_each_header(|header| {
2067 header.age.set(GcAge::New);
2068 header.color.set(current_white);
2069 });
2070 self.clear_generation_cursors();
2071 }
2072
2073 pub fn minor_collect_with_post_mark<F: FnMut(&mut Marker)>(
2080 &self,
2081 roots: &dyn Trace,
2082 mut post_mark: F,
2083 ) {
2084 if self.paused.get() {
2085 return;
2086 }
2087 if !self.state.get().is_pause() {
2088 self.abort_cycle();
2089 }
2090
2091 self.state.set(GcState::Propagate);
2092 let mut marker = self.marker_from_pool(MarkerMode::Minor);
2093 self.last_sweep_stats.set(SweepStats::default());
2094 self.mark_minor_revisit_objects(&mut marker);
2095 roots.trace(&mut marker);
2096 marker.drain_gray_queue();
2097
2098 self.state.set(GcState::EnterAtomic);
2099 self.state.set(GcState::Atomic);
2100 post_mark(&mut marker);
2101 marker.drain_gray_queue();
2102 self.last_mark_stats.set(marker.stats());
2103 self.recycle_marker(marker);
2104
2105 self.state.set(GcState::SweepAllGc);
2106 self.sweep_young();
2107 self.recycle_marker_cell();
2108 self.sweep_prev_next.set(None);
2109 self.state.set(GcState::Pause);
2110 self.collections.set(self.collections.get() + 1);
2111 self.minor_collections.set(self.minor_collections.get() + 1);
2112 }
2113
2114 pub fn full_collect_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 if !self.state.get().is_pause() {
2129 self.abort_cycle();
2130 }
2131 self.full_collections.set(self.full_collections.get() + 1);
2132 let unlimited = StepBudget {
2133 remaining_work: isize::MAX,
2134 max_credit: isize::MAX,
2135 };
2136 loop {
2137 let outcome = self.incremental_step_with_post_mark(roots, unlimited, &mut post_mark);
2138 if matches!(outcome, StepOutcome::Paused | StepOutcome::SkippedStopped) {
2139 break;
2140 }
2141 }
2142 }
2143
2144 pub fn incremental_step_with_post_mark<F: FnMut(&mut Marker)>(
2159 &self,
2160 roots: &dyn Trace,
2161 mut budget: StepBudget,
2162 mut post_mark: F,
2163 ) -> StepOutcome {
2164 if self.paused.get() {
2165 return StepOutcome::SkippedStopped;
2166 }
2167 self.run_budgeted(roots, &mut budget, &mut post_mark);
2168 if self.state.get().is_pause() {
2169 StepOutcome::Paused
2170 } else {
2171 StepOutcome::InProgress
2172 }
2173 }
2174
2175 fn run_budgeted(
2176 &self,
2177 roots: &dyn Trace,
2178 budget: &mut StepBudget,
2179 post_mark: &mut dyn FnMut(&mut Marker),
2180 ) -> bool {
2181 self.run_budgeted_until(roots, budget, post_mark, None)
2182 }
2183
2184 fn run_budgeted_until(
2185 &self,
2186 roots: &dyn Trace,
2187 budget: &mut StepBudget,
2188 post_mark: &mut dyn FnMut(&mut Marker),
2189 stop_at: Option<GcState>,
2190 ) -> bool {
2191 let mut did_work = false;
2192 loop {
2193 if stop_at == Some(self.state.get()) {
2194 return did_work;
2195 }
2196 if budget.remaining_work <= -budget.max_credit {
2197 return did_work;
2198 }
2199 match self.state.get() {
2200 GcState::Pause => {
2201 self.start_cycle(roots);
2202 self.state.set(GcState::Propagate);
2203 budget.remaining_work -= 1;
2204 did_work = true;
2205 if stop_at == Some(GcState::Propagate) {
2206 return did_work;
2207 }
2208 }
2209 GcState::Propagate => {
2210 let work = self.drain_gray_budgeted(budget.remaining_work.max(1));
2211 budget.remaining_work -= work as isize;
2212 did_work = did_work || work > 0;
2213 let empty = {
2214 let m = self.marker.borrow();
2215 m.as_ref().map(|m| m.gray_queue.is_empty()).unwrap_or(true)
2216 };
2217 if empty {
2218 self.state.set(GcState::EnterAtomic);
2219 if stop_at == Some(GcState::EnterAtomic) {
2220 return did_work;
2221 }
2222 } else if budget.remaining_work <= 0 {
2223 return did_work;
2224 }
2225 }
2226 GcState::EnterAtomic => {
2227 self.state.set(GcState::Atomic);
2228 budget.remaining_work -= 1;
2229 did_work = true;
2230 if stop_at == Some(GcState::Atomic) || budget.remaining_work <= 0 {
2231 return did_work;
2232 }
2233 }
2234 GcState::Atomic => {
2235 self.run_atomic(post_mark);
2236 self.state.set(GcState::SweepAllGc);
2237 budget.remaining_work -= 1;
2238 did_work = true;
2239 if stop_at == Some(GcState::SweepAllGc) {
2240 return did_work;
2241 }
2242 }
2243 GcState::SweepAllGc => {
2244 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2245 budget.remaining_work -= work as isize;
2246 did_work = did_work || work > 0;
2247 if self.sweep_prev_next.get().is_none() {
2248 self.state.set(GcState::SweepFinObj);
2249 self.sweep_prev_next.set(Some(NonNull::from(&self.finobj)));
2250 if stop_at == Some(GcState::SweepFinObj) {
2251 return did_work;
2252 }
2253 } else if budget.remaining_work <= 0 {
2254 return did_work;
2255 }
2256 }
2257 GcState::SweepFinObj => {
2258 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2259 budget.remaining_work -= work as isize;
2260 did_work = did_work || work > 0;
2261 if self.sweep_prev_next.get().is_none() {
2262 self.state.set(GcState::SweepToBeFnz);
2263 self.sweep_prev_next.set(Some(NonNull::from(&self.tobefnz)));
2264 if stop_at == Some(GcState::SweepToBeFnz) {
2265 return did_work;
2266 }
2267 } else if budget.remaining_work <= 0 {
2268 return did_work;
2269 }
2270 }
2271 GcState::SweepToBeFnz => {
2272 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2273 budget.remaining_work -= work as isize;
2274 did_work = did_work || work > 0;
2275 if self.sweep_prev_next.get().is_none() {
2276 self.state.set(GcState::SweepEnd);
2277 if stop_at == Some(GcState::SweepEnd) {
2278 return did_work;
2279 }
2280 } else if budget.remaining_work <= 0 {
2281 return did_work;
2282 }
2283 }
2284 GcState::SweepEnd => {
2285 self.state.set(GcState::CallFin);
2286 budget.remaining_work -= 1;
2287 did_work = true;
2288 if stop_at == Some(GcState::CallFin) || budget.remaining_work <= 0 {
2289 return did_work;
2290 }
2291 }
2292 GcState::CallFin => {
2293 self.finish_cycle();
2294 self.state.set(GcState::Pause);
2295 if stop_at == Some(GcState::Pause) {
2296 return did_work;
2297 }
2298 return did_work;
2299 }
2300 }
2301 }
2302 }
2303
2304 pub fn incremental_run_until_state_with_post_mark<F: FnMut(&mut Marker)>(
2309 &self,
2310 roots: &dyn Trace,
2311 target: GcState,
2312 max_work: isize,
2313 mut post_mark: F,
2314 ) -> StepOutcome {
2315 if self.paused.get() {
2316 return StepOutcome::SkippedStopped;
2317 }
2318 let work = max_work.max(1);
2319 let mut budget = StepBudget {
2320 remaining_work: work,
2321 max_credit: work,
2322 };
2323 self.run_budgeted_until(roots, &mut budget, &mut post_mark, Some(target));
2324 if self.state.get().is_pause() {
2325 StepOutcome::Paused
2326 } else {
2327 StepOutcome::InProgress
2328 }
2329 }
2330
2331 fn marker_from_pool(&self, mode: MarkerMode) -> Marker {
2334 match self.marker_pool.borrow_mut().take() {
2335 Some((gray_queue, visited)) => Marker {
2336 gray_queue,
2337 visited,
2338 stats: MarkerStats::default(),
2339 mode,
2340 },
2341 None => Marker::new_with_capacity(mode, self.objects.get()),
2342 }
2343 }
2344
2345 fn recycle_marker(&self, marker: Marker) {
2346 let Marker {
2347 mut gray_queue,
2348 mut visited,
2349 ..
2350 } = marker;
2351 gray_queue.clear();
2352 visited.clear();
2353 *self.marker_pool.borrow_mut() = Some((gray_queue, visited));
2354 }
2355
2356 fn recycle_marker_cell(&self) {
2357 if let Some(marker) = self.marker.borrow_mut().take() {
2358 self.recycle_marker(marker);
2359 }
2360 }
2361
2362 fn start_cycle(&self, roots: &dyn Trace) {
2363 self.flip_current_white();
2364 let dead_white = self.other_white();
2365 self.for_each_header(|header| {
2366 header.color.set(dead_white);
2367 });
2368 let mut marker = self.marker_from_pool(MarkerMode::Full);
2369 roots.trace(&mut marker);
2370 *self.marker.borrow_mut() = Some(marker);
2371 self.sweep_prev_next.set(None);
2372 }
2373
2374 fn drain_gray_budgeted(&self, max_units: isize) -> usize {
2375 let mut m_opt = self.marker.borrow_mut();
2376 let marker = match m_opt.as_mut() {
2377 Some(m) => m,
2378 None => return 0,
2379 };
2380 let mut work = 0usize;
2381 let mut budget = max_units;
2382 while budget > 0 {
2383 let next = match marker.gray_queue.pop() {
2384 Some(p) => p,
2385 None => break,
2386 };
2387 unsafe {
2388 let bx = next.as_ref();
2389 marker.stats.traced += 1;
2390 if bx.header.age.get().is_old() {
2391 marker.stats.traced_old += 1;
2392 } else {
2393 marker.stats.traced_young += 1;
2394 }
2395 bx.header.color.set(Color::Black);
2396 bx.value.trace(marker);
2397 }
2398 work += 1;
2399 budget -= 1;
2400 }
2401 work
2402 }
2403
2404 fn run_atomic(&self, post_mark: &mut dyn FnMut(&mut Marker)) {
2405 let mut m_opt = self.marker.borrow_mut();
2406 if let Some(marker) = m_opt.as_mut() {
2407 marker.drain_gray_queue();
2408 post_mark(marker);
2409 marker.drain_gray_queue();
2410 }
2411 self.sweep_prev_next.set(Some(NonNull::from(&self.head)));
2412 self.last_sweep_stats.set(SweepStats::default());
2413 }
2414
2415 fn sweep_budgeted(&self, max_units: isize) -> usize {
2416 let mut work = 0usize;
2417 let mut budget = max_units;
2418 let mut freed_bytes = 0usize;
2419 let mut stats = SweepStats::default();
2420 let current_white = self.current_white();
2421 let dead_white = self.other_white();
2422 let mut prev_next_ptr = match self.sweep_prev_next.get() {
2423 Some(p) => p,
2424 None => return 0,
2425 };
2426 while budget > 0 {
2427 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2428 let cursor = prev_cell.get();
2429 let ptr = match cursor {
2430 Some(p) => p,
2431 None => {
2432 self.sweep_prev_next.set(None);
2433 break;
2434 }
2435 };
2436 let header = self.header_from_ptr(ptr);
2437 let next = header.next.get();
2438 let age = header.age.get();
2439 stats.record_visit(age);
2440 let color = header.color.get();
2441 if color == dead_white {
2442 prev_cell.set(next);
2443 let size = header.size();
2444 freed_bytes += size;
2445 stats.record_free(size);
2446 self.correct_generation_pointers(ptr, next);
2447 self.allocation_tokens
2448 .borrow_mut()
2449 .remove(&(ptr.as_ptr() as *const () as usize));
2450 self.objects.set(self.objects.get().saturating_sub(1));
2451 self.release_box(ptr);
2452 } else {
2453 if matches!(color, Color::Black | Color::Gray) {
2454 header.color.set(current_white);
2455 }
2456 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2457 self.sweep_prev_next.set(Some(prev_next_ptr));
2458 }
2459 work += 1;
2460 budget -= 1;
2461 }
2462 if freed_bytes > 0 {
2463 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2464 }
2465 if stats.visited > 0 {
2466 let mut total = self.last_sweep_stats.get();
2467 total.add(stats);
2468 self.last_sweep_stats.set(total);
2469 }
2470 work
2471 }
2472
2473 fn push_next_revisit(
2474 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2475 seen: &mut IdentityHashSet,
2476 ptr: NonNull<GcBox<dyn Trace>>,
2477 age: GcAge,
2478 ) {
2479 if matches!(
2480 age,
2481 GcAge::Old0 | GcAge::Old1 | GcAge::Touched1 | GcAge::Touched2
2482 ) {
2483 let id = ptr.as_ptr() as *const () as usize;
2484 if seen.insert(id) {
2485 next_revisit.push(ptr);
2486 }
2487 }
2488 }
2489
2490 fn sweep_young_range(
2491 &self,
2492 mut prev_next_ptr: NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2493 limit: Option<NonNull<GcBox<dyn Trace>>>,
2494 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2495 next_revisit_seen: &mut IdentityHashSet,
2496 processed: &mut Option<OldRevisitTracker>,
2497 firstold1: &mut Option<NonNull<GcBox<dyn Trace>>>,
2498 freed_bytes: &mut usize,
2499 stats: &mut SweepStats,
2500 ) -> (
2501 NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2502 Option<NonNull<GcBox<dyn Trace>>>,
2503 ) {
2504 let current_white = self.current_white();
2505 loop {
2506 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2507 let Some(ptr) = prev_cell.get() else {
2508 return (prev_next_ptr, None);
2509 };
2510 if Some(ptr) == limit {
2511 return (prev_next_ptr, Some(ptr));
2512 }
2513 let header = self.header_from_ptr(ptr);
2514 let next = header.next.get();
2515 let age = header.age.get();
2516 stats.record_visit(age);
2517 if let Some(processed) = processed.as_mut() {
2518 processed.record_processed(ptr.as_ptr() as *const () as usize);
2519 }
2520 if header.color.get().is_white() && !age.is_old() {
2521 prev_cell.set(next);
2522 let size = header.size();
2523 *freed_bytes += size;
2524 stats.record_free(size);
2525 self.correct_generation_pointers(ptr, next);
2526 self.allocation_tokens
2527 .borrow_mut()
2528 .remove(&(ptr.as_ptr() as *const () as usize));
2529 self.objects.set(self.objects.get().saturating_sub(1));
2530 self.release_box(ptr);
2531 continue;
2532 }
2533
2534 if !header.color.get().is_white() {
2535 let next_age = age.next_after_minor();
2536 header.age.set(next_age);
2537 if next_age == GcAge::Old1 && firstold1.is_none() {
2538 *firstold1 = Some(ptr);
2539 }
2540 match age {
2541 GcAge::New => header.color.set(current_white),
2542 GcAge::Touched1 | GcAge::Touched2 => header.color.set(Color::Black),
2543 _ => {}
2544 }
2545 Self::push_next_revisit(next_revisit, next_revisit_seen, ptr, next_age);
2546 }
2547 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2548 }
2549 }
2550
2551 fn sweep_young(&self) {
2552 let mut freed_bytes = 0usize;
2553 let mut next_revisit = Vec::new();
2554 let mut next_revisit_seen = IdentityHashSet::default();
2555 let mut firstold1 = None;
2556 let mut stats = SweepStats::default();
2557 let old_revisit = self.take_grayagain();
2558 let mut processed = OldRevisitTracker::new(&old_revisit);
2559 let survival = self.survival.get();
2560 let old1 = self.old1.get();
2561
2562 let (psurvival, new_old1) = self.sweep_young_range(
2563 NonNull::from(&self.head),
2564 survival,
2565 &mut next_revisit,
2566 &mut next_revisit_seen,
2567 &mut processed,
2568 &mut firstold1,
2569 &mut freed_bytes,
2570 &mut stats,
2571 );
2572 self.sweep_young_range(
2573 psurvival,
2574 old1,
2575 &mut next_revisit,
2576 &mut next_revisit_seen,
2577 &mut processed,
2578 &mut firstold1,
2579 &mut freed_bytes,
2580 &mut stats,
2581 );
2582
2583 let finobjsur = self.finobjsur.get();
2584 let finobjold1 = self.finobjold1.get();
2585 let mut dummy_firstold1 = None;
2586 let (pfinobjsur, new_finobjold1) = self.sweep_young_range(
2587 NonNull::from(&self.finobj),
2588 finobjsur,
2589 &mut next_revisit,
2590 &mut next_revisit_seen,
2591 &mut processed,
2592 &mut dummy_firstold1,
2593 &mut freed_bytes,
2594 &mut stats,
2595 );
2596 self.sweep_young_range(
2597 pfinobjsur,
2598 finobjold1,
2599 &mut next_revisit,
2600 &mut next_revisit_seen,
2601 &mut processed,
2602 &mut dummy_firstold1,
2603 &mut freed_bytes,
2604 &mut stats,
2605 );
2606 self.sweep_young_range(
2607 NonNull::from(&self.tobefnz),
2608 None,
2609 &mut next_revisit,
2610 &mut next_revisit_seen,
2611 &mut processed,
2612 &mut dummy_firstold1,
2613 &mut freed_bytes,
2614 &mut stats,
2615 );
2616
2617 if let Some(processed) = processed.as_mut() {
2618 processed.finish();
2619 }
2620
2621 for ptr in old_revisit {
2622 let id = ptr.as_ptr() as *const () as usize;
2623 if processed
2624 .as_ref()
2625 .is_some_and(|processed| processed.was_processed(id))
2626 {
2627 continue;
2628 }
2629 stats.revisit += 1;
2630 let header = self.header_from_ptr(ptr);
2631 if header.color.get().is_white() {
2632 continue;
2633 }
2634 let age = header.age.get();
2635 let next_age = age.next_after_minor();
2636 header.age.set(next_age);
2637 if next_age == GcAge::Old1 && firstold1.is_none() {
2638 firstold1 = Some(ptr);
2639 }
2640 if matches!(age, GcAge::Touched1 | GcAge::Touched2) {
2641 header.color.set(Color::Black);
2642 }
2643 Self::push_next_revisit(&mut next_revisit, &mut next_revisit_seen, ptr, next_age);
2644 }
2645
2646 if freed_bytes > 0 {
2647 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2648 }
2649 self.replace_grayagain(next_revisit);
2650 self.reallyold.set(old1);
2651 self.old1.set(new_old1);
2652 self.survival.set(self.head.get());
2653 self.firstold1.set(firstold1);
2654 self.finobjrold.set(finobjold1);
2655 self.finobjold1.set(new_finobjold1);
2656 self.finobjsur.set(self.finobj.get());
2657 self.last_sweep_stats.set(stats);
2658 }
2659
2660 fn finish_cycle(&self) {
2661 let stats = self
2662 .marker
2663 .borrow()
2664 .as_ref()
2665 .map(|marker| marker.stats())
2666 .unwrap_or_default();
2667 self.last_mark_stats.set(stats);
2668 self.recycle_marker_cell();
2669 self.sweep_prev_next.set(None);
2670 let next = self.bytes.get().saturating_mul(self.pause_multiplier.get()) / 100;
2671 self.threshold.set(next.max(GC_MIN_THRESHOLD));
2672 self.collections.set(self.collections.get() + 1);
2673 }
2674
2675 pub fn finish_callfin_phase(&self) -> bool {
2678 if self.state.get() != GcState::CallFin {
2679 return false;
2680 }
2681 self.finish_cycle();
2682 self.state.set(GcState::Pause);
2683 true
2684 }
2685
2686 fn abort_cycle(&self) {
2687 if !self.state.get().is_pause() {
2688 self.recycle_marker_cell();
2689 self.sweep_prev_next.set(None);
2690 let current_white = self.current_white();
2691 self.for_each_header(|header| {
2692 header.color.set(current_white);
2693 });
2694 self.state.set(GcState::Pause);
2695 }
2696 }
2697
2698 pub fn gc_state(&self) -> GcState {
2700 self.state.get()
2701 }
2702
2703 pub fn allgc_count(&self) -> usize {
2705 self.objects.get()
2706 }
2707
2708 pub fn type_name_count(&self, mut predicate: impl FnMut(&'static str) -> bool) -> usize {
2712 let mut count = 0usize;
2713 for head in [self.head.get(), self.finobj.get(), self.tobefnz.get()] {
2714 let mut cursor = head;
2715 while let Some(ptr) = cursor {
2716 let bx = unsafe { ptr.as_ref() };
2717 cursor = bx.header.next.get();
2718 if predicate(bx.value().type_name()) {
2719 count += 1;
2720 }
2721 }
2722 }
2723 count
2724 }
2725
2726 pub fn drop_all(&self) {
2730 self.recycle_marker_cell();
2731 self.sweep_prev_next.set(None);
2732 self.clear_generation_cursors();
2733 self.state.set(GcState::Pause);
2734 self.allocation_tokens.borrow_mut().clear();
2735 self.drop_list(&self.head);
2736 self.drop_list(&self.finobj);
2737 self.drop_list(&self.tobefnz);
2738 self.drop_list(&self.quarantined);
2739 self.bytes.set(0);
2740 self.objects.set(0);
2741 }
2742
2743 fn drop_list(&self, list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>) {
2744 let mut cursor = list.get();
2745 list.set(None);
2746 while let Some(ptr) = cursor {
2747 let next = unsafe {
2749 let next = (*ptr.as_ptr()).header.next.get();
2750 let _ = Box::from_raw(ptr.as_ptr());
2751 next
2752 };
2753 cursor = next;
2754 }
2755 }
2756}
2757
2758impl Drop for Heap {
2759 fn drop(&mut self) {
2760 self.drop_all();
2761 }
2762}
2763
2764#[cfg(test)]
2769mod tests {
2770 use super::*;
2771
2772 struct Cell0 {
2774 next: Cell<Option<Gc<Cell0>>>,
2775 marker_calls: Cell<usize>,
2776 }
2777
2778 impl Trace for Cell0 {
2779 fn trace(&self, m: &mut Marker) {
2780 self.marker_calls.set(self.marker_calls.get() + 1);
2781 if let Some(n) = self.next.get() {
2782 m.mark(n);
2783 }
2784 }
2785 }
2786
2787 struct OneRoot(Option<Gc<Cell0>>);
2789 impl Trace for OneRoot {
2790 fn trace(&self, m: &mut Marker) {
2791 if let Some(g) = self.0 {
2792 m.mark(g);
2793 }
2794 }
2795 }
2796
2797 struct TwoRoots {
2798 first: Option<Gc<Cell0>>,
2799 second: Option<Gc<Cell0>>,
2800 }
2801
2802 impl Trace for TwoRoots {
2803 fn trace(&self, m: &mut Marker) {
2804 if let Some(g) = self.first {
2805 m.mark(g);
2806 }
2807 if let Some(g) = self.second {
2808 m.mark(g);
2809 }
2810 }
2811 }
2812
2813 #[derive(Clone)]
2814 struct FinalizerCell {
2815 id: usize,
2816 age: GcAge,
2817 finalized: std::rc::Rc<Cell<bool>>,
2818 }
2819
2820 impl FinalizerCell {
2821 fn new(id: usize) -> Self {
2822 Self {
2823 id,
2824 age: GcAge::New,
2825 finalized: std::rc::Rc::new(Cell::new(false)),
2826 }
2827 }
2828 }
2829
2830 impl FinalizerEntry for FinalizerCell {
2831 fn identity(&self) -> usize {
2832 self.id
2833 }
2834
2835 fn age(&self) -> GcAge {
2836 self.age
2837 }
2838
2839 fn is_finalized(&self) -> bool {
2840 self.finalized.get()
2841 }
2842
2843 fn set_finalized(&self, finalized: bool) {
2844 self.finalized.set(finalized);
2845 }
2846 }
2847
2848 fn finalizer_ids(objects: &[FinalizerCell]) -> Vec<usize> {
2849 objects.iter().map(|object| object.id).collect()
2850 }
2851
2852 #[derive(Clone)]
2853 struct WeakCell {
2854 id: usize,
2855 live: bool,
2856 kind: WeakListKind,
2857 }
2858
2859 impl WeakEntry for WeakCell {
2860 type Strong = usize;
2861
2862 fn identity(&self) -> usize {
2863 self.id
2864 }
2865
2866 fn list_kind(&self) -> WeakListKind {
2867 self.kind
2868 }
2869
2870 fn upgrade(&self) -> Option<Self::Strong> {
2871 self.live.then_some(self.id)
2872 }
2873 }
2874
2875 #[test]
2876 fn weak_registry_dedups_snapshots_and_retains_live_ids() {
2877 let mut registry = WeakRegistry::default();
2878 registry.push_unique(WeakCell {
2879 id: 1,
2880 live: true,
2881 kind: WeakListKind::WeakValues,
2882 });
2883 registry.push_unique(WeakCell {
2884 id: 1,
2885 live: true,
2886 kind: WeakListKind::Ephemeron,
2887 });
2888 registry.push_unique(WeakCell {
2889 id: 2,
2890 live: false,
2891 kind: WeakListKind::AllWeak,
2892 });
2893 registry.push_unique(WeakCell {
2894 id: 3,
2895 live: true,
2896 kind: WeakListKind::WeakValues,
2897 });
2898
2899 let stats = registry.stats();
2900 assert_eq!(stats.weak_values, 1);
2901 assert_eq!(stats.ephemeron, 1);
2902 assert_eq!(stats.all_weak, 1);
2903
2904 let snapshot = registry.live_snapshot();
2905 assert_eq!(snapshot, vec![3, 1]);
2906 assert_eq!(
2907 registry.stats(),
2908 WeakRegistryStats {
2909 tracked: 3,
2910 snapshot_live: 2,
2911 snapshot_dead: 1,
2912 retained: 2,
2913 weak_values: 1,
2914 ephemeron: 1,
2915 all_weak: 0,
2916 }
2917 );
2918
2919 let keep: std::collections::HashSet<usize> = [3usize].into_iter().collect();
2920 registry.retain_identities(&keep);
2921 assert_eq!(registry.len(), 1);
2922 assert_eq!(registry.stats().retained, 1);
2923 assert_eq!(registry.stats().weak_values, 1);
2924 registry.remove_identity(3);
2925 assert_eq!(registry.len(), 0);
2926 }
2927
2928 #[test]
2929 fn finalizer_registry_tracks_generational_cohorts() {
2930 let mut registry = FinalizerRegistry::default();
2931 registry.push_pending_unique(FinalizerCell::new(1));
2932 registry.push_pending_unique(FinalizerCell::new(2));
2933
2934 let stats = registry.stats();
2935 assert_eq!(stats.finobj_new, 2);
2936 assert_eq!(stats.finobj_survival, 0);
2937 assert_eq!(stats.finobj_old1, 0);
2938 assert_eq!(stats.finobj_reallyold, 0);
2939 assert_eq!(stats.finobj_minor_scan, 2);
2940
2941 registry.finish_minor_collection();
2942 let stats = registry.stats();
2943 assert_eq!(stats.finobj_new, 0);
2944 assert_eq!(stats.finobj_survival, 2);
2945 assert_eq!(stats.finobj_old1, 0);
2946 assert_eq!(stats.finobj_reallyold, 0);
2947 assert_eq!(stats.finobj_minor_scan, 2);
2948
2949 registry.push_pending_unique(FinalizerCell::new(3));
2950 registry.finish_minor_collection();
2951 let stats = registry.stats();
2952 assert_eq!(stats.finobj_new, 0);
2953 assert_eq!(stats.finobj_survival, 1);
2954 assert_eq!(stats.finobj_old1, 2);
2955 assert_eq!(stats.finobj_reallyold, 0);
2956 assert_eq!(stats.finobj_minor_scan, 1);
2957
2958 registry.finish_minor_collection();
2959 let stats = registry.stats();
2960 assert_eq!(stats.finobj_new, 0);
2961 assert_eq!(stats.finobj_survival, 0);
2962 assert_eq!(stats.finobj_old1, 1);
2963 assert_eq!(stats.finobj_reallyold, 2);
2964 assert_eq!(stats.finobj_minor_scan, 0);
2965 }
2966
2967 #[test]
2968 fn finalizer_registry_minor_snapshot_uses_cohort_boundaries() {
2969 let mut registry = FinalizerRegistry::default();
2970 registry.push_pending_unique(FinalizerCell::new(1));
2971 registry.push_pending_unique(FinalizerCell::new(2));
2972 registry.push_pending_unique(FinalizerCell::new(3));
2973 registry.finish_minor_collection();
2974 registry.finish_minor_collection();
2975 registry.push_pending_unique(FinalizerCell::new(4));
2976 registry.push_pending_unique(FinalizerCell::new(5));
2977
2978 assert_eq!(
2979 finalizer_ids(®istry.pending_minor_snapshot()),
2980 vec![4, 5],
2981 "minor finalizer scan must skip the old1/reallyold prefix"
2982 );
2983
2984 registry.push_to_be_finalized(FinalizerCell::new(99));
2985 registry.promote_pending_to_finalized(vec![
2986 FinalizerCell::new(1),
2987 FinalizerCell::new(2),
2988 FinalizerCell::new(4),
2989 ]);
2990
2991 let stats = registry.stats();
2992 assert_eq!(stats.finobj_old1, 1);
2993 assert_eq!(stats.finobj_new, 1);
2994 assert_eq!(stats.finobj_minor_scan, 1);
2995 assert_eq!(finalizer_ids(registry.pending()), vec![3, 5]);
2996 assert_eq!(
2997 finalizer_ids(registry.to_be_finalized()),
2998 vec![99, 4, 2, 1],
2999 "new to-be-finalized batches append behind older queued finalizers"
3000 );
3001 }
3002
3003 #[test]
3004 fn finalizer_registry_marks_and_clears_finalized_bit() {
3005 let mut registry = FinalizerRegistry::default();
3006 let object = FinalizerCell::new(1);
3007
3008 assert!(!object.is_finalized());
3009 registry.push_pending_unique(object.clone());
3010 assert!(object.is_finalized());
3011
3012 registry.push_pending_unique(object.clone());
3013 assert_eq!(registry.pending_len(), 1);
3014
3015 registry.promote_pending_to_finalized(vec![object.clone()]);
3016 assert!(object.is_finalized());
3017 assert_eq!(registry.pending_len(), 0);
3018 assert_eq!(registry.to_be_finalized_len(), 1);
3019
3020 let popped = registry.pop_to_be_finalized().unwrap();
3021 assert_eq!(popped.id, 1);
3022 assert!(!object.is_finalized());
3023
3024 registry.push_pending_unique(object.clone());
3025 assert!(object.is_finalized());
3026 assert_eq!(registry.pending_len(), 1);
3027 }
3028
3029 #[test]
3030 fn alloc_and_drop_all() {
3031 let heap = Heap::new();
3032 heap.unpause();
3033 let _a = heap.allocate(Cell0 {
3034 next: Cell::new(None),
3035 marker_calls: Cell::new(0),
3036 });
3037 let _b = heap.allocate(Cell0 {
3038 next: Cell::new(None),
3039 marker_calls: Cell::new(0),
3040 });
3041 assert!(heap.bytes_used() > 0);
3042 heap.drop_all();
3043 assert_eq!(heap.bytes_used(), 0);
3044 }
3045
3046 fn list_len(heap: &Heap, mut cursor: Option<NonNull<GcBox<dyn Trace>>>) -> usize {
3047 let mut count = 0usize;
3048 while let Some(ptr) = cursor {
3049 count += 1;
3050 cursor = heap.header_from_ptr(ptr).next.get();
3051 }
3052 count
3053 }
3054
3055 #[test]
3056 fn finalizer_intrusive_lists_sweep_and_drop() {
3057 let heap = Heap::new();
3058 heap.unpause();
3059 let _normal = heap.allocate(Cell0 {
3060 next: Cell::new(None),
3061 marker_calls: Cell::new(0),
3062 });
3063 let finobj = heap.allocate(Cell0 {
3064 next: Cell::new(None),
3065 marker_calls: Cell::new(0),
3066 });
3067 let tobefnz = heap.allocate(Cell0 {
3068 next: Cell::new(None),
3069 marker_calls: Cell::new(0),
3070 });
3071
3072 assert!(heap.move_allgc_to_finobj(finobj.as_trace_ptr()));
3073 assert!(heap.move_allgc_to_finobj(tobefnz.as_trace_ptr()));
3074 assert!(heap.move_finobj_to_tobefnz(tobefnz.as_trace_ptr()));
3075 assert_eq!(list_len(&heap, heap.head.get()), 1);
3076 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3077 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3078 assert_eq!(heap.allgc_count(), 3);
3079
3080 heap.full_collect(&TwoRoots {
3081 first: Some(finobj),
3082 second: Some(tobefnz),
3083 });
3084 assert_eq!(list_len(&heap, heap.head.get()), 0);
3085 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3086 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3087 assert_eq!(heap.allgc_count(), 2);
3088
3089 assert!(heap.move_tobefnz_to_allgc(tobefnz.as_trace_ptr()));
3090 heap.full_collect(&OneRoot(Some(tobefnz)));
3091 assert_eq!(list_len(&heap, heap.head.get()), 1);
3092 assert_eq!(list_len(&heap, heap.finobj.get()), 0);
3093 assert_eq!(list_len(&heap, heap.tobefnz.get()), 0);
3094 assert_eq!(heap.allgc_count(), 1);
3095
3096 heap.drop_all();
3097 assert_eq!(heap.bytes_used(), 0);
3098 }
3099
3100 #[test]
3101 fn account_buffer_refunded_on_sweep() {
3102 let heap = Heap::new();
3103 heap.unpause();
3104 let baseline = heap.bytes_used();
3105 {
3106 let a = heap.allocate(Cell0 {
3107 next: Cell::new(None),
3108 marker_calls: Cell::new(0),
3109 });
3110 assert!(heap.bytes_used() > baseline);
3111 a.account_buffer(&heap, 4096);
3112 assert_eq!(
3113 a.header().size(),
3114 std::mem::size_of::<GcBox<Cell0>>() + 4096
3115 );
3116 }
3117 heap.full_collect(&OneRoot(None));
3120 assert_eq!(
3121 heap.bytes_used(),
3122 baseline,
3123 "account_buffer charge must be fully refunded by sweep"
3124 );
3125 }
3126
3127 #[test]
3128 fn account_buffer_noop_on_uncollected() {
3129 let heap = Heap::new();
3130 heap.unpause();
3131 let g = Gc::new_uncollected(Cell0 {
3132 next: Cell::new(None),
3133 marker_calls: Cell::new(0),
3134 });
3135 let before = heap.bytes_used();
3136 g.account_buffer(&heap, 8192);
3137 assert_eq!(
3138 heap.bytes_used(),
3139 before,
3140 "uncollected box must not charge the pacer"
3141 );
3142 }
3143
3144 #[test]
3145 fn collect_unreachable_frees_bytes() {
3146 let heap = Heap::new();
3147 heap.unpause();
3148 let _a = heap.allocate(Cell0 {
3149 next: Cell::new(None),
3150 marker_calls: Cell::new(0),
3151 });
3152 let bytes_before = heap.bytes_used();
3153 assert!(bytes_before > 0);
3154 heap.full_collect(&OneRoot(None));
3156 assert_eq!(heap.bytes_used(), 0);
3157 assert_eq!(heap.collections(), 1);
3158 }
3159
3160 #[test]
3161 fn collect_keeps_reachable() {
3162 let heap = Heap::new();
3163 heap.unpause();
3164 let root = heap.allocate(Cell0 {
3165 next: Cell::new(None),
3166 marker_calls: Cell::new(0),
3167 });
3168 let bytes_before = heap.bytes_used();
3169 heap.full_collect(&OneRoot(Some(root)));
3170 assert_eq!(heap.bytes_used(), bytes_before);
3171 assert_eq!(root.marker_calls.get(), 1);
3172 }
3173
3174 #[test]
3175 fn allocations_start_new_and_white() {
3176 let heap = Heap::new();
3177 heap.unpause();
3178 let obj = heap.allocate(Cell0 {
3179 next: Cell::new(None),
3180 marker_calls: Cell::new(0),
3181 });
3182 assert_eq!(obj.age(), GcAge::New);
3183 assert!(obj.color().is_white());
3184 }
3185
3186 #[test]
3187 fn allocation_tokens_track_exact_live_box() {
3188 let heap = Heap::new();
3189 heap.unpause();
3190 let obj = heap.allocate(Cell0 {
3191 next: Cell::new(None),
3192 marker_calls: Cell::new(0),
3193 });
3194 let id = obj.identity();
3195 let token = heap
3196 .allocation_token(id)
3197 .expect("heap allocation should get a token");
3198
3199 assert!(heap.contains_allocation(id, token));
3200 assert!(!heap.contains_allocation(id, token + 1));
3201
3202 heap.full_collect(&OneRoot(None));
3203 assert_eq!(heap.allocation_token(id), None);
3204 assert!(!heap.contains_allocation(id, token));
3205 }
3206
3207 #[test]
3208 fn allocation_during_incremental_sweep_survives_current_cycle() {
3209 let heap = Heap::new();
3210 heap.unpause();
3211 let old_dead = heap.allocate(Cell0 {
3212 next: Cell::new(None),
3213 marker_calls: Cell::new(0),
3214 });
3215 let old_id = old_dead.identity();
3216
3217 let outcome = heap.incremental_run_until_state_with_post_mark(
3218 &OneRoot(None),
3219 GcState::SweepAllGc,
3220 1024,
3221 |_| {},
3222 );
3223 assert_eq!(outcome, StepOutcome::InProgress);
3224 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3225
3226 let new_during_sweep = heap.allocate(Cell0 {
3227 next: Cell::new(None),
3228 marker_calls: Cell::new(0),
3229 });
3230 let new_id = new_during_sweep.identity();
3231
3232 loop {
3233 let outcome = heap.incremental_step_with_post_mark(
3234 &OneRoot(None),
3235 StepBudget::from_work(64),
3236 |_| {},
3237 );
3238 if matches!(outcome, StepOutcome::Paused) {
3239 break;
3240 }
3241 }
3242
3243 assert_eq!(heap.allocation_token(old_id), None);
3244 assert!(heap.allocation_token(new_id).is_some());
3245 assert_eq!(heap.allgc_count(), 1);
3246
3247 heap.full_collect(&OneRoot(None));
3248 assert_eq!(heap.allocation_token(new_id), None);
3249 assert_eq!(heap.allgc_count(), 0);
3250 }
3251
3252 #[test]
3253 fn promote_and_reset_all_ages() {
3254 let heap = Heap::new();
3255 heap.unpause();
3256 let a = heap.allocate(Cell0 {
3257 next: Cell::new(None),
3258 marker_calls: Cell::new(0),
3259 });
3260 let b = heap.allocate(Cell0 {
3261 next: Cell::new(None),
3262 marker_calls: Cell::new(0),
3263 });
3264
3265 heap.promote_all_to_old();
3266 assert_eq!(a.age(), GcAge::Old);
3267 assert_eq!(b.age(), GcAge::Old);
3268 assert_eq!(a.color(), Color::Black);
3269 assert_eq!(b.color(), Color::Black);
3270
3271 heap.reset_all_ages();
3272 assert_eq!(a.age(), GcAge::New);
3273 assert_eq!(b.age(), GcAge::New);
3274 assert!(a.color().is_white());
3275 assert!(b.color().is_white());
3276 }
3277
3278 #[test]
3279 fn generational_barriers_update_ages() {
3280 let heap = Heap::new();
3281 heap.unpause();
3282 let parent = heap.allocate(Cell0 {
3283 next: Cell::new(None),
3284 marker_calls: Cell::new(0),
3285 });
3286 let child = heap.allocate(Cell0 {
3287 next: Cell::new(None),
3288 marker_calls: Cell::new(0),
3289 });
3290
3291 parent.set_age(GcAge::Old);
3292 parent.set_color(Color::Black);
3293 heap.generational_backward_barrier(parent);
3294 assert_eq!(parent.age(), GcAge::Touched1);
3295 assert_eq!(parent.color(), Color::Gray);
3296
3297 heap.generational_forward_barrier(parent, child);
3298 assert_eq!(child.age(), GcAge::Old0);
3299 }
3300
3301 #[test]
3302 fn minor_collect_frees_young_and_keeps_old() {
3303 let heap = Heap::new();
3304 heap.unpause();
3305 let old_unreachable = heap.allocate(Cell0 {
3306 next: Cell::new(None),
3307 marker_calls: Cell::new(0),
3308 });
3309 old_unreachable.set_age(GcAge::Old);
3310 old_unreachable.set_color(Color::Black);
3311 let _young_unreachable = heap.allocate(Cell0 {
3312 next: Cell::new(None),
3313 marker_calls: Cell::new(0),
3314 });
3315 let young_survivor = heap.allocate(Cell0 {
3316 next: Cell::new(None),
3317 marker_calls: Cell::new(0),
3318 });
3319
3320 heap.minor_collect_with_post_mark(&OneRoot(Some(young_survivor)), |_| {});
3321
3322 assert_eq!(heap.allgc_count(), 2);
3323 assert_eq!(old_unreachable.age(), GcAge::Old);
3324 assert_eq!(young_survivor.age(), GcAge::Survival);
3325 assert!(young_survivor.color().is_white());
3326 }
3327
3328 #[test]
3329 fn minor_collect_skips_untouched_old_root_scan_work() {
3330 let heap = Heap::new();
3331 heap.unpause();
3332 let old_root = heap.allocate(Cell0 {
3333 next: Cell::new(None),
3334 marker_calls: Cell::new(0),
3335 });
3336 old_root.set_age(GcAge::Old);
3337 old_root.set_color(Color::Black);
3338 let young_root = heap.allocate(Cell0 {
3339 next: Cell::new(None),
3340 marker_calls: Cell::new(0),
3341 });
3342
3343 heap.minor_collect_with_post_mark(
3344 &TwoRoots {
3345 first: Some(old_root),
3346 second: Some(young_root),
3347 },
3348 |_| {},
3349 );
3350
3351 let stats = heap.last_mark_stats();
3352 assert_eq!(stats.marked, 2);
3353 assert_eq!(stats.marked_old, 1);
3354 assert_eq!(stats.marked_young, 1);
3355 assert_eq!(stats.traced, 1);
3356 assert_eq!(stats.traced_old, 0);
3357 assert_eq!(stats.traced_young, 1);
3358 assert_eq!(old_root.marker_calls.get(), 0);
3359 assert_eq!(young_root.marker_calls.get(), 1);
3360 assert_eq!(old_root.age(), GcAge::Old);
3361 assert_eq!(young_root.age(), GcAge::Survival);
3362 }
3363
3364 #[test]
3365 fn minor_collect_traces_touched_old_parent() {
3366 let heap = Heap::new();
3367 heap.unpause();
3368 let old_root = heap.allocate(Cell0 {
3369 next: Cell::new(None),
3370 marker_calls: Cell::new(0),
3371 });
3372 old_root.set_age(GcAge::Old);
3373 old_root.set_color(Color::Black);
3374 let young_child = heap.allocate(Cell0 {
3375 next: Cell::new(None),
3376 marker_calls: Cell::new(0),
3377 });
3378 old_root.next.set(Some(young_child));
3379 heap.generational_backward_barrier(old_root);
3380
3381 heap.minor_collect_with_post_mark(&OneRoot(Some(old_root)), |_| {});
3382
3383 let stats = heap.last_mark_stats();
3384 assert_eq!(stats.marked, 2);
3385 assert_eq!(stats.marked_old, 1);
3386 assert_eq!(stats.marked_young, 1);
3387 assert_eq!(stats.traced, 2);
3388 assert_eq!(stats.traced_old, 1);
3389 assert_eq!(stats.traced_young, 1);
3390 assert_eq!(old_root.marker_calls.get(), 1);
3391 assert_eq!(young_child.marker_calls.get(), 1);
3392 assert_eq!(old_root.age(), GcAge::Touched2);
3393 assert_eq!(young_child.age(), GcAge::Survival);
3394 }
3395
3396 #[test]
3397 fn minor_sweep_uses_generation_cursors_to_skip_old_tail() {
3398 let heap = Heap::new();
3399 heap.unpause();
3400 let mut old_objects = Vec::new();
3401 for _ in 0..64 {
3402 old_objects.push(heap.allocate(Cell0 {
3403 next: Cell::new(None),
3404 marker_calls: Cell::new(0),
3405 }));
3406 }
3407 heap.promote_all_to_old();
3408 let young_root = heap.allocate(Cell0 {
3409 next: Cell::new(None),
3410 marker_calls: Cell::new(0),
3411 });
3412
3413 heap.minor_collect_with_post_mark(&OneRoot(Some(young_root)), |_| {});
3414
3415 let stats = heap.last_sweep_stats();
3416 assert_eq!(stats.visited, 1);
3417 assert_eq!(stats.visited_young, 1);
3418 assert_eq!(stats.visited_old, 0);
3419 assert_eq!(heap.allgc_count(), old_objects.len() + 1);
3420 assert_eq!(young_root.age(), GcAge::Survival);
3421 }
3422
3423 #[test]
3424 fn full_sweep_corrects_generation_cursors_when_cursor_object_is_freed() {
3425 let heap = Heap::new();
3426 heap.unpause();
3427 let _old = heap.allocate(Cell0 {
3428 next: Cell::new(None),
3429 marker_calls: Cell::new(0),
3430 });
3431 heap.promote_all_to_old();
3432 assert!(heap.survival.get().is_some());
3433 assert!(heap.old1.get().is_some());
3434 assert!(heap.reallyold.get().is_some());
3435
3436 heap.full_collect(&OneRoot(None));
3437
3438 assert_eq!(heap.allgc_count(), 0);
3439 assert_eq!(heap.survival.get(), None);
3440 assert_eq!(heap.old1.get(), None);
3441 assert_eq!(heap.reallyold.get(), None);
3442 assert_eq!(heap.firstold1.get(), None);
3443 assert_eq!(heap.last_sweep_stats().freed, 1);
3444 }
3445
3446 #[test]
3447 fn full_sweep_unlinks_freed_grayagain_entries() {
3448 let heap = Heap::new();
3449 heap.unpause();
3450 let parent = heap.allocate(Cell0 {
3451 next: Cell::new(None),
3452 marker_calls: Cell::new(0),
3453 });
3454 heap.promote_all_to_old();
3455 heap.generational_backward_barrier(parent);
3456 assert_eq!(heap.grayagain_count(), 1);
3457
3458 heap.full_collect(&OneRoot(None));
3459
3460 assert_eq!(heap.allgc_count(), 0);
3461 assert_eq!(heap.grayagain_count(), 0);
3462
3463 let young = heap.allocate(Cell0 {
3464 next: Cell::new(None),
3465 marker_calls: Cell::new(0),
3466 });
3467 heap.minor_collect_with_post_mark(&OneRoot(Some(young)), |_| {});
3468 assert_eq!(young.age(), GcAge::Survival);
3469 }
3470
3471 #[test]
3472 fn grayagain_links_object_once() {
3473 let heap = Heap::new();
3474 heap.unpause();
3475 let parent = heap.allocate(Cell0 {
3476 next: Cell::new(None),
3477 marker_calls: Cell::new(0),
3478 });
3479 parent.set_age(GcAge::Old);
3480 parent.set_color(Color::Black);
3481
3482 heap.generational_backward_barrier(parent);
3483 heap.generational_backward_barrier(parent);
3484
3485 assert_eq!(heap.grayagain_count(), 1);
3486 }
3487
3488 #[test]
3489 fn grayagain_list_carries_old1_until_old() {
3490 let heap = Heap::new();
3491 heap.unpause();
3492 let survivor = heap.allocate(Cell0 {
3493 next: Cell::new(None),
3494 marker_calls: Cell::new(0),
3495 });
3496
3497 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3498 assert_eq!(survivor.age(), GcAge::Survival);
3499
3500 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3501 assert_eq!(survivor.age(), GcAge::Old1);
3502
3503 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3504 assert_eq!(survivor.age(), GcAge::Old);
3505 assert_eq!(heap.allgc_count(), 1);
3506 }
3507
3508 #[test]
3509 fn grayagain_list_carries_touched2_until_old() {
3510 let heap = Heap::new();
3511 heap.unpause();
3512 let parent = heap.allocate(Cell0 {
3513 next: Cell::new(None),
3514 marker_calls: Cell::new(0),
3515 });
3516 parent.set_age(GcAge::Old);
3517 parent.set_color(Color::Black);
3518 heap.generational_backward_barrier(parent);
3519
3520 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3521 assert_eq!(parent.age(), GcAge::Touched2);
3522
3523 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3524 assert_eq!(parent.age(), GcAge::Old);
3525 assert_eq!(parent.marker_calls.get(), 2);
3526 }
3527
3528 #[test]
3529 fn collect_traverses_cycles() {
3530 let heap = Heap::new();
3531 heap.unpause();
3532 let a = heap.allocate(Cell0 {
3533 next: Cell::new(None),
3534 marker_calls: Cell::new(0),
3535 });
3536 let b = heap.allocate(Cell0 {
3537 next: Cell::new(Some(a)),
3538 marker_calls: Cell::new(0),
3539 });
3540 a.next.set(Some(b)); heap.full_collect(&OneRoot(Some(a)));
3543 assert_eq!(a.marker_calls.get(), 1);
3544 assert_eq!(b.marker_calls.get(), 1);
3545 heap.full_collect(&OneRoot(None));
3549 assert_eq!(heap.bytes_used(), 0);
3550 }
3551
3552 #[test]
3553 fn heap_guard_stacks() {
3554 assert!(
3555 with_current_heap(|heap| heap.is_none()),
3556 "no guard initially"
3557 );
3558 let h1 = Heap::new();
3559 h1.unpause();
3560 {
3561 let _g1 = HeapGuard::push(&h1);
3562 assert!(with_current_heap(|heap| heap.is_some()));
3563 let h2 = Heap::new();
3564 h2.unpause();
3565 {
3566 let _g2 = HeapGuard::push(&h2);
3567 with_current_heap(|heap| {
3569 assert!(std::ptr::addr_eq(
3570 heap.unwrap() as *const _,
3571 &h2 as *const _,
3572 ));
3573 });
3574 }
3575 with_current_heap(|heap| {
3577 assert!(std::ptr::addr_eq(
3578 heap.unwrap() as *const _,
3579 &h1 as *const _,
3580 ));
3581 });
3582 }
3583 assert!(
3584 with_current_heap(|heap| heap.is_none()),
3585 "stack empty after all guards drop"
3586 );
3587 }
3588
3589 #[test]
3590 fn step_threshold_triggers_collect() {
3591 let heap = Heap::new();
3592 heap.unpause();
3593 let mut keeps = Vec::new();
3595 for _ in 0..64 {
3596 keeps.push(heap.allocate(Cell0 {
3600 next: Cell::new(None),
3601 marker_calls: Cell::new(0),
3602 }));
3603 }
3604 struct ManyRoots<'a>(&'a [Gc<Cell0>]);
3606 impl<'a> Trace for ManyRoots<'a> {
3607 fn trace(&self, m: &mut Marker) {
3608 for g in self.0.iter() {
3609 m.mark(*g);
3610 }
3611 }
3612 }
3613 heap.step(&ManyRoots(&keeps));
3614 assert!(heap.bytes_used() > 0);
3616 }
3617
3618 #[test]
3619 fn threshold_floored_after_collecting_tiny_heap() {
3620 let heap = Heap::new();
3621 heap.unpause();
3622 struct NoRoots;
3623 impl Trace for NoRoots {
3624 fn trace(&self, _m: &mut Marker) {}
3625 }
3626 for _ in 0..200 {
3627 heap.allocate(Cell0 {
3628 next: Cell::new(None),
3629 marker_calls: Cell::new(0),
3630 });
3631 }
3632 heap.full_collect(&NoRoots);
3633 assert!(
3634 heap.threshold_bytes() >= GC_MIN_THRESHOLD,
3635 "threshold {} collapsed below floor {}; a churning program would full-collect per allocation",
3636 heap.threshold_bytes(),
3637 GC_MIN_THRESHOLD
3638 );
3639 }
3640
3641 fn build_chain(heap: &Heap, len: usize) -> Gc<Cell0> {
3642 let head = heap.allocate(Cell0 {
3643 next: Cell::new(None),
3644 marker_calls: Cell::new(0),
3645 });
3646 let mut cur = head;
3647 for _ in 1..len {
3648 let n = heap.allocate(Cell0 {
3649 next: Cell::new(None),
3650 marker_calls: Cell::new(0),
3651 });
3652 cur.next.set(Some(n));
3653 cur = n;
3654 }
3655 head
3656 }
3657
3658 #[test]
3659 fn budget_zero_does_some_work() {
3660 let heap = Heap::new();
3661 heap.unpause();
3662 let head = build_chain(&heap, 8);
3663 let roots = OneRoot(Some(head));
3664 let budget = StepBudget::from_work(0);
3665 let outcome = heap.incremental_step_with_post_mark(&roots, budget, |_| {});
3666 assert_ne!(outcome, StepOutcome::SkippedStopped);
3667 assert_ne!(heap.gc_state(), GcState::Pause);
3668 }
3669
3670 #[test]
3671 fn run_until_state_stops_before_next_phase_work() {
3672 let heap = Heap::new();
3673 heap.unpause();
3674 let head = build_chain(&heap, 8);
3675 let roots = OneRoot(Some(head));
3676 let atomic_calls = Cell::new(0);
3677
3678 let outcome =
3679 heap.incremental_run_until_state_with_post_mark(&roots, GcState::Atomic, 1024, |_| {
3680 atomic_calls.set(atomic_calls.get() + 1)
3681 });
3682 assert_eq!(outcome, StepOutcome::InProgress);
3683 assert_eq!(heap.gc_state(), GcState::Atomic);
3684 assert_eq!(
3685 atomic_calls.get(),
3686 0,
3687 "atomic hook must not run before inspection"
3688 );
3689
3690 let outcome = heap.incremental_run_until_state_with_post_mark(
3691 &roots,
3692 GcState::SweepAllGc,
3693 1024,
3694 |_| atomic_calls.set(atomic_calls.get() + 1),
3695 );
3696 assert_eq!(outcome, StepOutcome::InProgress);
3697 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3698 assert_eq!(
3699 atomic_calls.get(),
3700 1,
3701 "entering sweep must run the atomic hook once"
3702 );
3703 }
3704
3705 #[test]
3706 fn larger_budget_drains_more_gray_than_smaller() {
3707 let small_heap = Heap::new();
3708 small_heap.unpause();
3709 let h1 = build_chain(&small_heap, 64);
3710 let r1 = OneRoot(Some(h1));
3711 let mut small_calls = 0;
3712 loop {
3713 small_calls += 1;
3714 let outcome =
3715 small_heap.incremental_step_with_post_mark(&r1, StepBudget::from_work(2), |_| {});
3716 if outcome == StepOutcome::Paused {
3717 break;
3718 }
3719 assert!(small_calls < 10_000, "did not converge");
3720 }
3721
3722 let big_heap = Heap::new();
3723 big_heap.unpause();
3724 let h2 = build_chain(&big_heap, 64);
3725 let r2 = OneRoot(Some(h2));
3726 let mut big_calls = 0;
3727 loop {
3728 big_calls += 1;
3729 let outcome =
3730 big_heap.incremental_step_with_post_mark(&r2, StepBudget::from_work(64), |_| {});
3731 if outcome == StepOutcome::Paused {
3732 break;
3733 }
3734 assert!(big_calls < 10_000, "did not converge");
3735 }
3736
3737 assert!(
3738 big_calls < small_calls,
3739 "expected big_calls={} < small_calls={}",
3740 big_calls,
3741 small_calls
3742 );
3743 }
3744
3745 #[test]
3746 fn sweep_can_pause_and_resume() {
3747 let heap = Heap::new();
3748 heap.unpause();
3749 for _ in 0..16 {
3750 let _ = heap.allocate(Cell0 {
3751 next: Cell::new(None),
3752 marker_calls: Cell::new(0),
3753 });
3754 }
3755 let roots = OneRoot(None);
3756 let bytes_before = heap.bytes_used();
3757 assert!(bytes_before > 0);
3758 let mut step_count = 0;
3759 let mut saw_in_progress_during_sweep = false;
3760 loop {
3761 step_count += 1;
3762 let outcome =
3763 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {});
3764 if heap.gc_state().is_sweep() && outcome == StepOutcome::InProgress {
3765 saw_in_progress_during_sweep = true;
3766 }
3767 if outcome == StepOutcome::Paused {
3768 break;
3769 }
3770 assert!(step_count < 10_000, "did not converge");
3771 }
3772 assert!(saw_in_progress_during_sweep, "sweep never paused mid-list");
3773 assert_eq!(heap.bytes_used(), 0);
3774 }
3775
3776 #[test]
3777 fn post_mark_runs_once_per_atomic() {
3778 let heap = Heap::new();
3779 heap.unpause();
3780 for _ in 0..32 {
3781 let _ = heap.allocate(Cell0 {
3782 next: Cell::new(None),
3783 marker_calls: Cell::new(0),
3784 });
3785 }
3786 let roots = OneRoot(None);
3787 let call_count = std::cell::Cell::new(0);
3788 let mut step_count = 0;
3789 loop {
3790 step_count += 1;
3791 let outcome =
3792 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {
3793 call_count.set(call_count.get() + 1);
3794 });
3795 if outcome == StepOutcome::Paused {
3796 break;
3797 }
3798 assert!(step_count < 10_000, "did not converge");
3799 }
3800 assert_eq!(
3801 call_count.get(),
3802 1,
3803 "post_mark must run exactly once per cycle"
3804 );
3805 }
3806
3807 #[test]
3808 fn full_collect_equivalent_to_incremental_to_pause() {
3809 let h1 = Heap::new();
3810 h1.unpause();
3811 let head1 = h1.allocate(Cell0 {
3812 next: Cell::new(None),
3813 marker_calls: Cell::new(0),
3814 });
3815 let _orphan1 = h1.allocate(Cell0 {
3816 next: Cell::new(None),
3817 marker_calls: Cell::new(0),
3818 });
3819 let _orphan2 = h1.allocate(Cell0 {
3820 next: Cell::new(None),
3821 marker_calls: Cell::new(0),
3822 });
3823 let roots1 = OneRoot(Some(head1));
3824 h1.full_collect(&roots1);
3825 let bytes_full = h1.bytes_used();
3826
3827 let h2 = Heap::new();
3828 h2.unpause();
3829 let head2 = h2.allocate(Cell0 {
3830 next: Cell::new(None),
3831 marker_calls: Cell::new(0),
3832 });
3833 let _orphan3 = h2.allocate(Cell0 {
3834 next: Cell::new(None),
3835 marker_calls: Cell::new(0),
3836 });
3837 let _orphan4 = h2.allocate(Cell0 {
3838 next: Cell::new(None),
3839 marker_calls: Cell::new(0),
3840 });
3841 let roots2 = OneRoot(Some(head2));
3842 loop {
3843 let outcome =
3844 h2.incremental_step_with_post_mark(&roots2, StepBudget::from_work(1), |_| {});
3845 if outcome == StepOutcome::Paused {
3846 break;
3847 }
3848 }
3849 assert_eq!(h2.bytes_used(), bytes_full);
3850 }
3851}
3852
3853