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, regardless their color state.
13    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    /// 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
44    /// Submit a GcRef to collected list
45    #[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    /// Trace direct children of a node into the given trace context
66    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    /// Traverses the node tree starting at `node` in depth-first order,
85    /// invoking `callback` on each visited node with its optional parent.
86    /// If `filter` is non-null, only nodes in the specified partition are visited.
87    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
151// Implement GcTrace for basic types
152impl_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    /// Test node structure for tracing tests
183    #[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    /// Helper function to count marked nodes in a partition
221    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    /// Helper function to get all node IDs in a partition
234    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                // Calculate pointer to TestNode payload
239                let payload_ptr = node.as_ref().payload();
240                // ID field is at offset 24 bytes within TestNode (due to field reordering)
241                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 1: Simple tree structure with Propagate (depth-first)
250    #[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        // Debug: print node pointers
264        println!("Root: {:?}", root_ref.node_ptr());
265        println!("Child1: {:?}", child1.node_ptr());
266        println!("Child2: {:?}", child2.node_ptr());
267
268        // Mark reachable nodes using GC mark algorithm
269        while !heap.mark(partition_id, 16) {}
270
271        // check marks after tracing
272        println!(
273            "Marks after tracing: {}",
274            count_non_white_nodes(&heap, partition_id)
275        );
276
277        // Verify all nodes are marked
278        assert_eq!(count_non_white_nodes(&heap, partition_id), 3);
279
280        // Verify all node IDs are present
281        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 2: Simple tree structure with Continue (breadth-first)
288    #[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        // Verify all nodes are marked
303        assert_eq!(count_non_white_nodes(&heap, partition_id), 3);
304    }
305
306    /// Test 3: Deep nested tree with both algorithms
307    #[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        // Mark reachable nodes
327        while !heap.mark(partition_id, 4) {}
328        assert_eq!(count_non_white_nodes(&heap, partition_id), 4);
329    }
330
331    /// Test 4: Complex tree with multiple branches
332    #[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        // Create a complex tree:
338        //        root
339        //       /    \
340        //      a      b
341        //     / \    / \
342        //    c   d  e   f
343
344        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        // Mark reachable nodes
365        while !heap.mark(partition_id, 8) {}
366        assert_eq!(count_non_white_nodes(&heap, partition_id), 7);
367    }
368
369    /// Test 5: Verify both algorithms produce same result
370    #[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        // Create a tree with 10 nodes in a balanced structure
376        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        // Build tree: 0 -> 1,2; 1 -> 3,4; 2 -> 5,6; 3 -> 7,8; 4 -> 9
382        {
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        // Reset colors and mark again with smaller step limit
419        heap.mark_reset(partition_id);
420        while !heap.mark(partition_id, 1) {}
421        let marks2 = count_non_white_nodes(&heap, partition_id);
422
423        // Both algorithms should mark the same number of nodes
424        assert_eq!(marks1, marks2);
425        assert_eq!(marks1, 11);
426    }
427
428    /// Test 6: Circular reference handling
429    #[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        // Mark reachable nodes - should handle circular reference without infinite loop
443        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        // Both nodes should be marked
449        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}