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