1use std::{collections::VecDeque, marker::PhantomData, ptr::NonNull};
5
6use crate::{GcHeap, GcNode, GcPartitionId, GcRef, node::GcHead};
7
8pub trait GcTrace: 'static {
9 fn trace(&self, gcx: &mut GcTraceCtx);
11
12 fn gc_children(&self, heap: &GcHeap) -> Vec<NonNull<GcHead>> {
14 let mut gcx = heap.create_trace_ctx(64);
15 self.trace(&mut gcx);
16 gcx.traced_nodes
17 }
18}
19
20pub struct GcTraceCtx<'a> {
21 pub(crate) traced_nodes: Vec<NonNull<GcHead>>,
22 opaque: *mut u8,
23 _mark: PhantomData<&'a ()>,
24}
25
26impl<'a> GcTraceCtx<'a> {
27 #[inline(always)]
28 pub const fn opaque(&self) -> *mut u8 {
29 self.opaque
30 }
31
32 pub fn add_node(&mut self, node: NonNull<GcHead>) {
34 #[cfg(debug_assertions)]
35 unsafe {
36 node.as_ref().debug_assert_node_valid_simple();
37 }
38
39 self.traced_nodes.push(node);
44 }
45
46 #[inline(always)]
48 pub fn add<T: GcNode>(&mut self, gc_ref: GcRef<T>) {
49 self.add_node(gc_ref.head_ptr);
50 }
51
52 #[inline(always)]
53 pub fn take_nodes(&mut self) -> Vec<NonNull<GcHead>> {
54 std::mem::take(&mut self.traced_nodes)
55 }
56}
57
58impl GcHeap {
59 pub fn create_trace_ctx(&self, cap: usize) -> GcTraceCtx<'_> {
60 GcTraceCtx {
61 traced_nodes: Vec::with_capacity(cap),
62 opaque: self.opaque(),
63 _mark: PhantomData,
64 }
65 }
66
67 pub fn trace_node(&self, node: NonNull<GcHead>, gcx: &mut GcTraceCtx) {
69 unsafe {
70 (self
71 .node_dtypes
72 .type_info_list
73 .get_unchecked(node.as_ref().dtype() as usize)
74 .trace_fn)(node, gcx);
75 }
76 }
77
78 pub fn traverse_start(&mut self, partition_id: GcPartitionId) {
79 for mut node in self.nodes(partition_id) {
80 unsafe {
81 node.as_mut().set_traverse_visited(false);
82 }
83 }
84 }
85
86 pub fn traverse(
90 &mut self,
91 node: NonNull<GcHead>,
92 filter: GcPartitionId,
93 mut callback: impl FnMut(NonNull<GcHead>, Option<NonNull<GcHead>>),
94 ) {
95 let mut stack: VecDeque<(NonNull<GcHead>, Option<NonNull<GcHead>>)> =
96 vec![(node, None)].into();
97
98 let mut gcx = self.create_trace_ctx(64);
99
100 while let Some((mut current, parent)) = stack.pop_front() {
101 unsafe {
102 #[cfg(debug_assertions)]
103 current.as_ref().debug_assert_node_valid(self);
104
105 if current.as_ref().traverse_visited() {
106 continue;
107 }
108
109 current.as_mut().set_traverse_visited(true);
110
111 if filter.is_null() || filter == current.as_ref().partition_id() {
112 callback(current, parent);
113 }
114
115 self.trace_node(current, &mut gcx);
116
117 while let Some(child) = gcx.traced_nodes.pop() {
118 if !child.as_ref().traverse_visited() {
119 stack.push_back((child, Some(current)));
120 }
121 }
122 }
123 }
124 }
125}
126
127macro_rules! impl_dummy_trace_for_primitive {
128 ($($ty:ty),*) => {
129 $(
130 impl GcTrace for $ty {
131 #[inline(always)]
132 fn trace(&self, _: &mut GcTraceCtx) { }
133 }
134
135 impl GcTrace for [$ty] {
136 #[inline(always)]
137 fn trace(&self, _: &mut GcTraceCtx) { }
138 }
139
140 impl GcTrace for Vec<$ty> {
141 #[inline(always)]
142 fn trace(&self, _: &mut GcTraceCtx) { }
143 }
144
145 impl GcTrace for Box<[$ty]> {
146 #[inline(always)]
147 fn trace(&self, _: &mut GcTraceCtx) { }
148 }
149 )*
150 };
151}
152
153impl_dummy_trace_for_primitive!(
155 u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, usize, isize, bool, char
156);
157
158impl GcTrace for str {
159 #[inline(always)]
160 fn trace(&self, _: &mut GcTraceCtx) {}
161}
162
163impl GcTrace for &'static str {
164 #[inline(always)]
165 fn trace(&self, _: &mut GcTraceCtx) {}
166}
167
168impl GcTrace for String {
169 #[inline(always)]
170 fn trace(&self, _: &mut GcTraceCtx) {}
171}
172
173impl GcTrace for &'static String {
174 #[inline(always)]
175 fn trace(&self, _: &mut GcTraceCtx) {}
176}
177
178#[cfg(test)]
179mod tests {
180
181 use super::*;
182 use crate::{GcHeap, GcRef, node::GcTriColor};
183
184 #[derive(Debug)]
186 struct TestNode {
187 id: u32,
188 children: Vec<GcRef<TestNode>>,
189 }
190
191 impl TestNode {
192 fn new(id: u32) -> Self {
193 Self {
194 id,
195 children: Vec::new(),
196 }
197 }
198
199 fn add_child(&mut self, child: GcRef<TestNode>) {
200 self.children.push(child);
201 }
202 }
203
204 impl GcTrace for TestNode {
205 fn trace(&self, tr: &mut GcTraceCtx) {
206 println!(
207 "TestNode::trace({self:p}), {} children",
208 self.children.len()
209 );
210
211 for (i, child) in self.children.iter().enumerate() {
212 println!(" Tracing child {}: {:?}", i, child.node_ptr());
213 tr.add(*child);
214 }
215 }
216 }
217
218 crate::gc_type_register! {
219 TestNode, drop_pass = 0;
220 }
221
222 fn count_non_white_nodes(heap: &GcHeap, partition_id: GcPartitionId) -> usize {
224 let mut count = 0;
225 for node in heap.nodes(partition_id) {
226 unsafe {
227 if node.as_ref().color() != GcTriColor::White {
228 count += 1;
229 }
230 }
231 }
232 count
233 }
234
235 fn get_all_node_ids(heap: &GcHeap, partition_id: GcPartitionId) -> Vec<u32> {
237 let mut ids = Vec::new();
238 for node in heap.nodes(partition_id) {
239 unsafe {
240 let payload_ptr = node.as_ref().payload();
242 let id_addr = payload_ptr.add(24);
244 let id = *(id_addr.as_ptr() as *const u32);
245 ids.push(id);
246 }
247 }
248 ids
249 }
250
251 #[test]
253 fn test_trace_propagate_simple_tree() {
254 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
255 let partition_id = heap.create_partition();
256
257 let child1 = unsafe { heap.alloc_raw(partition_id, TestNode::new(1)) }.unwrap();
258 let child2 = unsafe { heap.alloc_raw(partition_id, TestNode::new(2)) }.unwrap();
259
260 let mut root = TestNode::new(0);
261 root.add_child(child1);
262 root.add_child(child2);
263 let root_ref = unsafe { heap.alloc_root_raw(partition_id, root) }.unwrap();
264
265 println!("Root: {:?}", root_ref.node_ptr());
267 println!("Child1: {:?}", child1.node_ptr());
268 println!("Child2: {:?}", child2.node_ptr());
269
270 while !heap.mark(partition_id, 16) {}
272
273 println!(
275 "Marks after tracing: {}",
276 count_non_white_nodes(&heap, partition_id)
277 );
278
279 assert_eq!(count_non_white_nodes(&heap, partition_id), 3);
281
282 let ids = get_all_node_ids(&heap, partition_id);
284 assert!(ids.contains(&0));
285 assert!(ids.contains(&1));
286 assert!(ids.contains(&2));
287 }
288
289 #[test]
291 fn test_trace_continue_simple_tree() {
292 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
293 let partition_id = heap.create_partition();
294
295 let child1 = unsafe { heap.alloc_raw(partition_id, TestNode::new(1)) }.unwrap();
296 let child2 = unsafe { heap.alloc_raw(partition_id, TestNode::new(2)) }.unwrap();
297
298 let mut root = TestNode::new(0);
299 root.add_child(child1);
300 root.add_child(child2);
301 let root_ref = unsafe { heap.alloc_root_raw(partition_id, root) }.unwrap();
302 while !heap.mark(partition_id, 1) {}
303
304 assert_eq!(count_non_white_nodes(&heap, partition_id), 3);
306 }
307
308 #[test]
310 fn test_trace_deep_nested_tree() {
311 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
312 let partition_id = heap.create_partition();
313
314 let level3 = unsafe { heap.alloc_raw(partition_id, TestNode::new(3)) }.unwrap();
315
316 let mut level2 = TestNode::new(2);
317 level2.add_child(level3);
318 let level2_ref = unsafe { heap.alloc_raw(partition_id, level2) }.unwrap();
319
320 let mut level1 = TestNode::new(1);
321 level1.add_child(level2_ref);
322 let level1_ref = unsafe { heap.alloc_raw(partition_id, level1) }.unwrap();
323
324 let mut level0 = TestNode::new(0);
325 level0.add_child(level1_ref);
326 let level0_ref = unsafe { heap.alloc_root_raw(partition_id, level0) }.unwrap();
327
328 while !heap.mark(partition_id, 4) {}
330 assert_eq!(count_non_white_nodes(&heap, partition_id), 4);
331 }
332
333 #[test]
335 fn test_trace_complex_tree() {
336 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
337 let partition_id = heap.create_partition();
338
339 let c = unsafe { heap.alloc_raw(partition_id, TestNode::new(3)) }.unwrap();
347 let d = unsafe { heap.alloc_raw(partition_id, TestNode::new(4)) }.unwrap();
348 let e = unsafe { heap.alloc_raw(partition_id, TestNode::new(5)) }.unwrap();
349 let f = unsafe { heap.alloc_raw(partition_id, TestNode::new(6)) }.unwrap();
350
351 let mut a = TestNode::new(1);
352 a.add_child(c);
353 a.add_child(d);
354 let a_ref = unsafe { heap.alloc_raw(partition_id, a) }.unwrap();
355
356 let mut b = TestNode::new(2);
357 b.add_child(e);
358 b.add_child(f);
359 let b_ref = unsafe { heap.alloc_raw(partition_id, b) }.unwrap();
360
361 let mut root = TestNode::new(0);
362 root.add_child(a_ref);
363 root.add_child(b_ref);
364 unsafe { heap.alloc_root_raw(partition_id, root) }.unwrap();
365
366 while !heap.mark(partition_id, 8) {}
368 assert_eq!(count_non_white_nodes(&heap, partition_id), 7);
369 }
370
371 #[test]
373 fn test_trace_algorithms_equivalence() {
374 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
375 let partition_id = heap.create_partition();
376
377 let mut nodes = Vec::new();
379 for i in 0..10 {
380 nodes.push(unsafe { heap.alloc_raw(partition_id, TestNode::new(i as u32)) }.unwrap());
381 }
382
383 {
385 let mut nodes = nodes.clone();
386 let n = nodes[1];
387 nodes[0].with_mut(&mut heap, |node| node.add_child(n));
388
389 let n = nodes[2];
390 nodes[0].with_mut(&mut heap, |node| node.add_child(n));
391
392 let n = nodes[3];
393 nodes[1].with_mut(&mut heap, |node| node.add_child(n));
394
395 let n = nodes[4];
396 nodes[1].with_mut(&mut heap, |node| node.add_child(n));
397
398 let n = nodes[5];
399 nodes[2].with_mut(&mut heap, |node| node.add_child(n));
400
401 let n = nodes[6];
402 nodes[2].with_mut(&mut heap, |node| node.add_child(n));
403
404 let n = nodes[7];
405 nodes[3].with_mut(&mut heap, |node| node.add_child(n));
406
407 let n = nodes[8];
408 nodes[3].with_mut(&mut heap, |node| node.add_child(n));
409
410 let n = nodes[9];
411 nodes[4].with_mut(&mut heap, |node| node.add_child(n));
412 }
413
414 let mut root = TestNode::new(100);
415 root.add_child(nodes[0]);
416 let _ = unsafe { heap.alloc_root_raw(partition_id, root) }.unwrap();
417 while !heap.mark(partition_id, 16) {}
418 let marks1 = count_non_white_nodes(&heap, partition_id);
419
420 heap.mark_reset(partition_id);
422 while !heap.mark(partition_id, 1) {}
423 let marks2 = count_non_white_nodes(&heap, partition_id);
424
425 assert_eq!(marks1, marks2);
427 assert_eq!(marks1, 11);
428 }
429
430 #[test]
432 fn test_trace_circular_reference() {
433 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
434 let partition_id = heap.create_partition();
435
436 let mut node1 = unsafe { heap.alloc_raw(partition_id, TestNode::new(1)) }.unwrap();
437 let mut node2 = unsafe { heap.alloc_raw(partition_id, TestNode::new(2)) }.unwrap();
438
439 {
440 node1.with_mut(&mut heap, |n| n.add_child(node2));
441 node2.with_mut(&mut heap, |n| n.add_child(node1));
442 }
443
444 let mut root = TestNode::new(100);
446 root.add_child(node1);
447 let _ = unsafe { heap.alloc_root_raw(partition_id, root) }.unwrap();
448 while !heap.mark(partition_id, 4) {}
449
450 assert_eq!(
452 count_non_white_nodes(&heap, partition_id),
453 3,
454 "Propagate should handle circular reference"
455 );
456
457 heap.mark_reset(partition_id);
458 while !heap.mark(partition_id, 1) {}
459 assert_eq!(count_non_white_nodes(&heap, partition_id), 3);
460 }
461}