Skip to main content

gc_lite/
trace.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright (c) 2025-2026 John Ray <996351336@qq.com>
3
4use std::{collections::VecDeque, marker::PhantomData, ptr::NonNull};
5
6use crate::{GcHeap, GcNode, GcPartitionId, GcRef, node::GcHead};
7
8pub trait GcTrace: 'static {
9    /// Collect directly referenced children gc nodes
10    fn trace(&self, gcx: &mut GcTraceCtx);
11
12    /// Get direct referencing children nodes
13    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    /// Submit a node to collected list regardless its color state.
33    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        self.traced_nodes.push(node);
44    }
45
46    /// Submit a GcRef to collected list
47    #[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    /// Trace direct children of a node into the given trace context
68    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    /// Traverses the node tree starting at `node` in depth-first order,
87    /// invoking `callback` on each visited node with its optional parent.
88    /// If `filter` is non-null, only nodes in the specified partition are visited.
89    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
153// Implement GcTrace for basic types
154impl_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    /// Test node structure for tracing tests
185    #[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    /// Helper function to count marked nodes in a partition
223    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    /// Helper function to get all node IDs in a partition
236    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                // Calculate pointer to TestNode payload
241                let payload_ptr = node.as_ref().payload();
242                // ID field is at offset 24 bytes within TestNode (due to field reordering)
243                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 1: Simple tree structure with Propagate (depth-first)
252    #[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        // Debug: print node pointers
266        println!("Root: {:?}", root_ref.node_ptr());
267        println!("Child1: {:?}", child1.node_ptr());
268        println!("Child2: {:?}", child2.node_ptr());
269
270        // Mark reachable nodes using GC mark algorithm
271        while !heap.mark(partition_id, 16) {}
272
273        // check marks after tracing
274        println!(
275            "Marks after tracing: {}",
276            count_non_white_nodes(&heap, partition_id)
277        );
278
279        // Verify all nodes are marked
280        assert_eq!(count_non_white_nodes(&heap, partition_id), 3);
281
282        // Verify all node IDs are present
283        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 2: Simple tree structure with Continue (breadth-first)
290    #[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        // Verify all nodes are marked
305        assert_eq!(count_non_white_nodes(&heap, partition_id), 3);
306    }
307
308    /// Test 3: Deep nested tree with both algorithms
309    #[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        // Mark reachable nodes
329        while !heap.mark(partition_id, 4) {}
330        assert_eq!(count_non_white_nodes(&heap, partition_id), 4);
331    }
332
333    /// Test 4: Complex tree with multiple branches
334    #[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        // Create a complex tree:
340        //        root
341        //       /    \
342        //      a      b
343        //     / \    / \
344        //    c   d  e   f
345
346        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        // Mark reachable nodes
367        while !heap.mark(partition_id, 8) {}
368        assert_eq!(count_non_white_nodes(&heap, partition_id), 7);
369    }
370
371    /// Test 5: Verify both algorithms produce same result
372    #[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        // Create a tree with 10 nodes in a balanced structure
378        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        // Build tree: 0 -> 1,2; 1 -> 3,4; 2 -> 5,6; 3 -> 7,8; 4 -> 9
384        {
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        // Reset colors and mark again with smaller step limit
421        heap.mark_reset(partition_id);
422        while !heap.mark(partition_id, 1) {}
423        let marks2 = count_non_white_nodes(&heap, partition_id);
424
425        // Both algorithms should mark the same number of nodes
426        assert_eq!(marks1, marks2);
427        assert_eq!(marks1, 11);
428    }
429
430    /// Test 6: Circular reference handling
431    #[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        // Mark reachable nodes - should handle circular reference without infinite loop
445        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        // Both nodes should be marked
451        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}