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 self.head.set(Some(dyn_ptr));
1507 self.bytes.set(self.bytes.get() + size);
1508 self.objects.set(self.objects.get() + 1);
1509 Gc {
1510 ptr,
1511 _marker: PhantomData,
1512 }
1513 }
1514
1515 pub fn bytes_used(&self) -> usize {
1517 self.bytes.get()
1518 }
1519
1520 pub fn adjust_bytes(&self, delta: isize) {
1525 if delta >= 0 {
1526 self.bytes
1527 .set(self.bytes.get().saturating_add(delta as usize));
1528 } else {
1529 self.bytes
1530 .set(self.bytes.get().saturating_sub((-delta) as usize));
1531 }
1532 }
1533
1534 pub fn threshold_bytes(&self) -> usize {
1539 self.threshold.get()
1540 }
1541
1542 pub fn set_threshold_bytes(&self, threshold: usize) {
1548 self.threshold.set(threshold.max(1));
1549 }
1550
1551 pub fn would_collect(&self) -> bool {
1555 if self.stress {
1556 return !self.paused.get();
1557 }
1558 !self.paused.get() && self.bytes.get() >= self.threshold.get()
1559 }
1560
1561 pub fn collections(&self) -> usize {
1562 self.collections.get()
1563 }
1564
1565 pub fn minor_collections(&self) -> usize {
1566 self.minor_collections.get()
1567 }
1568
1569 pub fn full_collections(&self) -> usize {
1570 self.full_collections.get()
1571 }
1572
1573 pub fn last_mark_stats(&self) -> MarkerStats {
1574 self.last_mark_stats.get()
1575 }
1576
1577 pub fn last_sweep_stats(&self) -> SweepStats {
1578 self.last_sweep_stats.get()
1579 }
1580
1581 pub fn allgc_cohort_stats(&self) -> AllGcCohortStats {
1582 let survival = self.survival.get();
1583 let old1 = self.old1.get();
1584 let reallyold = self.reallyold.get();
1585 let mut stats = AllGcCohortStats::default();
1586 let mut cursor = self.head.get();
1587 let mut seen = IdentityHashSet::default();
1588 let mut cohort = 0u8;
1589 while let Some(ptr) = cursor {
1590 let id = ptr.as_ptr() as *const () as usize;
1591 if !seen.insert(id) {
1592 break;
1593 }
1594 if Some(ptr) == reallyold {
1595 cohort = 3;
1596 } else if Some(ptr) == old1 {
1597 cohort = 2;
1598 } else if Some(ptr) == survival {
1599 cohort = 1;
1600 }
1601 match cohort {
1602 0 => stats.new += 1,
1603 1 => stats.survival += 1,
1604 2 => stats.old1 += 1,
1605 _ => stats.old += 1,
1606 }
1607 cursor = self.header_from_ptr(ptr).next.get();
1608 }
1609 stats
1610 }
1611
1612 fn next_token(&self) -> usize {
1613 let token = self.next_allocation_token.get().max(1);
1614 let next = token.checked_add(1).unwrap_or(1).max(1);
1615 self.next_allocation_token.set(next);
1616 token
1617 }
1618
1619 fn current_white(&self) -> Color {
1620 self.current_white.get()
1621 }
1622
1623 fn other_white(&self) -> Color {
1624 self.current_white.get().other_white()
1625 }
1626
1627 fn flip_current_white(&self) {
1628 self.current_white.set(self.other_white());
1629 }
1630
1631 fn for_each_list_header(
1632 &self,
1633 head: Option<NonNull<GcBox<dyn Trace>>>,
1634 f: &mut impl FnMut(&GcHeader),
1635 ) {
1636 let mut cursor = head;
1637 while let Some(ptr) = cursor {
1638 let header = self.header_from_ptr(ptr);
1639 cursor = header.next.get();
1640 f(header);
1641 }
1642 }
1643
1644 fn for_each_header(&self, mut f: impl FnMut(&GcHeader)) {
1645 self.for_each_list_header(self.head.get(), &mut f);
1646 self.for_each_list_header(self.finobj.get(), &mut f);
1647 self.for_each_list_header(self.tobefnz.get(), &mut f);
1648 }
1649
1650 fn header_from_ptr<'a>(&'a self, ptr: NonNull<GcBox<dyn Trace>>) -> &'a GcHeader {
1651 unsafe { &(*ptr.as_ptr()).header }
1652 }
1653
1654 fn release_box(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1661 if self.quarantine {
1662 let header = self.header_from_ptr(ptr);
1663 header.set_flag(HDR_FREED, true);
1664 header.next.set(self.quarantined.get());
1665 self.quarantined.set(Some(ptr));
1666 } else {
1667 unsafe {
1671 let _ = Box::from_raw(ptr.as_ptr());
1672 }
1673 }
1674 }
1675
1676 fn clear_generation_cursors(&self) {
1677 self.survival.set(None);
1678 self.old1.set(None);
1679 self.reallyold.set(None);
1680 self.firstold1.set(None);
1681 self.finobjsur.set(None);
1682 self.finobjold1.set(None);
1683 self.finobjrold.set(None);
1684 self.clear_grayagain();
1685 }
1686
1687 fn set_all_cursors_to_head(&self) {
1688 let head = self.head.get();
1689 self.survival.set(head);
1690 self.old1.set(head);
1691 self.reallyold.set(head);
1692 self.firstold1.set(None);
1693 let finobj = self.finobj.get();
1694 self.finobjsur.set(finobj);
1695 self.finobjold1.set(finobj);
1696 self.finobjrold.set(finobj);
1697 self.clear_grayagain();
1698 }
1699
1700 fn correct_generation_pointers(
1701 &self,
1702 removed: NonNull<GcBox<dyn Trace>>,
1703 next: Option<NonNull<GcBox<dyn Trace>>>,
1704 ) {
1705 if self.survival.get() == Some(removed) {
1706 self.survival.set(next);
1707 }
1708 if self.old1.get() == Some(removed) {
1709 self.old1.set(next);
1710 }
1711 if self.reallyold.get() == Some(removed) {
1712 self.reallyold.set(next);
1713 }
1714 if self.firstold1.get() == Some(removed) {
1715 self.firstold1.set(next);
1716 }
1717 if self.finobjsur.get() == Some(removed) {
1718 self.finobjsur.set(next);
1719 }
1720 if self.finobjold1.get() == Some(removed) {
1721 self.finobjold1.set(next);
1722 }
1723 if self.finobjrold.get() == Some(removed) {
1724 self.finobjrold.set(next);
1725 }
1726 if self.header_from_ptr(removed).gray_listed() {
1727 self.unlink_grayagain(removed);
1728 }
1729 }
1730
1731 fn unlink_from_list(
1732 &self,
1733 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1734 ptr: NonNull<GcBox<dyn Trace>>,
1735 ) -> bool {
1736 let mut prev_cell = list;
1737 loop {
1738 let Some(current) = prev_cell.get() else {
1739 return false;
1740 };
1741 let header = self.header_from_ptr(current);
1742 let next = header.next.get();
1743 if std::ptr::addr_eq(current.as_ptr(), ptr.as_ptr()) {
1744 prev_cell.set(next);
1745 let prev_next_ptr = NonNull::from(prev_cell);
1746 let removed_next_ptr = NonNull::from(&self.header_from_ptr(ptr).next);
1747 if self.sweep_prev_next.get() == Some(removed_next_ptr) {
1748 self.sweep_prev_next.set(Some(prev_next_ptr));
1749 }
1750 self.correct_generation_pointers(ptr, next);
1751 header.next.set(None);
1752 return true;
1753 }
1754 prev_cell = &header.next;
1755 }
1756 }
1757
1758 fn link_to_head(
1759 &self,
1760 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1761 ptr: NonNull<GcBox<dyn Trace>>,
1762 ) {
1763 let header = self.header_from_ptr(ptr);
1764 header.next.set(list.get());
1765 list.set(Some(ptr));
1766 }
1767
1768 fn link_to_tail(
1769 &self,
1770 list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
1771 ptr: NonNull<GcBox<dyn Trace>>,
1772 ) {
1773 let mut last_cell = list;
1774 loop {
1775 let Some(current) = last_cell.get() else {
1776 let header = self.header_from_ptr(ptr);
1777 header.next.set(None);
1778 last_cell.set(Some(ptr));
1779 return;
1780 };
1781 last_cell = &self.header_from_ptr(current).next;
1782 }
1783 }
1784
1785 pub fn move_allgc_to_finobj(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1786 let header = self.header_from_ptr(ptr);
1787 if !header.collected() {
1788 return false;
1789 }
1790 if !self.unlink_from_list(&self.head, ptr) {
1791 return false;
1792 }
1793 if self.state.get().is_sweep() {
1794 header.color.set(self.current_white());
1795 }
1796 self.link_to_head(&self.finobj, ptr);
1797 true
1798 }
1799
1800 pub fn move_finobj_to_tobefnz(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1801 if !self.unlink_from_list(&self.finobj, ptr) {
1802 return false;
1803 }
1804 self.link_to_tail(&self.tobefnz, ptr);
1805 true
1806 }
1807
1808 pub fn move_tobefnz_to_allgc(&self, ptr: NonNull<GcBox<dyn Trace>>) -> bool {
1809 let header = self.header_from_ptr(ptr);
1810 if !self.unlink_from_list(&self.tobefnz, ptr) {
1811 return false;
1812 }
1813 if self.state.get().is_sweep() {
1814 header.color.set(self.current_white());
1815 }
1816 self.link_to_head(&self.head, ptr);
1817 if header.age.get() == GcAge::Old1 {
1818 self.firstold1.set(Some(ptr));
1819 }
1820 true
1821 }
1822
1823 fn remember_minor_revisit(&self, ptr: NonNull<GcBox<dyn Trace>>) {
1824 let header = self.header_from_ptr(ptr);
1825 if header.gray_listed() {
1826 return;
1827 }
1828 header.gray_next.set(self.grayagain.get());
1829 header.set_gray_listed(true);
1830 self.grayagain.set(Some(ptr));
1831 }
1832
1833 fn mark_minor_revisit_objects(&self, marker: &mut Marker) {
1834 let mut cursor = self.grayagain.get();
1835 while let Some(ptr) = cursor {
1836 let header = self.header_from_ptr(ptr);
1837 cursor = header.gray_next.get();
1838 let id = ptr.as_ptr() as *const () as usize;
1839 marker.mark_box(ptr, header, id);
1840 }
1841 }
1842
1843 fn clear_grayagain(&self) {
1844 let mut cursor = self.grayagain.get();
1845 self.grayagain.set(None);
1846 while let Some(ptr) = cursor {
1847 let header = self.header_from_ptr(ptr);
1848 cursor = header.gray_next.get();
1849 header.gray_next.set(None);
1850 header.set_gray_listed(false);
1851 }
1852 }
1853
1854 fn take_grayagain(&self) -> Vec<NonNull<GcBox<dyn Trace>>> {
1855 let mut objects = Vec::new();
1856 let mut cursor = self.grayagain.get();
1857 self.grayagain.set(None);
1858 while let Some(ptr) = cursor {
1859 let header = self.header_from_ptr(ptr);
1860 cursor = header.gray_next.get();
1861 header.gray_next.set(None);
1862 header.set_gray_listed(false);
1863 objects.push(ptr);
1864 }
1865 objects
1866 }
1867
1868 fn replace_grayagain(&self, objects: Vec<NonNull<GcBox<dyn Trace>>>) {
1869 self.clear_grayagain();
1870 for ptr in objects.into_iter().rev() {
1871 self.remember_minor_revisit(ptr);
1872 }
1873 }
1874
1875 fn unlink_grayagain(&self, removed: NonNull<GcBox<dyn Trace>>) {
1876 let keep = self
1877 .take_grayagain()
1878 .into_iter()
1879 .filter(|ptr| !std::ptr::addr_eq(ptr.as_ptr(), removed.as_ptr()))
1880 .collect();
1881 self.replace_grayagain(keep);
1882 }
1883
1884 pub fn grayagain_count(&self) -> usize {
1885 let mut count = 0usize;
1886 let mut cursor = self.grayagain.get();
1887 while let Some(ptr) = cursor {
1888 count += 1;
1889 cursor = self.header_from_ptr(ptr).gray_next.get();
1890 }
1891 count
1892 }
1893
1894 pub fn allocation_token(&self, identity: usize) -> Option<usize> {
1902 self.allocation_tokens.borrow().get(&identity).copied()
1903 }
1904
1905 pub fn register_allocation_token(&self, identity: usize) -> usize {
1923 let mut tokens = self.allocation_tokens.borrow_mut();
1924 if let Some(token) = tokens.get(&identity) {
1925 return *token;
1926 }
1927 let token = self.next_token();
1928 tokens.insert(identity, token);
1929 token
1930 }
1931
1932 pub fn contains_allocation(&self, identity: usize, token: usize) -> bool {
1937 self.allocation_token(identity) == Some(token)
1938 }
1939
1940 pub fn barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1950 where
1951 P: Trace + 'static,
1952 C: Trace + 'static,
1953 {
1954 if self.paused.get() || self.state.get().is_pause() {
1955 return;
1956 }
1957 if parent.header().color.get() != Color::Black {
1958 return;
1959 }
1960 if !child.header().color.get().is_white() {
1961 return;
1962 }
1963 child.header().color.set(Color::Gray);
1964 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
1965 if let Some(m) = m_opt.as_mut() {
1966 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
1967 m.gray_queue.push(ptr);
1968 m.visited.insert(child.identity());
1969 }
1970 }
1971 }
1972
1973 pub fn barrier_back<P, C>(&self, parent: Gc<P>, child: Gc<C>)
1976 where
1977 P: Trace + 'static,
1978 C: Trace + 'static,
1979 {
1980 if self.paused.get() || self.state.get().is_pause() {
1981 return;
1982 }
1983 if parent.header().color.get() != Color::Black {
1984 return;
1985 }
1986 if !child.header().color.get().is_white() {
1987 return;
1988 }
1989 parent.header().color.set(Color::Gray);
1990 if let Ok(mut m_opt) = self.marker.try_borrow_mut() {
1991 if let Some(m) = m_opt.as_mut() {
1992 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
1993 m.gray_queue.push(ptr);
1994 m.visited.insert(parent.identity());
1995 }
1996 }
1997 }
1998
1999 pub fn generational_forward_barrier<P, C>(&self, parent: Gc<P>, child: Gc<C>)
2004 where
2005 P: Trace + 'static,
2006 C: Trace + 'static,
2007 {
2008 if parent.age().is_old() && !child.age().is_old() {
2009 child.set_age(GcAge::Old0);
2010 let ptr: NonNull<GcBox<dyn Trace>> = child.ptr;
2011 self.remember_minor_revisit(ptr);
2012 }
2013 self.barrier(parent, child);
2014 }
2015
2016 pub fn generational_backward_barrier<P>(&self, parent: Gc<P>)
2020 where
2021 P: Trace + 'static,
2022 {
2023 if parent.age().is_old() {
2024 parent.set_color(Color::Gray);
2025 parent.set_age(GcAge::Touched1);
2026 let ptr: NonNull<GcBox<dyn Trace>> = parent.ptr;
2027 self.remember_minor_revisit(ptr);
2028 }
2029 }
2030
2031 pub fn step(&self, roots: &dyn Trace) {
2035 self.step_with_post_mark(roots, |_: &mut Marker| {});
2036 }
2037
2038 pub fn step_with_post_mark<F: FnMut(&mut Marker)>(&self, roots: &dyn Trace, post_mark: F) {
2043 if self.paused.get() {
2044 return;
2045 }
2046 if !self.stress && self.bytes.get() < self.threshold.get() {
2047 return;
2048 }
2049 self.full_collect_with_post_mark(roots, post_mark);
2050 }
2051
2052 pub fn full_collect(&self, roots: &dyn Trace) {
2055 self.full_collect_with_post_mark(roots, |_: &mut Marker| {});
2056 }
2057
2058 pub fn mark_only_with_post_mark<F: FnMut(&mut Marker)>(
2063 &self,
2064 roots: &dyn Trace,
2065 mut post_mark: F,
2066 ) {
2067 if self.paused.get() {
2068 return;
2069 }
2070 let mut marker = self.marker_from_pool(MarkerMode::Full);
2071 roots.trace(&mut marker);
2072 marker.drain_gray_queue();
2073 post_mark(&mut marker);
2074 marker.drain_gray_queue();
2075 self.last_mark_stats.set(marker.stats());
2076 self.recycle_marker(marker);
2077 }
2078
2079 pub fn promote_all_to_old(&self) {
2082 self.for_each_header(|header| {
2083 header.age.set(GcAge::Old);
2084 header.color.set(Color::Black);
2085 });
2086 self.set_all_cursors_to_head();
2087 }
2088
2089 pub fn reset_all_ages(&self) {
2092 let current_white = self.current_white();
2093 self.for_each_header(|header| {
2094 header.age.set(GcAge::New);
2095 header.color.set(current_white);
2096 });
2097 self.clear_generation_cursors();
2098 }
2099
2100 pub fn minor_collect_with_post_mark<F: FnMut(&mut Marker)>(
2107 &self,
2108 roots: &dyn Trace,
2109 mut post_mark: F,
2110 ) {
2111 if self.paused.get() {
2112 return;
2113 }
2114 if !self.state.get().is_pause() {
2115 self.abort_cycle();
2116 }
2117
2118 self.state.set(GcState::Propagate);
2119 let mut marker = self.marker_from_pool(MarkerMode::Minor);
2120 self.last_sweep_stats.set(SweepStats::default());
2121 self.mark_minor_revisit_objects(&mut marker);
2122 roots.trace(&mut marker);
2123 marker.drain_gray_queue();
2124
2125 self.state.set(GcState::EnterAtomic);
2126 self.state.set(GcState::Atomic);
2127 post_mark(&mut marker);
2128 marker.drain_gray_queue();
2129 self.last_mark_stats.set(marker.stats());
2130 self.recycle_marker(marker);
2131
2132 self.state.set(GcState::SweepAllGc);
2133 self.sweep_young();
2134 self.recycle_marker_cell();
2135 self.sweep_prev_next.set(None);
2136 self.state.set(GcState::Pause);
2137 self.collections.set(self.collections.get() + 1);
2138 self.minor_collections.set(self.minor_collections.get() + 1);
2139 }
2140
2141 pub fn full_collect_with_post_mark<F: FnMut(&mut Marker)>(
2148 &self,
2149 roots: &dyn Trace,
2150 mut post_mark: F,
2151 ) {
2152 if self.paused.get() {
2153 return;
2154 }
2155 if !self.state.get().is_pause() {
2156 self.abort_cycle();
2157 }
2158 self.full_collections.set(self.full_collections.get() + 1);
2159 let unlimited = StepBudget {
2160 remaining_work: isize::MAX,
2161 max_credit: isize::MAX,
2162 };
2163 loop {
2164 let outcome = self.incremental_step_with_post_mark(roots, unlimited, &mut post_mark);
2165 if matches!(outcome, StepOutcome::Paused | StepOutcome::SkippedStopped) {
2166 break;
2167 }
2168 }
2169 }
2170
2171 pub fn incremental_step_with_post_mark<F: FnMut(&mut Marker)>(
2186 &self,
2187 roots: &dyn Trace,
2188 mut budget: StepBudget,
2189 mut post_mark: F,
2190 ) -> StepOutcome {
2191 if self.paused.get() {
2192 return StepOutcome::SkippedStopped;
2193 }
2194 self.run_budgeted(roots, &mut budget, &mut post_mark);
2195 if self.state.get().is_pause() {
2196 StepOutcome::Paused
2197 } else {
2198 StepOutcome::InProgress
2199 }
2200 }
2201
2202 fn run_budgeted(
2203 &self,
2204 roots: &dyn Trace,
2205 budget: &mut StepBudget,
2206 post_mark: &mut dyn FnMut(&mut Marker),
2207 ) -> bool {
2208 self.run_budgeted_until(roots, budget, post_mark, None)
2209 }
2210
2211 fn run_budgeted_until(
2212 &self,
2213 roots: &dyn Trace,
2214 budget: &mut StepBudget,
2215 post_mark: &mut dyn FnMut(&mut Marker),
2216 stop_at: Option<GcState>,
2217 ) -> bool {
2218 let mut did_work = false;
2219 loop {
2220 if stop_at == Some(self.state.get()) {
2221 return did_work;
2222 }
2223 if budget.remaining_work <= -budget.max_credit {
2224 return did_work;
2225 }
2226 match self.state.get() {
2227 GcState::Pause => {
2228 self.start_cycle(roots);
2229 self.state.set(GcState::Propagate);
2230 budget.remaining_work -= 1;
2231 did_work = true;
2232 if stop_at == Some(GcState::Propagate) {
2233 return did_work;
2234 }
2235 }
2236 GcState::Propagate => {
2237 let work = self.drain_gray_budgeted(budget.remaining_work.max(1));
2238 budget.remaining_work -= work as isize;
2239 did_work = did_work || work > 0;
2240 let empty = {
2241 let m = self.marker.borrow();
2242 m.as_ref().map(|m| m.gray_queue.is_empty()).unwrap_or(true)
2243 };
2244 if empty {
2245 self.state.set(GcState::EnterAtomic);
2246 if stop_at == Some(GcState::EnterAtomic) {
2247 return did_work;
2248 }
2249 } else if budget.remaining_work <= 0 {
2250 return did_work;
2251 }
2252 }
2253 GcState::EnterAtomic => {
2254 self.state.set(GcState::Atomic);
2255 budget.remaining_work -= 1;
2256 did_work = true;
2257 if stop_at == Some(GcState::Atomic) || budget.remaining_work <= 0 {
2258 return did_work;
2259 }
2260 }
2261 GcState::Atomic => {
2262 self.run_atomic(post_mark);
2263 self.state.set(GcState::SweepAllGc);
2264 budget.remaining_work -= 1;
2265 did_work = true;
2266 if stop_at == Some(GcState::SweepAllGc) {
2267 return did_work;
2268 }
2269 }
2270 GcState::SweepAllGc => {
2271 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2272 budget.remaining_work -= work as isize;
2273 did_work = did_work || work > 0;
2274 if self.sweep_prev_next.get().is_none() {
2275 self.state.set(GcState::SweepFinObj);
2276 self.sweep_prev_next.set(Some(NonNull::from(&self.finobj)));
2277 if stop_at == Some(GcState::SweepFinObj) {
2278 return did_work;
2279 }
2280 } else if budget.remaining_work <= 0 {
2281 return did_work;
2282 }
2283 }
2284 GcState::SweepFinObj => {
2285 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2286 budget.remaining_work -= work as isize;
2287 did_work = did_work || work > 0;
2288 if self.sweep_prev_next.get().is_none() {
2289 self.state.set(GcState::SweepToBeFnz);
2290 self.sweep_prev_next.set(Some(NonNull::from(&self.tobefnz)));
2291 if stop_at == Some(GcState::SweepToBeFnz) {
2292 return did_work;
2293 }
2294 } else if budget.remaining_work <= 0 {
2295 return did_work;
2296 }
2297 }
2298 GcState::SweepToBeFnz => {
2299 let work = self.sweep_budgeted(budget.remaining_work.max(1));
2300 budget.remaining_work -= work as isize;
2301 did_work = did_work || work > 0;
2302 if self.sweep_prev_next.get().is_none() {
2303 self.state.set(GcState::SweepEnd);
2304 if stop_at == Some(GcState::SweepEnd) {
2305 return did_work;
2306 }
2307 } else if budget.remaining_work <= 0 {
2308 return did_work;
2309 }
2310 }
2311 GcState::SweepEnd => {
2312 self.state.set(GcState::CallFin);
2313 budget.remaining_work -= 1;
2314 did_work = true;
2315 if stop_at == Some(GcState::CallFin) || budget.remaining_work <= 0 {
2316 return did_work;
2317 }
2318 }
2319 GcState::CallFin => {
2320 self.finish_cycle();
2321 self.state.set(GcState::Pause);
2322 if stop_at == Some(GcState::Pause) {
2323 return did_work;
2324 }
2325 return did_work;
2326 }
2327 }
2328 }
2329 }
2330
2331 pub fn incremental_run_until_state_with_post_mark<F: FnMut(&mut Marker)>(
2336 &self,
2337 roots: &dyn Trace,
2338 target: GcState,
2339 max_work: isize,
2340 mut post_mark: F,
2341 ) -> StepOutcome {
2342 if self.paused.get() {
2343 return StepOutcome::SkippedStopped;
2344 }
2345 let work = max_work.max(1);
2346 let mut budget = StepBudget {
2347 remaining_work: work,
2348 max_credit: work,
2349 };
2350 self.run_budgeted_until(roots, &mut budget, &mut post_mark, Some(target));
2351 if self.state.get().is_pause() {
2352 StepOutcome::Paused
2353 } else {
2354 StepOutcome::InProgress
2355 }
2356 }
2357
2358 fn marker_from_pool(&self, mode: MarkerMode) -> Marker {
2361 match self.marker_pool.borrow_mut().take() {
2362 Some((gray_queue, visited)) => Marker {
2363 gray_queue,
2364 visited,
2365 stats: MarkerStats::default(),
2366 mode,
2367 },
2368 None => Marker::new_with_capacity(mode, self.objects.get()),
2369 }
2370 }
2371
2372 fn recycle_marker(&self, marker: Marker) {
2373 let Marker {
2374 mut gray_queue,
2375 mut visited,
2376 ..
2377 } = marker;
2378 gray_queue.clear();
2379 visited.clear();
2380 *self.marker_pool.borrow_mut() = Some((gray_queue, visited));
2381 }
2382
2383 fn recycle_marker_cell(&self) {
2384 if let Some(marker) = self.marker.borrow_mut().take() {
2385 self.recycle_marker(marker);
2386 }
2387 }
2388
2389 fn start_cycle(&self, roots: &dyn Trace) {
2390 self.flip_current_white();
2391 let dead_white = self.other_white();
2392 self.for_each_header(|header| {
2393 header.color.set(dead_white);
2394 });
2395 let mut marker = self.marker_from_pool(MarkerMode::Full);
2396 roots.trace(&mut marker);
2397 *self.marker.borrow_mut() = Some(marker);
2398 self.sweep_prev_next.set(None);
2399 }
2400
2401 fn drain_gray_budgeted(&self, max_units: isize) -> usize {
2402 let mut m_opt = self.marker.borrow_mut();
2403 let marker = match m_opt.as_mut() {
2404 Some(m) => m,
2405 None => return 0,
2406 };
2407 let mut work = 0usize;
2408 let mut budget = max_units;
2409 while budget > 0 {
2410 let next = match marker.gray_queue.pop() {
2411 Some(p) => p,
2412 None => break,
2413 };
2414 unsafe {
2415 let bx = next.as_ref();
2416 marker.stats.traced += 1;
2417 if bx.header.age.get().is_old() {
2418 marker.stats.traced_old += 1;
2419 } else {
2420 marker.stats.traced_young += 1;
2421 }
2422 bx.header.color.set(Color::Black);
2423 bx.value.trace(marker);
2424 }
2425 work += 1;
2426 budget -= 1;
2427 }
2428 work
2429 }
2430
2431 fn run_atomic(&self, post_mark: &mut dyn FnMut(&mut Marker)) {
2432 let mut m_opt = self.marker.borrow_mut();
2433 if let Some(marker) = m_opt.as_mut() {
2434 marker.drain_gray_queue();
2435 post_mark(marker);
2436 marker.drain_gray_queue();
2437 }
2438 self.sweep_prev_next.set(Some(NonNull::from(&self.head)));
2439 self.last_sweep_stats.set(SweepStats::default());
2440 }
2441
2442 fn sweep_budgeted(&self, max_units: isize) -> usize {
2443 let mut work = 0usize;
2444 let mut budget = max_units;
2445 let mut freed_bytes = 0usize;
2446 let mut stats = SweepStats::default();
2447 let current_white = self.current_white();
2448 let dead_white = self.other_white();
2449 let mut prev_next_ptr = match self.sweep_prev_next.get() {
2450 Some(p) => p,
2451 None => return 0,
2452 };
2453 while budget > 0 {
2454 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2455 let cursor = prev_cell.get();
2456 let ptr = match cursor {
2457 Some(p) => p,
2458 None => {
2459 self.sweep_prev_next.set(None);
2460 break;
2461 }
2462 };
2463 let header = self.header_from_ptr(ptr);
2464 let next = header.next.get();
2465 let age = header.age.get();
2466 stats.record_visit(age);
2467 let color = header.color.get();
2468 if color == dead_white {
2469 prev_cell.set(next);
2470 let size = header.size();
2471 freed_bytes += size;
2472 stats.record_free(size);
2473 self.correct_generation_pointers(ptr, next);
2474 self.allocation_tokens
2475 .borrow_mut()
2476 .remove(&(ptr.as_ptr() as *const () as usize));
2477 self.objects.set(self.objects.get().saturating_sub(1));
2478 self.release_box(ptr);
2479 } else {
2480 if matches!(color, Color::Black | Color::Gray) {
2481 header.color.set(current_white);
2482 }
2483 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2484 self.sweep_prev_next.set(Some(prev_next_ptr));
2485 }
2486 work += 1;
2487 budget -= 1;
2488 }
2489 if freed_bytes > 0 {
2490 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2491 }
2492 if stats.visited > 0 {
2493 let mut total = self.last_sweep_stats.get();
2494 total.add(stats);
2495 self.last_sweep_stats.set(total);
2496 }
2497 work
2498 }
2499
2500 fn push_next_revisit(
2501 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2502 seen: &mut IdentityHashSet,
2503 ptr: NonNull<GcBox<dyn Trace>>,
2504 age: GcAge,
2505 ) {
2506 if matches!(
2507 age,
2508 GcAge::Old0 | GcAge::Old1 | GcAge::Touched1 | GcAge::Touched2
2509 ) {
2510 let id = ptr.as_ptr() as *const () as usize;
2511 if seen.insert(id) {
2512 next_revisit.push(ptr);
2513 }
2514 }
2515 }
2516
2517 fn sweep_young_range(
2518 &self,
2519 mut prev_next_ptr: NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2520 limit: Option<NonNull<GcBox<dyn Trace>>>,
2521 next_revisit: &mut Vec<NonNull<GcBox<dyn Trace>>>,
2522 next_revisit_seen: &mut IdentityHashSet,
2523 processed: &mut Option<OldRevisitTracker>,
2524 firstold1: &mut Option<NonNull<GcBox<dyn Trace>>>,
2525 freed_bytes: &mut usize,
2526 stats: &mut SweepStats,
2527 ) -> (
2528 NonNull<Cell<Option<NonNull<GcBox<dyn Trace>>>>>,
2529 Option<NonNull<GcBox<dyn Trace>>>,
2530 ) {
2531 let current_white = self.current_white();
2532 loop {
2533 let prev_cell = unsafe { prev_next_ptr.as_ref() };
2534 let Some(ptr) = prev_cell.get() else {
2535 return (prev_next_ptr, None);
2536 };
2537 if Some(ptr) == limit {
2538 return (prev_next_ptr, Some(ptr));
2539 }
2540 let header = self.header_from_ptr(ptr);
2541 let next = header.next.get();
2542 let age = header.age.get();
2543 stats.record_visit(age);
2544 if let Some(processed) = processed.as_mut() {
2545 processed.record_processed(ptr.as_ptr() as *const () as usize);
2546 }
2547 if header.color.get().is_white() && !age.is_old() {
2548 prev_cell.set(next);
2549 let size = header.size();
2550 *freed_bytes += size;
2551 stats.record_free(size);
2552 self.correct_generation_pointers(ptr, next);
2553 self.allocation_tokens
2554 .borrow_mut()
2555 .remove(&(ptr.as_ptr() as *const () as usize));
2556 self.objects.set(self.objects.get().saturating_sub(1));
2557 self.release_box(ptr);
2558 continue;
2559 }
2560
2561 if !header.color.get().is_white() {
2562 let next_age = age.next_after_minor();
2563 header.age.set(next_age);
2564 if next_age == GcAge::Old1 && firstold1.is_none() {
2565 *firstold1 = Some(ptr);
2566 }
2567 match age {
2568 GcAge::New => header.color.set(current_white),
2569 GcAge::Touched1 | GcAge::Touched2 => header.color.set(Color::Black),
2570 _ => {}
2571 }
2572 Self::push_next_revisit(next_revisit, next_revisit_seen, ptr, next_age);
2573 }
2574 prev_next_ptr = unsafe { NonNull::from(&(*ptr.as_ptr()).header.next) };
2575 }
2576 }
2577
2578 fn sweep_young(&self) {
2579 let mut freed_bytes = 0usize;
2580 let mut next_revisit = Vec::new();
2581 let mut next_revisit_seen = IdentityHashSet::default();
2582 let mut firstold1 = None;
2583 let mut stats = SweepStats::default();
2584 let old_revisit = self.take_grayagain();
2585 let mut processed = OldRevisitTracker::new(&old_revisit);
2586 let survival = self.survival.get();
2587 let old1 = self.old1.get();
2588
2589 let (psurvival, new_old1) = self.sweep_young_range(
2590 NonNull::from(&self.head),
2591 survival,
2592 &mut next_revisit,
2593 &mut next_revisit_seen,
2594 &mut processed,
2595 &mut firstold1,
2596 &mut freed_bytes,
2597 &mut stats,
2598 );
2599 self.sweep_young_range(
2600 psurvival,
2601 old1,
2602 &mut next_revisit,
2603 &mut next_revisit_seen,
2604 &mut processed,
2605 &mut firstold1,
2606 &mut freed_bytes,
2607 &mut stats,
2608 );
2609
2610 let finobjsur = self.finobjsur.get();
2611 let finobjold1 = self.finobjold1.get();
2612 let mut dummy_firstold1 = None;
2613 let (pfinobjsur, new_finobjold1) = self.sweep_young_range(
2614 NonNull::from(&self.finobj),
2615 finobjsur,
2616 &mut next_revisit,
2617 &mut next_revisit_seen,
2618 &mut processed,
2619 &mut dummy_firstold1,
2620 &mut freed_bytes,
2621 &mut stats,
2622 );
2623 self.sweep_young_range(
2624 pfinobjsur,
2625 finobjold1,
2626 &mut next_revisit,
2627 &mut next_revisit_seen,
2628 &mut processed,
2629 &mut dummy_firstold1,
2630 &mut freed_bytes,
2631 &mut stats,
2632 );
2633 self.sweep_young_range(
2634 NonNull::from(&self.tobefnz),
2635 None,
2636 &mut next_revisit,
2637 &mut next_revisit_seen,
2638 &mut processed,
2639 &mut dummy_firstold1,
2640 &mut freed_bytes,
2641 &mut stats,
2642 );
2643
2644 if let Some(processed) = processed.as_mut() {
2645 processed.finish();
2646 }
2647
2648 for ptr in old_revisit {
2649 let id = ptr.as_ptr() as *const () as usize;
2650 if processed
2651 .as_ref()
2652 .is_some_and(|processed| processed.was_processed(id))
2653 {
2654 continue;
2655 }
2656 stats.revisit += 1;
2657 let header = self.header_from_ptr(ptr);
2658 if header.color.get().is_white() {
2659 continue;
2660 }
2661 let age = header.age.get();
2662 let next_age = age.next_after_minor();
2663 header.age.set(next_age);
2664 if next_age == GcAge::Old1 && firstold1.is_none() {
2665 firstold1 = Some(ptr);
2666 }
2667 if matches!(age, GcAge::Touched1 | GcAge::Touched2) {
2668 header.color.set(Color::Black);
2669 }
2670 Self::push_next_revisit(&mut next_revisit, &mut next_revisit_seen, ptr, next_age);
2671 }
2672
2673 if freed_bytes > 0 {
2674 self.bytes.set(self.bytes.get().saturating_sub(freed_bytes));
2675 }
2676 self.replace_grayagain(next_revisit);
2677 self.reallyold.set(old1);
2678 self.old1.set(new_old1);
2679 self.survival.set(self.head.get());
2680 self.firstold1.set(firstold1);
2681 self.finobjrold.set(finobjold1);
2682 self.finobjold1.set(new_finobjold1);
2683 self.finobjsur.set(self.finobj.get());
2684 self.last_sweep_stats.set(stats);
2685 }
2686
2687 fn finish_cycle(&self) {
2688 let stats = self
2689 .marker
2690 .borrow()
2691 .as_ref()
2692 .map(|marker| marker.stats())
2693 .unwrap_or_default();
2694 self.last_mark_stats.set(stats);
2695 self.recycle_marker_cell();
2696 self.sweep_prev_next.set(None);
2697 let next = self.bytes.get().saturating_mul(self.pause_multiplier.get()) / 100;
2698 self.threshold.set(next.max(GC_MIN_THRESHOLD));
2699 self.collections.set(self.collections.get() + 1);
2700 }
2701
2702 pub fn finish_callfin_phase(&self) -> bool {
2705 if self.state.get() != GcState::CallFin {
2706 return false;
2707 }
2708 self.finish_cycle();
2709 self.state.set(GcState::Pause);
2710 true
2711 }
2712
2713 fn abort_cycle(&self) {
2714 if !self.state.get().is_pause() {
2715 self.recycle_marker_cell();
2716 self.sweep_prev_next.set(None);
2717 let current_white = self.current_white();
2718 self.for_each_header(|header| {
2719 header.color.set(current_white);
2720 });
2721 self.state.set(GcState::Pause);
2722 }
2723 }
2724
2725 pub fn gc_state(&self) -> GcState {
2727 self.state.get()
2728 }
2729
2730 pub fn allgc_count(&self) -> usize {
2732 self.objects.get()
2733 }
2734
2735 pub fn type_name_count(&self, mut predicate: impl FnMut(&'static str) -> bool) -> usize {
2739 let mut count = 0usize;
2740 for head in [self.head.get(), self.finobj.get(), self.tobefnz.get()] {
2741 let mut cursor = head;
2742 while let Some(ptr) = cursor {
2743 let bx = unsafe { ptr.as_ref() };
2744 cursor = bx.header.next.get();
2745 if predicate(bx.value().type_name()) {
2746 count += 1;
2747 }
2748 }
2749 }
2750 count
2751 }
2752
2753 pub fn drop_all(&self) {
2757 self.recycle_marker_cell();
2758 self.sweep_prev_next.set(None);
2759 self.clear_generation_cursors();
2760 self.state.set(GcState::Pause);
2761 self.allocation_tokens.borrow_mut().clear();
2762 self.drop_list(&self.head);
2763 self.drop_list(&self.finobj);
2764 self.drop_list(&self.tobefnz);
2765 self.drop_list(&self.quarantined);
2766 self.bytes.set(0);
2767 self.objects.set(0);
2768 }
2769
2770 fn drop_list(&self, list: &Cell<Option<NonNull<GcBox<dyn Trace>>>>) {
2771 let mut cursor = list.get();
2772 list.set(None);
2773 while let Some(ptr) = cursor {
2774 let next = unsafe {
2776 let next = (*ptr.as_ptr()).header.next.get();
2777 let _ = Box::from_raw(ptr.as_ptr());
2778 next
2779 };
2780 cursor = next;
2781 }
2782 }
2783}
2784
2785impl Drop for Heap {
2786 fn drop(&mut self) {
2787 self.drop_all();
2788 }
2789}
2790
2791#[cfg(test)]
2796mod tests {
2797 use super::*;
2798
2799 struct Cell0 {
2801 next: Cell<Option<Gc<Cell0>>>,
2802 marker_calls: Cell<usize>,
2803 }
2804
2805 impl Trace for Cell0 {
2806 fn trace(&self, m: &mut Marker) {
2807 self.marker_calls.set(self.marker_calls.get() + 1);
2808 if let Some(n) = self.next.get() {
2809 m.mark(n);
2810 }
2811 }
2812 }
2813
2814 struct OneRoot(Option<Gc<Cell0>>);
2816 impl Trace for OneRoot {
2817 fn trace(&self, m: &mut Marker) {
2818 if let Some(g) = self.0 {
2819 m.mark(g);
2820 }
2821 }
2822 }
2823
2824 struct TwoRoots {
2825 first: Option<Gc<Cell0>>,
2826 second: Option<Gc<Cell0>>,
2827 }
2828
2829 impl Trace for TwoRoots {
2830 fn trace(&self, m: &mut Marker) {
2831 if let Some(g) = self.first {
2832 m.mark(g);
2833 }
2834 if let Some(g) = self.second {
2835 m.mark(g);
2836 }
2837 }
2838 }
2839
2840 #[derive(Clone)]
2841 struct FinalizerCell {
2842 id: usize,
2843 age: GcAge,
2844 finalized: std::rc::Rc<Cell<bool>>,
2845 }
2846
2847 impl FinalizerCell {
2848 fn new(id: usize) -> Self {
2849 Self {
2850 id,
2851 age: GcAge::New,
2852 finalized: std::rc::Rc::new(Cell::new(false)),
2853 }
2854 }
2855 }
2856
2857 impl FinalizerEntry for FinalizerCell {
2858 fn identity(&self) -> usize {
2859 self.id
2860 }
2861
2862 fn age(&self) -> GcAge {
2863 self.age
2864 }
2865
2866 fn is_finalized(&self) -> bool {
2867 self.finalized.get()
2868 }
2869
2870 fn set_finalized(&self, finalized: bool) {
2871 self.finalized.set(finalized);
2872 }
2873 }
2874
2875 fn finalizer_ids(objects: &[FinalizerCell]) -> Vec<usize> {
2876 objects.iter().map(|object| object.id).collect()
2877 }
2878
2879 #[derive(Clone)]
2880 struct WeakCell {
2881 id: usize,
2882 live: bool,
2883 kind: WeakListKind,
2884 }
2885
2886 impl WeakEntry for WeakCell {
2887 type Strong = usize;
2888
2889 fn identity(&self) -> usize {
2890 self.id
2891 }
2892
2893 fn list_kind(&self) -> WeakListKind {
2894 self.kind
2895 }
2896
2897 fn upgrade(&self) -> Option<Self::Strong> {
2898 self.live.then_some(self.id)
2899 }
2900 }
2901
2902 #[test]
2903 fn weak_registry_dedups_snapshots_and_retains_live_ids() {
2904 let mut registry = WeakRegistry::default();
2905 registry.push_unique(WeakCell {
2906 id: 1,
2907 live: true,
2908 kind: WeakListKind::WeakValues,
2909 });
2910 registry.push_unique(WeakCell {
2911 id: 1,
2912 live: true,
2913 kind: WeakListKind::Ephemeron,
2914 });
2915 registry.push_unique(WeakCell {
2916 id: 2,
2917 live: false,
2918 kind: WeakListKind::AllWeak,
2919 });
2920 registry.push_unique(WeakCell {
2921 id: 3,
2922 live: true,
2923 kind: WeakListKind::WeakValues,
2924 });
2925
2926 let stats = registry.stats();
2927 assert_eq!(stats.weak_values, 1);
2928 assert_eq!(stats.ephemeron, 1);
2929 assert_eq!(stats.all_weak, 1);
2930
2931 let snapshot = registry.live_snapshot();
2932 assert_eq!(snapshot, vec![3, 1]);
2933 assert_eq!(
2934 registry.stats(),
2935 WeakRegistryStats {
2936 tracked: 3,
2937 snapshot_live: 2,
2938 snapshot_dead: 1,
2939 retained: 2,
2940 weak_values: 1,
2941 ephemeron: 1,
2942 all_weak: 0,
2943 }
2944 );
2945
2946 let keep: std::collections::HashSet<usize> = [3usize].into_iter().collect();
2947 registry.retain_identities(&keep);
2948 assert_eq!(registry.len(), 1);
2949 assert_eq!(registry.stats().retained, 1);
2950 assert_eq!(registry.stats().weak_values, 1);
2951 registry.remove_identity(3);
2952 assert_eq!(registry.len(), 0);
2953 }
2954
2955 #[test]
2956 fn finalizer_registry_tracks_generational_cohorts() {
2957 let mut registry = FinalizerRegistry::default();
2958 registry.push_pending_unique(FinalizerCell::new(1));
2959 registry.push_pending_unique(FinalizerCell::new(2));
2960
2961 let stats = registry.stats();
2962 assert_eq!(stats.finobj_new, 2);
2963 assert_eq!(stats.finobj_survival, 0);
2964 assert_eq!(stats.finobj_old1, 0);
2965 assert_eq!(stats.finobj_reallyold, 0);
2966 assert_eq!(stats.finobj_minor_scan, 2);
2967
2968 registry.finish_minor_collection();
2969 let stats = registry.stats();
2970 assert_eq!(stats.finobj_new, 0);
2971 assert_eq!(stats.finobj_survival, 2);
2972 assert_eq!(stats.finobj_old1, 0);
2973 assert_eq!(stats.finobj_reallyold, 0);
2974 assert_eq!(stats.finobj_minor_scan, 2);
2975
2976 registry.push_pending_unique(FinalizerCell::new(3));
2977 registry.finish_minor_collection();
2978 let stats = registry.stats();
2979 assert_eq!(stats.finobj_new, 0);
2980 assert_eq!(stats.finobj_survival, 1);
2981 assert_eq!(stats.finobj_old1, 2);
2982 assert_eq!(stats.finobj_reallyold, 0);
2983 assert_eq!(stats.finobj_minor_scan, 1);
2984
2985 registry.finish_minor_collection();
2986 let stats = registry.stats();
2987 assert_eq!(stats.finobj_new, 0);
2988 assert_eq!(stats.finobj_survival, 0);
2989 assert_eq!(stats.finobj_old1, 1);
2990 assert_eq!(stats.finobj_reallyold, 2);
2991 assert_eq!(stats.finobj_minor_scan, 0);
2992 }
2993
2994 #[test]
2995 fn finalizer_registry_minor_snapshot_uses_cohort_boundaries() {
2996 let mut registry = FinalizerRegistry::default();
2997 registry.push_pending_unique(FinalizerCell::new(1));
2998 registry.push_pending_unique(FinalizerCell::new(2));
2999 registry.push_pending_unique(FinalizerCell::new(3));
3000 registry.finish_minor_collection();
3001 registry.finish_minor_collection();
3002 registry.push_pending_unique(FinalizerCell::new(4));
3003 registry.push_pending_unique(FinalizerCell::new(5));
3004
3005 assert_eq!(
3006 finalizer_ids(®istry.pending_minor_snapshot()),
3007 vec![4, 5],
3008 "minor finalizer scan must skip the old1/reallyold prefix"
3009 );
3010
3011 registry.push_to_be_finalized(FinalizerCell::new(99));
3012 registry.promote_pending_to_finalized(vec![
3013 FinalizerCell::new(1),
3014 FinalizerCell::new(2),
3015 FinalizerCell::new(4),
3016 ]);
3017
3018 let stats = registry.stats();
3019 assert_eq!(stats.finobj_old1, 1);
3020 assert_eq!(stats.finobj_new, 1);
3021 assert_eq!(stats.finobj_minor_scan, 1);
3022 assert_eq!(finalizer_ids(registry.pending()), vec![3, 5]);
3023 assert_eq!(
3024 finalizer_ids(registry.to_be_finalized()),
3025 vec![99, 4, 2, 1],
3026 "new to-be-finalized batches append behind older queued finalizers"
3027 );
3028 }
3029
3030 #[test]
3031 fn finalizer_registry_marks_and_clears_finalized_bit() {
3032 let mut registry = FinalizerRegistry::default();
3033 let object = FinalizerCell::new(1);
3034
3035 assert!(!object.is_finalized());
3036 registry.push_pending_unique(object.clone());
3037 assert!(object.is_finalized());
3038
3039 registry.push_pending_unique(object.clone());
3040 assert_eq!(registry.pending_len(), 1);
3041
3042 registry.promote_pending_to_finalized(vec![object.clone()]);
3043 assert!(object.is_finalized());
3044 assert_eq!(registry.pending_len(), 0);
3045 assert_eq!(registry.to_be_finalized_len(), 1);
3046
3047 let popped = registry.pop_to_be_finalized().unwrap();
3048 assert_eq!(popped.id, 1);
3049 assert!(!object.is_finalized());
3050
3051 registry.push_pending_unique(object.clone());
3052 assert!(object.is_finalized());
3053 assert_eq!(registry.pending_len(), 1);
3054 }
3055
3056 #[test]
3057 fn alloc_and_drop_all() {
3058 let heap = Heap::new();
3059 heap.unpause();
3060 let _a = heap.allocate(Cell0 {
3061 next: Cell::new(None),
3062 marker_calls: Cell::new(0),
3063 });
3064 let _b = heap.allocate(Cell0 {
3065 next: Cell::new(None),
3066 marker_calls: Cell::new(0),
3067 });
3068 assert!(heap.bytes_used() > 0);
3069 heap.drop_all();
3070 assert_eq!(heap.bytes_used(), 0);
3071 }
3072
3073 fn list_len(heap: &Heap, mut cursor: Option<NonNull<GcBox<dyn Trace>>>) -> usize {
3074 let mut count = 0usize;
3075 while let Some(ptr) = cursor {
3076 count += 1;
3077 cursor = heap.header_from_ptr(ptr).next.get();
3078 }
3079 count
3080 }
3081
3082 #[test]
3083 fn finalizer_intrusive_lists_sweep_and_drop() {
3084 let heap = Heap::new();
3085 heap.unpause();
3086 let _normal = heap.allocate(Cell0 {
3087 next: Cell::new(None),
3088 marker_calls: Cell::new(0),
3089 });
3090 let finobj = heap.allocate(Cell0 {
3091 next: Cell::new(None),
3092 marker_calls: Cell::new(0),
3093 });
3094 let tobefnz = heap.allocate(Cell0 {
3095 next: Cell::new(None),
3096 marker_calls: Cell::new(0),
3097 });
3098
3099 assert!(heap.move_allgc_to_finobj(finobj.as_trace_ptr()));
3100 assert!(heap.move_allgc_to_finobj(tobefnz.as_trace_ptr()));
3101 assert!(heap.move_finobj_to_tobefnz(tobefnz.as_trace_ptr()));
3102 assert_eq!(list_len(&heap, heap.head.get()), 1);
3103 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3104 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3105 assert_eq!(heap.allgc_count(), 3);
3106
3107 heap.full_collect(&TwoRoots {
3108 first: Some(finobj),
3109 second: Some(tobefnz),
3110 });
3111 assert_eq!(list_len(&heap, heap.head.get()), 0);
3112 assert_eq!(list_len(&heap, heap.finobj.get()), 1);
3113 assert_eq!(list_len(&heap, heap.tobefnz.get()), 1);
3114 assert_eq!(heap.allgc_count(), 2);
3115
3116 assert!(heap.move_tobefnz_to_allgc(tobefnz.as_trace_ptr()));
3117 heap.full_collect(&OneRoot(Some(tobefnz)));
3118 assert_eq!(list_len(&heap, heap.head.get()), 1);
3119 assert_eq!(list_len(&heap, heap.finobj.get()), 0);
3120 assert_eq!(list_len(&heap, heap.tobefnz.get()), 0);
3121 assert_eq!(heap.allgc_count(), 1);
3122
3123 heap.drop_all();
3124 assert_eq!(heap.bytes_used(), 0);
3125 }
3126
3127 #[test]
3128 fn account_buffer_refunded_on_sweep() {
3129 let heap = Heap::new();
3130 heap.unpause();
3131 let baseline = heap.bytes_used();
3132 {
3133 let a = heap.allocate(Cell0 {
3134 next: Cell::new(None),
3135 marker_calls: Cell::new(0),
3136 });
3137 assert!(heap.bytes_used() > baseline);
3138 a.account_buffer(&heap, 4096);
3139 assert_eq!(
3140 a.header().size(),
3141 std::mem::size_of::<GcBox<Cell0>>() + 4096
3142 );
3143 }
3144 heap.full_collect(&OneRoot(None));
3147 assert_eq!(
3148 heap.bytes_used(),
3149 baseline,
3150 "account_buffer charge must be fully refunded by sweep"
3151 );
3152 }
3153
3154 #[test]
3155 fn account_buffer_noop_on_uncollected() {
3156 let heap = Heap::new();
3157 heap.unpause();
3158 let g = Gc::new_uncollected(Cell0 {
3159 next: Cell::new(None),
3160 marker_calls: Cell::new(0),
3161 });
3162 let before = heap.bytes_used();
3163 g.account_buffer(&heap, 8192);
3164 assert_eq!(
3165 heap.bytes_used(),
3166 before,
3167 "uncollected box must not charge the pacer"
3168 );
3169 }
3170
3171 #[test]
3172 fn collect_unreachable_frees_bytes() {
3173 let heap = Heap::new();
3174 heap.unpause();
3175 let _a = heap.allocate(Cell0 {
3176 next: Cell::new(None),
3177 marker_calls: Cell::new(0),
3178 });
3179 let bytes_before = heap.bytes_used();
3180 assert!(bytes_before > 0);
3181 heap.full_collect(&OneRoot(None));
3183 assert_eq!(heap.bytes_used(), 0);
3184 assert_eq!(heap.collections(), 1);
3185 }
3186
3187 #[test]
3188 fn collect_keeps_reachable() {
3189 let heap = Heap::new();
3190 heap.unpause();
3191 let root = heap.allocate(Cell0 {
3192 next: Cell::new(None),
3193 marker_calls: Cell::new(0),
3194 });
3195 let bytes_before = heap.bytes_used();
3196 heap.full_collect(&OneRoot(Some(root)));
3197 assert_eq!(heap.bytes_used(), bytes_before);
3198 assert_eq!(root.marker_calls.get(), 1);
3199 }
3200
3201 #[test]
3202 fn allocations_start_new_and_white() {
3203 let heap = Heap::new();
3204 heap.unpause();
3205 let obj = heap.allocate(Cell0 {
3206 next: Cell::new(None),
3207 marker_calls: Cell::new(0),
3208 });
3209 assert_eq!(obj.age(), GcAge::New);
3210 assert!(obj.color().is_white());
3211 }
3212
3213 #[test]
3214 fn allocation_tokens_track_exact_live_box() {
3215 let heap = Heap::new();
3216 heap.unpause();
3217 let obj = heap.allocate(Cell0 {
3218 next: Cell::new(None),
3219 marker_calls: Cell::new(0),
3220 });
3221 let id = obj.identity();
3222
3223 assert_eq!(
3224 heap.allocation_token(id),
3225 None,
3226 "lazy registration: a freshly allocated box has no token until it is downgraded"
3227 );
3228
3229 let token = heap.register_allocation_token(id);
3230 assert_eq!(
3231 heap.register_allocation_token(id),
3232 token,
3233 "registration is get-or-insert: re-registering a live identity returns the same token"
3234 );
3235 assert_eq!(heap.allocation_token(id), Some(token));
3236 assert!(heap.contains_allocation(id, token));
3237 assert!(!heap.contains_allocation(id, token + 1));
3238
3239 heap.full_collect(&OneRoot(None));
3240 assert_eq!(heap.allocation_token(id), None);
3241 assert!(!heap.contains_allocation(id, token));
3242 }
3243
3244 #[test]
3245 fn registering_after_sweep_yields_a_distinct_token_on_address_reuse() {
3246 let heap = Heap::new();
3247 heap.unpause();
3248 let first = heap.allocate(Cell0 {
3249 next: Cell::new(None),
3250 marker_calls: Cell::new(0),
3251 });
3252 let id = first.identity();
3253 let first_token = heap.register_allocation_token(id);
3254
3255 heap.full_collect(&OneRoot(None));
3256 assert_eq!(
3257 heap.allocation_token(id),
3258 None,
3259 "sweep removes the token for the dead identity"
3260 );
3261
3262 let second_token = heap.register_allocation_token(id);
3263 assert_ne!(
3264 first_token, second_token,
3265 "monotonic tokens keep address reuse from resurrecting a stale weak handle"
3266 );
3267 assert!(!heap.contains_allocation(id, first_token));
3268 assert!(heap.contains_allocation(id, second_token));
3269 }
3270
3271 #[test]
3272 fn allocation_during_incremental_sweep_survives_current_cycle() {
3273 let heap = Heap::new();
3274 heap.unpause();
3275 let old_dead = heap.allocate(Cell0 {
3276 next: Cell::new(None),
3277 marker_calls: Cell::new(0),
3278 });
3279 let old_id = old_dead.identity();
3280 heap.register_allocation_token(old_id);
3281
3282 let outcome = heap.incremental_run_until_state_with_post_mark(
3283 &OneRoot(None),
3284 GcState::SweepAllGc,
3285 1024,
3286 |_| {},
3287 );
3288 assert_eq!(outcome, StepOutcome::InProgress);
3289 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3290
3291 let new_during_sweep = heap.allocate(Cell0 {
3292 next: Cell::new(None),
3293 marker_calls: Cell::new(0),
3294 });
3295 let new_id = new_during_sweep.identity();
3296 heap.register_allocation_token(new_id);
3297
3298 loop {
3299 let outcome = heap.incremental_step_with_post_mark(
3300 &OneRoot(None),
3301 StepBudget::from_work(64),
3302 |_| {},
3303 );
3304 if matches!(outcome, StepOutcome::Paused) {
3305 break;
3306 }
3307 }
3308
3309 assert_eq!(heap.allocation_token(old_id), None);
3310 assert!(heap.allocation_token(new_id).is_some());
3311 assert_eq!(heap.allgc_count(), 1);
3312
3313 heap.full_collect(&OneRoot(None));
3314 assert_eq!(heap.allocation_token(new_id), None);
3315 assert_eq!(heap.allgc_count(), 0);
3316 }
3317
3318 #[test]
3319 fn promote_and_reset_all_ages() {
3320 let heap = Heap::new();
3321 heap.unpause();
3322 let a = heap.allocate(Cell0 {
3323 next: Cell::new(None),
3324 marker_calls: Cell::new(0),
3325 });
3326 let b = heap.allocate(Cell0 {
3327 next: Cell::new(None),
3328 marker_calls: Cell::new(0),
3329 });
3330
3331 heap.promote_all_to_old();
3332 assert_eq!(a.age(), GcAge::Old);
3333 assert_eq!(b.age(), GcAge::Old);
3334 assert_eq!(a.color(), Color::Black);
3335 assert_eq!(b.color(), Color::Black);
3336
3337 heap.reset_all_ages();
3338 assert_eq!(a.age(), GcAge::New);
3339 assert_eq!(b.age(), GcAge::New);
3340 assert!(a.color().is_white());
3341 assert!(b.color().is_white());
3342 }
3343
3344 #[test]
3345 fn generational_barriers_update_ages() {
3346 let heap = Heap::new();
3347 heap.unpause();
3348 let parent = heap.allocate(Cell0 {
3349 next: Cell::new(None),
3350 marker_calls: Cell::new(0),
3351 });
3352 let child = heap.allocate(Cell0 {
3353 next: Cell::new(None),
3354 marker_calls: Cell::new(0),
3355 });
3356
3357 parent.set_age(GcAge::Old);
3358 parent.set_color(Color::Black);
3359 heap.generational_backward_barrier(parent);
3360 assert_eq!(parent.age(), GcAge::Touched1);
3361 assert_eq!(parent.color(), Color::Gray);
3362
3363 heap.generational_forward_barrier(parent, child);
3364 assert_eq!(child.age(), GcAge::Old0);
3365 }
3366
3367 #[test]
3368 fn minor_collect_frees_young_and_keeps_old() {
3369 let heap = Heap::new();
3370 heap.unpause();
3371 let old_unreachable = heap.allocate(Cell0 {
3372 next: Cell::new(None),
3373 marker_calls: Cell::new(0),
3374 });
3375 old_unreachable.set_age(GcAge::Old);
3376 old_unreachable.set_color(Color::Black);
3377 let _young_unreachable = heap.allocate(Cell0 {
3378 next: Cell::new(None),
3379 marker_calls: Cell::new(0),
3380 });
3381 let young_survivor = heap.allocate(Cell0 {
3382 next: Cell::new(None),
3383 marker_calls: Cell::new(0),
3384 });
3385
3386 heap.minor_collect_with_post_mark(&OneRoot(Some(young_survivor)), |_| {});
3387
3388 assert_eq!(heap.allgc_count(), 2);
3389 assert_eq!(old_unreachable.age(), GcAge::Old);
3390 assert_eq!(young_survivor.age(), GcAge::Survival);
3391 assert!(young_survivor.color().is_white());
3392 }
3393
3394 #[test]
3395 fn minor_collect_skips_untouched_old_root_scan_work() {
3396 let heap = Heap::new();
3397 heap.unpause();
3398 let old_root = heap.allocate(Cell0 {
3399 next: Cell::new(None),
3400 marker_calls: Cell::new(0),
3401 });
3402 old_root.set_age(GcAge::Old);
3403 old_root.set_color(Color::Black);
3404 let young_root = heap.allocate(Cell0 {
3405 next: Cell::new(None),
3406 marker_calls: Cell::new(0),
3407 });
3408
3409 heap.minor_collect_with_post_mark(
3410 &TwoRoots {
3411 first: Some(old_root),
3412 second: Some(young_root),
3413 },
3414 |_| {},
3415 );
3416
3417 let stats = heap.last_mark_stats();
3418 assert_eq!(stats.marked, 2);
3419 assert_eq!(stats.marked_old, 1);
3420 assert_eq!(stats.marked_young, 1);
3421 assert_eq!(stats.traced, 1);
3422 assert_eq!(stats.traced_old, 0);
3423 assert_eq!(stats.traced_young, 1);
3424 assert_eq!(old_root.marker_calls.get(), 0);
3425 assert_eq!(young_root.marker_calls.get(), 1);
3426 assert_eq!(old_root.age(), GcAge::Old);
3427 assert_eq!(young_root.age(), GcAge::Survival);
3428 }
3429
3430 #[test]
3431 fn minor_collect_traces_touched_old_parent() {
3432 let heap = Heap::new();
3433 heap.unpause();
3434 let old_root = heap.allocate(Cell0 {
3435 next: Cell::new(None),
3436 marker_calls: Cell::new(0),
3437 });
3438 old_root.set_age(GcAge::Old);
3439 old_root.set_color(Color::Black);
3440 let young_child = heap.allocate(Cell0 {
3441 next: Cell::new(None),
3442 marker_calls: Cell::new(0),
3443 });
3444 old_root.next.set(Some(young_child));
3445 heap.generational_backward_barrier(old_root);
3446
3447 heap.minor_collect_with_post_mark(&OneRoot(Some(old_root)), |_| {});
3448
3449 let stats = heap.last_mark_stats();
3450 assert_eq!(stats.marked, 2);
3451 assert_eq!(stats.marked_old, 1);
3452 assert_eq!(stats.marked_young, 1);
3453 assert_eq!(stats.traced, 2);
3454 assert_eq!(stats.traced_old, 1);
3455 assert_eq!(stats.traced_young, 1);
3456 assert_eq!(old_root.marker_calls.get(), 1);
3457 assert_eq!(young_child.marker_calls.get(), 1);
3458 assert_eq!(old_root.age(), GcAge::Touched2);
3459 assert_eq!(young_child.age(), GcAge::Survival);
3460 }
3461
3462 #[test]
3463 fn minor_sweep_uses_generation_cursors_to_skip_old_tail() {
3464 let heap = Heap::new();
3465 heap.unpause();
3466 let mut old_objects = Vec::new();
3467 for _ in 0..64 {
3468 old_objects.push(heap.allocate(Cell0 {
3469 next: Cell::new(None),
3470 marker_calls: Cell::new(0),
3471 }));
3472 }
3473 heap.promote_all_to_old();
3474 let young_root = heap.allocate(Cell0 {
3475 next: Cell::new(None),
3476 marker_calls: Cell::new(0),
3477 });
3478
3479 heap.minor_collect_with_post_mark(&OneRoot(Some(young_root)), |_| {});
3480
3481 let stats = heap.last_sweep_stats();
3482 assert_eq!(stats.visited, 1);
3483 assert_eq!(stats.visited_young, 1);
3484 assert_eq!(stats.visited_old, 0);
3485 assert_eq!(heap.allgc_count(), old_objects.len() + 1);
3486 assert_eq!(young_root.age(), GcAge::Survival);
3487 }
3488
3489 #[test]
3490 fn full_sweep_corrects_generation_cursors_when_cursor_object_is_freed() {
3491 let heap = Heap::new();
3492 heap.unpause();
3493 let _old = heap.allocate(Cell0 {
3494 next: Cell::new(None),
3495 marker_calls: Cell::new(0),
3496 });
3497 heap.promote_all_to_old();
3498 assert!(heap.survival.get().is_some());
3499 assert!(heap.old1.get().is_some());
3500 assert!(heap.reallyold.get().is_some());
3501
3502 heap.full_collect(&OneRoot(None));
3503
3504 assert_eq!(heap.allgc_count(), 0);
3505 assert_eq!(heap.survival.get(), None);
3506 assert_eq!(heap.old1.get(), None);
3507 assert_eq!(heap.reallyold.get(), None);
3508 assert_eq!(heap.firstold1.get(), None);
3509 assert_eq!(heap.last_sweep_stats().freed, 1);
3510 }
3511
3512 #[test]
3513 fn full_sweep_unlinks_freed_grayagain_entries() {
3514 let heap = Heap::new();
3515 heap.unpause();
3516 let parent = heap.allocate(Cell0 {
3517 next: Cell::new(None),
3518 marker_calls: Cell::new(0),
3519 });
3520 heap.promote_all_to_old();
3521 heap.generational_backward_barrier(parent);
3522 assert_eq!(heap.grayagain_count(), 1);
3523
3524 heap.full_collect(&OneRoot(None));
3525
3526 assert_eq!(heap.allgc_count(), 0);
3527 assert_eq!(heap.grayagain_count(), 0);
3528
3529 let young = heap.allocate(Cell0 {
3530 next: Cell::new(None),
3531 marker_calls: Cell::new(0),
3532 });
3533 heap.minor_collect_with_post_mark(&OneRoot(Some(young)), |_| {});
3534 assert_eq!(young.age(), GcAge::Survival);
3535 }
3536
3537 #[test]
3538 fn grayagain_links_object_once() {
3539 let heap = Heap::new();
3540 heap.unpause();
3541 let parent = heap.allocate(Cell0 {
3542 next: Cell::new(None),
3543 marker_calls: Cell::new(0),
3544 });
3545 parent.set_age(GcAge::Old);
3546 parent.set_color(Color::Black);
3547
3548 heap.generational_backward_barrier(parent);
3549 heap.generational_backward_barrier(parent);
3550
3551 assert_eq!(heap.grayagain_count(), 1);
3552 }
3553
3554 #[test]
3555 fn grayagain_list_carries_old1_until_old() {
3556 let heap = Heap::new();
3557 heap.unpause();
3558 let survivor = heap.allocate(Cell0 {
3559 next: Cell::new(None),
3560 marker_calls: Cell::new(0),
3561 });
3562
3563 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3564 assert_eq!(survivor.age(), GcAge::Survival);
3565
3566 heap.minor_collect_with_post_mark(&OneRoot(Some(survivor)), |_| {});
3567 assert_eq!(survivor.age(), GcAge::Old1);
3568
3569 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3570 assert_eq!(survivor.age(), GcAge::Old);
3571 assert_eq!(heap.allgc_count(), 1);
3572 }
3573
3574 #[test]
3575 fn grayagain_list_carries_touched2_until_old() {
3576 let heap = Heap::new();
3577 heap.unpause();
3578 let parent = heap.allocate(Cell0 {
3579 next: Cell::new(None),
3580 marker_calls: Cell::new(0),
3581 });
3582 parent.set_age(GcAge::Old);
3583 parent.set_color(Color::Black);
3584 heap.generational_backward_barrier(parent);
3585
3586 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3587 assert_eq!(parent.age(), GcAge::Touched2);
3588
3589 heap.minor_collect_with_post_mark(&OneRoot(None), |_| {});
3590 assert_eq!(parent.age(), GcAge::Old);
3591 assert_eq!(parent.marker_calls.get(), 2);
3592 }
3593
3594 #[test]
3595 fn collect_traverses_cycles() {
3596 let heap = Heap::new();
3597 heap.unpause();
3598 let a = heap.allocate(Cell0 {
3599 next: Cell::new(None),
3600 marker_calls: Cell::new(0),
3601 });
3602 let b = heap.allocate(Cell0 {
3603 next: Cell::new(Some(a)),
3604 marker_calls: Cell::new(0),
3605 });
3606 a.next.set(Some(b)); heap.full_collect(&OneRoot(Some(a)));
3609 assert_eq!(a.marker_calls.get(), 1);
3610 assert_eq!(b.marker_calls.get(), 1);
3611 heap.full_collect(&OneRoot(None));
3615 assert_eq!(heap.bytes_used(), 0);
3616 }
3617
3618 #[test]
3619 fn heap_guard_stacks() {
3620 assert!(
3621 with_current_heap(|heap| heap.is_none()),
3622 "no guard initially"
3623 );
3624 let h1 = Heap::new();
3625 h1.unpause();
3626 {
3627 let _g1 = HeapGuard::push(&h1);
3628 assert!(with_current_heap(|heap| heap.is_some()));
3629 let h2 = Heap::new();
3630 h2.unpause();
3631 {
3632 let _g2 = HeapGuard::push(&h2);
3633 with_current_heap(|heap| {
3635 assert!(std::ptr::addr_eq(
3636 heap.unwrap() as *const _,
3637 &h2 as *const _,
3638 ));
3639 });
3640 }
3641 with_current_heap(|heap| {
3643 assert!(std::ptr::addr_eq(
3644 heap.unwrap() as *const _,
3645 &h1 as *const _,
3646 ));
3647 });
3648 }
3649 assert!(
3650 with_current_heap(|heap| heap.is_none()),
3651 "stack empty after all guards drop"
3652 );
3653 }
3654
3655 #[test]
3656 fn step_threshold_triggers_collect() {
3657 let heap = Heap::new();
3658 heap.unpause();
3659 let mut keeps = Vec::new();
3661 for _ in 0..64 {
3662 keeps.push(heap.allocate(Cell0 {
3666 next: Cell::new(None),
3667 marker_calls: Cell::new(0),
3668 }));
3669 }
3670 struct ManyRoots<'a>(&'a [Gc<Cell0>]);
3672 impl<'a> Trace for ManyRoots<'a> {
3673 fn trace(&self, m: &mut Marker) {
3674 for g in self.0.iter() {
3675 m.mark(*g);
3676 }
3677 }
3678 }
3679 heap.step(&ManyRoots(&keeps));
3680 assert!(heap.bytes_used() > 0);
3682 }
3683
3684 #[test]
3685 fn threshold_floored_after_collecting_tiny_heap() {
3686 let heap = Heap::new();
3687 heap.unpause();
3688 struct NoRoots;
3689 impl Trace for NoRoots {
3690 fn trace(&self, _m: &mut Marker) {}
3691 }
3692 for _ in 0..200 {
3693 heap.allocate(Cell0 {
3694 next: Cell::new(None),
3695 marker_calls: Cell::new(0),
3696 });
3697 }
3698 heap.full_collect(&NoRoots);
3699 assert!(
3700 heap.threshold_bytes() >= GC_MIN_THRESHOLD,
3701 "threshold {} collapsed below floor {}; a churning program would full-collect per allocation",
3702 heap.threshold_bytes(),
3703 GC_MIN_THRESHOLD
3704 );
3705 }
3706
3707 fn build_chain(heap: &Heap, len: usize) -> Gc<Cell0> {
3708 let head = heap.allocate(Cell0 {
3709 next: Cell::new(None),
3710 marker_calls: Cell::new(0),
3711 });
3712 let mut cur = head;
3713 for _ in 1..len {
3714 let n = heap.allocate(Cell0 {
3715 next: Cell::new(None),
3716 marker_calls: Cell::new(0),
3717 });
3718 cur.next.set(Some(n));
3719 cur = n;
3720 }
3721 head
3722 }
3723
3724 #[test]
3725 fn budget_zero_does_some_work() {
3726 let heap = Heap::new();
3727 heap.unpause();
3728 let head = build_chain(&heap, 8);
3729 let roots = OneRoot(Some(head));
3730 let budget = StepBudget::from_work(0);
3731 let outcome = heap.incremental_step_with_post_mark(&roots, budget, |_| {});
3732 assert_ne!(outcome, StepOutcome::SkippedStopped);
3733 assert_ne!(heap.gc_state(), GcState::Pause);
3734 }
3735
3736 #[test]
3737 fn run_until_state_stops_before_next_phase_work() {
3738 let heap = Heap::new();
3739 heap.unpause();
3740 let head = build_chain(&heap, 8);
3741 let roots = OneRoot(Some(head));
3742 let atomic_calls = Cell::new(0);
3743
3744 let outcome =
3745 heap.incremental_run_until_state_with_post_mark(&roots, GcState::Atomic, 1024, |_| {
3746 atomic_calls.set(atomic_calls.get() + 1)
3747 });
3748 assert_eq!(outcome, StepOutcome::InProgress);
3749 assert_eq!(heap.gc_state(), GcState::Atomic);
3750 assert_eq!(
3751 atomic_calls.get(),
3752 0,
3753 "atomic hook must not run before inspection"
3754 );
3755
3756 let outcome = heap.incremental_run_until_state_with_post_mark(
3757 &roots,
3758 GcState::SweepAllGc,
3759 1024,
3760 |_| atomic_calls.set(atomic_calls.get() + 1),
3761 );
3762 assert_eq!(outcome, StepOutcome::InProgress);
3763 assert_eq!(heap.gc_state(), GcState::SweepAllGc);
3764 assert_eq!(
3765 atomic_calls.get(),
3766 1,
3767 "entering sweep must run the atomic hook once"
3768 );
3769 }
3770
3771 #[test]
3772 fn larger_budget_drains_more_gray_than_smaller() {
3773 let small_heap = Heap::new();
3774 small_heap.unpause();
3775 let h1 = build_chain(&small_heap, 64);
3776 let r1 = OneRoot(Some(h1));
3777 let mut small_calls = 0;
3778 loop {
3779 small_calls += 1;
3780 let outcome =
3781 small_heap.incremental_step_with_post_mark(&r1, StepBudget::from_work(2), |_| {});
3782 if outcome == StepOutcome::Paused {
3783 break;
3784 }
3785 assert!(small_calls < 10_000, "did not converge");
3786 }
3787
3788 let big_heap = Heap::new();
3789 big_heap.unpause();
3790 let h2 = build_chain(&big_heap, 64);
3791 let r2 = OneRoot(Some(h2));
3792 let mut big_calls = 0;
3793 loop {
3794 big_calls += 1;
3795 let outcome =
3796 big_heap.incremental_step_with_post_mark(&r2, StepBudget::from_work(64), |_| {});
3797 if outcome == StepOutcome::Paused {
3798 break;
3799 }
3800 assert!(big_calls < 10_000, "did not converge");
3801 }
3802
3803 assert!(
3804 big_calls < small_calls,
3805 "expected big_calls={} < small_calls={}",
3806 big_calls,
3807 small_calls
3808 );
3809 }
3810
3811 #[test]
3812 fn sweep_can_pause_and_resume() {
3813 let heap = Heap::new();
3814 heap.unpause();
3815 for _ in 0..16 {
3816 let _ = heap.allocate(Cell0 {
3817 next: Cell::new(None),
3818 marker_calls: Cell::new(0),
3819 });
3820 }
3821 let roots = OneRoot(None);
3822 let bytes_before = heap.bytes_used();
3823 assert!(bytes_before > 0);
3824 let mut step_count = 0;
3825 let mut saw_in_progress_during_sweep = false;
3826 loop {
3827 step_count += 1;
3828 let outcome =
3829 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {});
3830 if heap.gc_state().is_sweep() && outcome == StepOutcome::InProgress {
3831 saw_in_progress_during_sweep = true;
3832 }
3833 if outcome == StepOutcome::Paused {
3834 break;
3835 }
3836 assert!(step_count < 10_000, "did not converge");
3837 }
3838 assert!(saw_in_progress_during_sweep, "sweep never paused mid-list");
3839 assert_eq!(heap.bytes_used(), 0);
3840 }
3841
3842 #[test]
3843 fn post_mark_runs_once_per_atomic() {
3844 let heap = Heap::new();
3845 heap.unpause();
3846 for _ in 0..32 {
3847 let _ = heap.allocate(Cell0 {
3848 next: Cell::new(None),
3849 marker_calls: Cell::new(0),
3850 });
3851 }
3852 let roots = OneRoot(None);
3853 let call_count = std::cell::Cell::new(0);
3854 let mut step_count = 0;
3855 loop {
3856 step_count += 1;
3857 let outcome =
3858 heap.incremental_step_with_post_mark(&roots, StepBudget::from_work(2), |_| {
3859 call_count.set(call_count.get() + 1);
3860 });
3861 if outcome == StepOutcome::Paused {
3862 break;
3863 }
3864 assert!(step_count < 10_000, "did not converge");
3865 }
3866 assert_eq!(
3867 call_count.get(),
3868 1,
3869 "post_mark must run exactly once per cycle"
3870 );
3871 }
3872
3873 #[test]
3874 fn full_collect_equivalent_to_incremental_to_pause() {
3875 let h1 = Heap::new();
3876 h1.unpause();
3877 let head1 = h1.allocate(Cell0 {
3878 next: Cell::new(None),
3879 marker_calls: Cell::new(0),
3880 });
3881 let _orphan1 = h1.allocate(Cell0 {
3882 next: Cell::new(None),
3883 marker_calls: Cell::new(0),
3884 });
3885 let _orphan2 = h1.allocate(Cell0 {
3886 next: Cell::new(None),
3887 marker_calls: Cell::new(0),
3888 });
3889 let roots1 = OneRoot(Some(head1));
3890 h1.full_collect(&roots1);
3891 let bytes_full = h1.bytes_used();
3892
3893 let h2 = Heap::new();
3894 h2.unpause();
3895 let head2 = h2.allocate(Cell0 {
3896 next: Cell::new(None),
3897 marker_calls: Cell::new(0),
3898 });
3899 let _orphan3 = h2.allocate(Cell0 {
3900 next: Cell::new(None),
3901 marker_calls: Cell::new(0),
3902 });
3903 let _orphan4 = h2.allocate(Cell0 {
3904 next: Cell::new(None),
3905 marker_calls: Cell::new(0),
3906 });
3907 let roots2 = OneRoot(Some(head2));
3908 loop {
3909 let outcome =
3910 h2.incremental_step_with_post_mark(&roots2, StepBudget::from_work(1), |_| {});
3911 if outcome == StepOutcome::Paused {
3912 break;
3913 }
3914 }
3915 assert_eq!(h2.bytes_used(), bytes_full);
3916 }
3917}
3918
3919