1use std::{collections::VecDeque, marker::PhantomData, ptr::NonNull};
5
6use crate::{GcHeap, GcNode, GcPartitionId, GcRef, node::GcHead};
7
8pub trait GcTraceFn: Fn(&mut GcTraceCtx) {}
10impl<C: Fn(&mut GcTraceCtx)> GcTraceFn for C {}
11
12pub trait GcTrace: 'static {
13 fn trace(&self, gcx: &mut GcTraceCtx);
15
16 fn gc_children(&self, heap: &GcHeap) -> Vec<NonNull<GcHead>> {
18 let mut gcx = heap.create_trace_ctx(64);
19 self.trace(&mut gcx);
20 gcx.traced_nodes
21 }
22}
23
24pub struct GcTraceCtx<'a> {
25 pub(crate) traced_nodes: Vec<NonNull<GcHead>>,
26 pub(crate) opaque: *mut u8,
27 pub(crate) _mark: PhantomData<&'a ()>,
28}
29
30impl<'a> GcTraceCtx<'a> {
31 #[inline(always)]
32 pub const fn opaque(&self) -> *mut u8 {
33 self.opaque
34 }
35
36 pub fn add_node(&mut self, node: NonNull<GcHead>) {
38 #[cfg(debug_assertions)]
39 unsafe {
40 node.as_ref().debug_assert_node_valid_simple();
41 }
42
43 if !self.traced_nodes.contains(&node) {
44 self.traced_nodes.push(node);
45 }
46 }
47
48 #[inline(always)]
50 pub fn add<T: GcNode>(&mut self, gc_ref: GcRef<T>) {
51 self.add_node(gc_ref.head_ptr);
52 }
53
54 #[inline(always)]
55 pub fn take_nodes(&mut self) -> Vec<NonNull<GcHead>> {
56 std::mem::take(&mut self.traced_nodes)
57 }
58}
59
60impl GcHeap {
61 pub fn create_trace_ctx(&self, cap: usize) -> GcTraceCtx<'_> {
62 GcTraceCtx {
63 traced_nodes: Vec::with_capacity(cap),
64 opaque: self.opaque(),
65 _mark: PhantomData,
66 }
67 }
68
69 pub fn trace_node(&self, node: NonNull<GcHead>, gcx: &mut GcTraceCtx) {
71 let dtype = unsafe { node.as_ref().dtype() } as usize;
72
73 #[cfg(debug_assertions)]
74 let info = self
75 .node_dtypes
76 .type_info_list
77 .get(dtype)
78 .unwrap_or_else(|| {
79 panic!(
80 "trace_node: invalid dtype {} (max {})",
81 dtype,
82 self.node_dtypes.type_info_list.len().saturating_sub(1),
83 )
84 });
85
86 #[cfg(not(debug_assertions))]
87 let info = unsafe { self.node_dtypes.type_info_list.get_unchecked(dtype) };
88
89 (info.trace_fn)(node, gcx);
90 }
91
92 pub fn traverse_start(&mut self, partition_id: GcPartitionId) {
93 for mut node in self.nodes(partition_id) {
94 unsafe {
95 node.as_mut().set_traverse_visited(false);
96 }
97 }
98 }
99
100 pub fn traverse(
104 &mut self,
105 node: NonNull<GcHead>,
106 filter: Option<GcPartitionId>,
107 mut callback: impl FnMut(NonNull<GcHead>, Option<NonNull<GcHead>>),
108 ) {
109 let mut stack: VecDeque<(NonNull<GcHead>, Option<NonNull<GcHead>>)> =
110 vec![(node, None)].into();
111
112 let mut gcx = self.create_trace_ctx(64);
113
114 while let Some((mut current, parent)) = stack.pop_front() {
115 unsafe {
116 #[cfg(debug_assertions)]
117 current.as_ref().debug_assert_node_valid(self);
118
119 if current.as_ref().traverse_visited() {
120 continue;
121 }
122
123 current.as_mut().set_traverse_visited(true);
124
125 if filter.is_none() || filter == Some(current.as_ref().partition_id()) {
126 callback(current, parent);
127 }
128
129 self.trace_node(current, &mut gcx);
130
131 while let Some(child) = gcx.traced_nodes.pop() {
132 if !child.as_ref().traverse_visited() {
133 stack.push_back((child, Some(current)));
134 }
135 }
136 }
137 }
138 }
139}
140
141macro_rules! impl_dummy_trace_for_primitive {
142 ($($ty:ty),*) => {
143 $(
144 impl GcTrace for $ty {
145 #[inline(always)]
146 fn trace(&self, _: &mut GcTraceCtx) { }
147 }
148
149 impl GcTrace for [$ty] {
150 #[inline(always)]
151 fn trace(&self, _: &mut GcTraceCtx) { }
152 }
153
154 impl GcTrace for Vec<$ty> {
155 #[inline(always)]
156 fn trace(&self, _: &mut GcTraceCtx) { }
157 }
158
159 impl GcTrace for Box<[$ty]> {
160 #[inline(always)]
161 fn trace(&self, _: &mut GcTraceCtx) { }
162 }
163 )*
164 };
165}
166
167impl_dummy_trace_for_primitive!(
169 u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, usize, isize, bool, char
170);
171
172impl GcTrace for str {
173 #[inline(always)]
174 fn trace(&self, _: &mut GcTraceCtx) {}
175}
176
177impl GcTrace for &'static str {
178 #[inline(always)]
179 fn trace(&self, _: &mut GcTraceCtx) {}
180}
181
182impl GcTrace for String {
183 #[inline(always)]
184 fn trace(&self, _: &mut GcTraceCtx) {}
185}
186
187impl GcTrace for &'static String {
188 #[inline(always)]
189 fn trace(&self, _: &mut GcTraceCtx) {}
190}
191
192#[cfg(test)]
193mod tests {
194
195 use super::*;
196 use crate::{GcHeap, GcRef, node::GcTriColor};
197
198 #[derive(Debug)]
200 struct TestNode {
201 id: u32,
202 children: Vec<GcRef<TestNode>>,
203 }
204
205 impl TestNode {
206 fn new(id: u32) -> Self {
207 Self {
208 id,
209 children: Vec::new(),
210 }
211 }
212
213 fn add_child(&mut self, child: GcRef<TestNode>) {
214 self.children.push(child);
215 }
216 }
217
218 impl GcTrace for TestNode {
219 fn trace(&self, tr: &mut GcTraceCtx) {
220 println!(
221 "TestNode::trace({self:p}), {} children",
222 self.children.len()
223 );
224
225 for (i, child) in self.children.iter().enumerate() {
226 println!(" Tracing child {}: {:?}", i, child.node_ptr());
227 tr.add(*child);
228 }
229 }
230 }
231
232 crate::gc_type_register! {
233 TestNode, drop_pass = 0;
234 Align32Node, drop_pass = 0;
235 }
236
237 fn count_non_white_nodes(heap: &GcHeap, partition_id: GcPartitionId) -> usize {
239 let mut count = 0;
240 for node in heap.nodes(partition_id) {
241 unsafe {
242 if node.as_ref().color() != GcTriColor::White {
243 count += 1;
244 }
245 }
246 }
247 count
248 }
249
250 fn get_all_node_ids(heap: &GcHeap, partition_id: GcPartitionId) -> Vec<u32> {
252 let mut ids = Vec::new();
253 let info = &GC_TYPE_REGISTRY.type_info_list[TestNode::GC_TYPE_ID as usize];
254 for node in heap.nodes(partition_id) {
255 unsafe {
256 let payload_ptr = info.payload_ptr(node);
257 let id = payload_ptr.cast::<TestNode>().as_ref().id;
258 ids.push(id);
259 }
260 }
261 ids
262 }
263
264 #[test]
266 fn test_trace_propagate_simple_tree() {
267 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
268 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
269
270 let child1 = unsafe { heap.alloc_raw(partition_id, TestNode::new(1)) }.unwrap();
271 let child2 = unsafe { heap.alloc_raw(partition_id, TestNode::new(2)) }.unwrap();
272
273 let mut root = TestNode::new(0);
274 root.add_child(child1);
275 root.add_child(child2);
276 let root_ref = unsafe { heap.alloc_root_raw(partition_id, root) }.unwrap();
277
278 println!("Root: {:?}", root_ref.node_ptr());
280 println!("Child1: {:?}", child1.node_ptr());
281 println!("Child2: {:?}", child2.node_ptr());
282
283 while !heap.mark(partition_id, 16) {}
285
286 println!(
288 "Marks after tracing: {}",
289 count_non_white_nodes(&heap, partition_id)
290 );
291
292 assert_eq!(count_non_white_nodes(&heap, partition_id), 3);
294
295 let ids = get_all_node_ids(&heap, partition_id);
297 assert!(ids.contains(&0));
298 assert!(ids.contains(&1));
299 assert!(ids.contains(&2));
300 }
301
302 #[test]
304 fn test_trace_continue_simple_tree() {
305 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
306 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
307
308 let child1 = unsafe { heap.alloc_raw(partition_id, TestNode::new(1)) }.unwrap();
309 let child2 = unsafe { heap.alloc_raw(partition_id, TestNode::new(2)) }.unwrap();
310
311 let mut root = TestNode::new(0);
312 root.add_child(child1);
313 root.add_child(child2);
314 let root_ref = unsafe { heap.alloc_root_raw(partition_id, root) }.unwrap();
315 while !heap.mark(partition_id, 1) {}
316
317 assert_eq!(count_non_white_nodes(&heap, partition_id), 3);
319 }
320
321 #[test]
323 fn test_trace_deep_nested_tree() {
324 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
325 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
326
327 let level3 = unsafe { heap.alloc_raw(partition_id, TestNode::new(3)) }.unwrap();
328
329 let mut level2 = TestNode::new(2);
330 level2.add_child(level3);
331 let level2_ref = unsafe { heap.alloc_raw(partition_id, level2) }.unwrap();
332
333 let mut level1 = TestNode::new(1);
334 level1.add_child(level2_ref);
335 let level1_ref = unsafe { heap.alloc_raw(partition_id, level1) }.unwrap();
336
337 let mut level0 = TestNode::new(0);
338 level0.add_child(level1_ref);
339 let level0_ref = unsafe { heap.alloc_root_raw(partition_id, level0) }.unwrap();
340
341 while !heap.mark(partition_id, 4) {}
343 assert_eq!(count_non_white_nodes(&heap, partition_id), 4);
344 }
345
346 #[test]
348 fn test_trace_complex_tree() {
349 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
350 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
351
352 let c = unsafe { heap.alloc_raw(partition_id, TestNode::new(3)) }.unwrap();
360 let d = unsafe { heap.alloc_raw(partition_id, TestNode::new(4)) }.unwrap();
361 let e = unsafe { heap.alloc_raw(partition_id, TestNode::new(5)) }.unwrap();
362 let f = unsafe { heap.alloc_raw(partition_id, TestNode::new(6)) }.unwrap();
363
364 let mut a = TestNode::new(1);
365 a.add_child(c);
366 a.add_child(d);
367 let a_ref = unsafe { heap.alloc_raw(partition_id, a) }.unwrap();
368
369 let mut b = TestNode::new(2);
370 b.add_child(e);
371 b.add_child(f);
372 let b_ref = unsafe { heap.alloc_raw(partition_id, b) }.unwrap();
373
374 let mut root = TestNode::new(0);
375 root.add_child(a_ref);
376 root.add_child(b_ref);
377 unsafe { heap.alloc_root_raw(partition_id, root) }.unwrap();
378
379 while !heap.mark(partition_id, 8) {}
381 assert_eq!(count_non_white_nodes(&heap, partition_id), 7);
382 }
383
384 #[test]
386 fn test_trace_algorithms_equivalence() {
387 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
388 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
389
390 let mut nodes = Vec::new();
392 for i in 0..10 {
393 nodes.push(unsafe { heap.alloc_raw(partition_id, TestNode::new(i as u32)) }.unwrap());
394 }
395
396 unsafe {
398 let mut nodes = nodes.clone();
399 let n = nodes[1];
400 unsafe {
401 nodes[0].with_write_barrier(&mut heap, |node| node.add_child(n));
402 }
403
404 let n = nodes[2];
405 unsafe {
406 nodes[0].with_write_barrier(&mut heap, |node| node.add_child(n));
407 }
408
409 let n = nodes[3];
410 nodes[1].with_write_barrier(&mut heap, |node| node.add_child(n));
411
412 let n = nodes[4];
413 nodes[1].with_write_barrier(&mut heap, |node| node.add_child(n));
414
415 let n = nodes[5];
416 unsafe {
417 nodes[2].with_write_barrier(&mut heap, |node| node.add_child(n));
418 }
419
420 let n = nodes[6];
421 nodes[2].with_write_barrier(&mut heap, |node| node.add_child(n));
422
423 let n = nodes[7];
424 nodes[3].with_write_barrier(&mut heap, |node| node.add_child(n));
425
426 let n = nodes[8];
427 nodes[3].with_write_barrier(&mut heap, |node| node.add_child(n));
428
429 let n = nodes[9];
430 nodes[4].with_write_barrier(&mut heap, |node| node.add_child(n));
431 }
432
433 let mut root = TestNode::new(100);
434 root.add_child(nodes[0]);
435 let _ = unsafe { heap.alloc_root_raw(partition_id, root) }.unwrap();
436 while !heap.mark(partition_id, 16) {}
437 let marks1 = count_non_white_nodes(&heap, partition_id);
438
439 heap.mark_reset(partition_id);
441 while !heap.mark(partition_id, 1) {}
442 let marks2 = count_non_white_nodes(&heap, partition_id);
443
444 assert_eq!(marks1, marks2);
446 assert_eq!(marks1, 11);
447 }
448
449 #[test]
451 fn test_trace_circular_reference() {
452 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
453 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
454
455 let mut node1 = unsafe { heap.alloc_raw(partition_id, TestNode::new(1)) }.unwrap();
456 let mut node2 = unsafe { heap.alloc_raw(partition_id, TestNode::new(2)) }.unwrap();
457
458 {
459 unsafe {
460 node1.with_write_barrier(&mut heap, |n| n.add_child(node2));
461 }
462 unsafe {
463 node2.with_write_barrier(&mut heap, |n| n.add_child(node1));
464 }
465 }
466
467 let mut root = TestNode::new(100);
469 root.add_child(node1);
470 let _ = unsafe { heap.alloc_root_raw(partition_id, root) }.unwrap();
471 while !heap.mark(partition_id, 4) {}
472
473 assert_eq!(
475 count_non_white_nodes(&heap, partition_id),
476 3,
477 "Propagate should handle circular reference"
478 );
479
480 heap.mark_reset(partition_id);
481 while !heap.mark(partition_id, 1) {}
482 assert_eq!(count_non_white_nodes(&heap, partition_id), 3);
483 }
484
485 #[test]
491 fn test_write_barrier_black_node_adds_white_child() {
492 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
493 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
494
495 let child = unsafe { heap.alloc_raw(partition_id, TestNode::new(1)) }.unwrap();
497
498 let mut root = unsafe { heap.alloc_root_raw(partition_id, TestNode::new(0)) }.unwrap();
500
501 while !heap.mark(partition_id, 1) {}
504 assert_eq!(count_non_white_nodes(&heap, partition_id), 1);
505
506 unsafe {
509 root.with_write_barrier(&mut heap, |node| node.add_child(child));
510 }
511
512 while !heap.mark(partition_id, 1) {}
514
515 assert_eq!(
517 count_non_white_nodes(&heap, partition_id),
518 2,
519 "Write barrier should have re-grayed root and discovered child"
520 );
521
522 let freed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
524 assert_eq!(freed, 0, "No nodes should be freed after write barrier");
525
526 let ids = get_all_node_ids(&heap, partition_id);
528 assert!(ids.contains(&0));
529 assert!(ids.contains(&1));
530 }
531
532 #[test]
535 fn test_write_barrier_incremental_black_adds_white() {
536 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
537 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
538
539 let c = unsafe { heap.alloc_raw(partition_id, TestNode::new(3)) }.unwrap();
541 let mut b = unsafe { heap.alloc_raw(partition_id, TestNode::new(2)) }.unwrap();
542 unsafe {
543 b.with_write_barrier(&mut heap, |node| node.add_child(c));
544 }
545
546 let mut a = unsafe { heap.alloc_raw(partition_id, TestNode::new(1)) }.unwrap();
547 unsafe {
548 a.with_write_barrier(&mut heap, |node| node.add_child(b));
549 }
550
551 let mut root = unsafe { heap.alloc_root_raw(partition_id, TestNode::new(0)) }.unwrap();
552 unsafe {
553 root.with_write_barrier(&mut heap, |node| node.add_child(a));
554 }
555
556 while !heap.mark(partition_id, 1) {}
559
560 let new_child = unsafe { heap.alloc_raw(partition_id, TestNode::new(10)) }.unwrap();
563 unsafe {
564 root.with_write_barrier(&mut heap, |node| node.add_child(new_child));
565 }
566
567 while !heap.mark(partition_id, 1) {}
569
570 assert_eq!(
572 count_non_white_nodes(&heap, partition_id),
573 5,
574 "Write barrier should have preserved the new child added during marking"
575 );
576
577 let freed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
579 assert_eq!(freed, 0);
580 }
581
582 #[test]
586 fn test_write_barrier_bypass_leaks_white_child() {
587 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
588 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
589
590 let child = unsafe { heap.alloc_raw(partition_id, TestNode::new(1)) }.unwrap();
591 let mut root = unsafe { heap.alloc_root_raw(partition_id, TestNode::new(0)) }.unwrap();
592
593 while !heap.mark(partition_id, 1) {}
595 assert_eq!(count_non_white_nodes(&heap, partition_id), 1);
596
597 unsafe {
600 let payload = root
601 .head_ptr
602 .as_mut()
603 .payload_for::<TestNode>()
604 .cast::<TestNode>()
605 .as_mut();
606 payload.children.push(child);
607 }
608
609 while !heap.mark(partition_id, 1) {}
612
613 assert_eq!(
615 count_non_white_nodes(&heap, partition_id),
616 1,
617 "Without write barrier, the white child should remain unmarked"
618 );
619
620 let freed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
622 assert!(
623 freed > 0,
624 "The white child should be swept without write barrier"
625 );
626
627 let ids = get_all_node_ids(&heap, partition_id);
629 assert!(ids.contains(&0));
630 assert!(!ids.contains(&1), "Child should have been collected");
631 }
632
633 #[repr(align(32))]
636 #[derive(Debug)]
637 struct Align32Node {
638 id: u64,
639 data: [u8; 64],
640 }
641
642 impl GcTrace for Align32Node {
643 fn trace(&self, _: &mut GcTraceCtx) {}
644 }
645
646 #[test]
647 fn test_high_alignment_payload_alloc_and_access() {
648 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
649 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
650
651 let node: GcRef<Align32Node> = unsafe {
652 heap.alloc_root_raw(
653 partition_id,
654 Align32Node {
655 id: 42,
656 data: [0xAB; 64],
657 },
658 )
659 }
660 .unwrap();
661
662 let n = unsafe { node.as_ref() };
664 assert_eq!(n.id, 42);
665 assert_eq!(n.data[0], 0xAB);
666 assert_eq!(n.data[63], 0xAB);
667
668 let payload_ptr = unsafe { node.as_ptr() }.as_ptr() as usize;
670 assert_eq!(
671 payload_ptr % 32,
672 0,
673 "Align32Node payload must be 32-byte aligned, got offset {}",
674 payload_ptr % 32
675 );
676
677 while !heap.mark(partition_id, 64) {}
679 let freed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
680 assert_eq!(freed, 0);
681
682 assert_eq!(unsafe { node.as_ref() }.id, 42);
684 }
685
686 #[test]
687 fn test_high_alignment_payload_multiple_nodes() {
688 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
689 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
690
691 let nodes: Vec<GcRef<Align32Node>> = (0..10)
692 .map(|i| {
693 unsafe {
694 heap.alloc_root_raw(
695 partition_id,
696 Align32Node {
697 id: i as u64,
698 data: [i as u8; 64],
699 },
700 )
701 }
702 .unwrap()
703 })
704 .collect();
705
706 for (i, node) in nodes.iter().enumerate() {
708 let n = unsafe { node.as_ref() };
709 assert_eq!(n.id, i as u64);
710 assert_eq!(n.data[0], i as u8);
711 assert_eq!(n.data[63], i as u8);
712
713 let payload_ptr = unsafe { node.as_ptr() }.as_ptr() as usize;
714 assert_eq!(
715 payload_ptr % 32,
716 0,
717 "node[{}] payload must be 32-byte aligned",
718 i
719 );
720 }
721
722 while !heap.mark(partition_id, 64) {}
724 let freed = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
725 assert_eq!(freed, 0);
726
727 for (i, node) in nodes.iter().enumerate() {
729 assert_eq!(unsafe { node.as_ref() }.id, i as u64);
730 }
731 }
732
733 fn count_nodes_in_partition(heap: &GcHeap, pid: GcPartitionId) -> usize {
748 heap.nodes(pid).count()
749 }
750
751 #[test]
759 fn test_cross_partition_basic_ref() {
760 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
761 let p0 = heap.create_partition(64 * 1024, 16 * 1024);
762 let p1 = heap.create_partition(64 * 1024, 16 * 1024);
763
764 let node_b = unsafe { heap.alloc_raw(p1, TestNode::new(2)) }.unwrap();
765
766 let mut node_a = TestNode::new(1);
767 node_a.add_child(node_b);
768 let node_a_ref = unsafe { heap.alloc_raw(p0, node_a) }.unwrap();
769
770 let mut root = TestNode::new(0);
771 root.add_child(node_a_ref);
772 let _root_ref = unsafe { heap.alloc_root_raw(p0, root) }.unwrap();
773
774 assert_eq!(count_nodes_in_partition(&heap, p0), 2);
775 assert_eq!(count_nodes_in_partition(&heap, p1), 1);
776
777 while !heap.mark(p0, 16) {}
780
781 assert_eq!(
782 count_non_white_nodes(&heap, p0),
783 2,
784 "p0: Root + NodeA should be marked"
785 );
786 assert_eq!(
787 count_non_white_nodes(&heap, p1),
788 1,
789 "p1: NodeB should be marked via cross-partition push"
790 );
791
792 heap.sweep(p0, |_, _| {});
794
795 assert_eq!(count_nodes_in_partition(&heap, p1), 1);
797
798 while !heap.mark(p1, 16) {}
800 let freed = heap.sweep(p1, |_, _| {});
801 assert_eq!(freed, 0, "p1 should have no garbage to collect");
802
803 let ids1 = get_all_node_ids(&heap, p1);
804 assert_eq!(ids1, vec![2], "NodeB should survive p1's sweep");
805 }
806
807 #[test]
817 fn test_cross_partition_chain_with_garbage() {
818 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
819 let p0 = heap.create_partition(64 * 1024, 16 * 1024);
820 let p1 = heap.create_partition(64 * 1024, 16 * 1024);
821
822 let node_c = unsafe { heap.alloc_raw(p1, TestNode::new(3)) }.unwrap();
823
824 let mut node_b = TestNode::new(2);
825 node_b.add_child(node_c);
826 let node_b_ref = unsafe { heap.alloc_raw(p1, node_b) }.unwrap();
827
828 let mut node_a = TestNode::new(1);
829 node_a.add_child(node_b_ref);
830 let node_a_ref = unsafe { heap.alloc_raw(p0, node_a) }.unwrap();
831
832 let mut root = TestNode::new(0);
833 root.add_child(node_a_ref);
834 let _root_ref = unsafe { heap.alloc_root_raw(p0, root) }.unwrap();
835
836 let _node_d = unsafe { heap.alloc_raw(p1, TestNode::new(4)) }.unwrap();
838
839 assert_eq!(count_nodes_in_partition(&heap, p0), 2);
840 assert_eq!(count_nodes_in_partition(&heap, p1), 3);
841
842 while !heap.mark(p0, 16) {}
844
845 assert_eq!(
846 count_non_white_nodes(&heap, p0),
847 2,
848 "p0: Root + NodeA marked"
849 );
850 assert_eq!(
854 count_non_white_nodes(&heap, p1),
855 1,
856 "p1: NodeB marked via cross-partition push; C needs p1's own mark"
857 );
858
859 heap.sweep(p0, |_, _| {});
860
861 while !heap.mark(p1, 16) {}
863 assert_eq!(
864 count_non_white_nodes(&heap, p1),
865 2,
866 "p1: B + C both marked after p1 processes its gray_list"
867 );
868 let freed = heap.sweep(p1, |_, _| {});
869 assert!(freed > 0, "p1 should free isolated node D");
870
871 let ids1 = get_all_node_ids(&heap, p1);
872 assert!(
873 ids1.contains(&2),
874 "NodeB should survive (cross-partition protected)"
875 );
876 assert!(
877 ids1.contains(&3),
878 "NodeC should survive (transitive cross-partition protected)"
879 );
880 assert!(
881 !ids1.contains(&4),
882 "NodeD should be collected (no incoming ref)"
883 );
884 }
885
886 #[test]
897 fn test_cross_partition_three_way_cascade() {
898 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
899 let p0 = heap.create_partition(64 * 1024, 16 * 1024);
900 let p1 = heap.create_partition(64 * 1024, 16 * 1024);
901 let p2 = heap.create_partition(64 * 1024, 16 * 1024);
902
903 let node_c = unsafe { heap.alloc_raw(p2, TestNode::new(3)) }.unwrap();
904
905 let mut node_b = TestNode::new(2);
906 node_b.add_child(node_c);
907 let node_b_ref = unsafe { heap.alloc_raw(p1, node_b) }.unwrap();
908
909 let mut node_a = TestNode::new(1);
910 node_a.add_child(node_b_ref);
911 let node_a_ref = unsafe { heap.alloc_raw(p0, node_a) }.unwrap();
912
913 let mut root = TestNode::new(0);
914 root.add_child(node_a_ref);
915 let _root_ref = unsafe { heap.alloc_root_raw(p0, root) }.unwrap();
916
917 while !heap.mark(p0, 16) {}
919
920 assert_eq!(count_non_white_nodes(&heap, p0), 2, "p0: Root + NodeA");
921 assert_eq!(
922 count_non_white_nodes(&heap, p1),
923 1,
924 "p1: NodeB (cross-partition from A)"
925 );
926 assert_eq!(
929 count_non_white_nodes(&heap, p2),
930 0,
931 "p2: NodeC not yet traced (needs p2's mark_grays)"
932 );
933
934 heap.sweep(p0, |_, _| {});
936 while !heap.mark(p1, 16) {}
937 heap.sweep(p1, |_, _| {});
938 while !heap.mark(p2, 16) {}
939 let freed = heap.sweep(p2, |_, _| {});
940 assert_eq!(freed, 0, "no garbage in cascade");
941
942 assert!(
943 get_all_node_ids(&heap, p2).contains(&3),
944 "NodeC should survive entire GC cascade"
945 );
946 }
947
948 #[test]
956 fn test_cross_partition_bidirectional_circular() {
957 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
958 let p0 = heap.create_partition(64 * 1024, 16 * 1024);
959 let p1 = heap.create_partition(64 * 1024, 16 * 1024);
960
961 let node_a = TestNode::new(1);
963 let mut node_a_ref = unsafe { heap.alloc_raw(p0, node_a) }.unwrap();
964
965 let mut node_b = TestNode::new(2);
967 node_b.add_child(node_a_ref);
968 let node_b_ref = unsafe { heap.alloc_raw(p1, node_b) }.unwrap();
969
970 unsafe {
972 node_a_ref.with_write_barrier(&mut heap, |a| {
973 a.add_child(node_b_ref);
974 });
975 }
976
977 let mut root = TestNode::new(0);
978 root.add_child(node_a_ref);
979 let _root_ref = unsafe { heap.alloc_root_raw(p0, root) }.unwrap();
980
981 assert_eq!(count_nodes_in_partition(&heap, p0), 2);
982 assert_eq!(count_nodes_in_partition(&heap, p1), 1);
983
984 while !heap.mark(p0, 16) {}
986
987 assert_eq!(count_non_white_nodes(&heap, p0), 2, "Root + NodeA");
988 assert_eq!(
989 count_non_white_nodes(&heap, p1),
990 1,
991 "NodeB reachable via cross-partition cycle"
992 );
993
994 heap.sweep(p0, |_, _| {});
996 while !heap.mark(p1, 16) {}
997 let freed = heap.sweep(p1, |_, _| {});
998 assert_eq!(freed, 0);
999
1000 assert!(get_all_node_ids(&heap, p0).contains(&1), "NodeA survives");
1001 assert!(get_all_node_ids(&heap, p1).contains(&2), "NodeB survives");
1002 }
1003}