Skip to main content

oxihuman_core/
gc_stub.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Mark-and-sweep GC stub — demonstrates the two-phase collection cycle using
6//! object indices instead of actual heap pointers.
7
8/// Identifier for a GC-managed object.
9pub type GcId = usize;
10
11/// State of a GC object.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum GcState {
14    White, /* unreachable — candidate for collection */
15    Grey,  /* reachable but children not yet scanned */
16    Black, /* reachable and fully scanned */
17}
18
19/// A managed object with an adjacency list of references.
20#[derive(Debug)]
21pub struct GcObject {
22    pub id: GcId,
23    pub state: GcState,
24    pub refs: Vec<GcId>,
25}
26
27impl GcObject {
28    fn new(id: GcId) -> Self {
29        Self {
30            id,
31            state: GcState::White,
32            refs: Vec::new(),
33        }
34    }
35}
36
37/// Simple mark-and-sweep garbage collector stub.
38pub struct GcStub {
39    objects: Vec<GcObject>,
40    roots: Vec<GcId>,
41    collected: usize,
42}
43
44impl GcStub {
45    /// Create an empty GC.
46    pub fn new() -> Self {
47        Self {
48            objects: Vec::new(),
49            roots: Vec::new(),
50            collected: 0,
51        }
52    }
53
54    /// Allocate a new managed object. Returns its id.
55    pub fn alloc(&mut self) -> GcId {
56        let id = self.objects.len();
57        self.objects.push(GcObject::new(id));
58        id
59    }
60
61    /// Add a reference from object `from` to object `to`.
62    pub fn add_ref(&mut self, from: GcId, to: GcId) {
63        if from < self.objects.len() {
64            self.objects[from].refs.push(to);
65        }
66    }
67
68    /// Mark `id` as a root (always reachable).
69    pub fn add_root(&mut self, id: GcId) {
70        self.roots.push(id);
71    }
72
73    /// Run mark phase: mark all objects reachable from roots.
74    pub fn mark(&mut self) {
75        /* reset all to white */
76        for obj in &mut self.objects {
77            obj.state = GcState::White;
78        }
79        /* seed grey set from roots */
80        let mut grey: Vec<GcId> = self.roots.clone();
81        for &r in &self.roots {
82            if r < self.objects.len() {
83                self.objects[r].state = GcState::Grey;
84            }
85        }
86        while let Some(id) = grey.pop() {
87            if id >= self.objects.len() {
88                continue;
89            }
90            let children: Vec<GcId> = self.objects[id].refs.clone();
91            for child in children {
92                if child < self.objects.len() && self.objects[child].state == GcState::White {
93                    self.objects[child].state = GcState::Grey;
94                    grey.push(child);
95                }
96            }
97            self.objects[id].state = GcState::Black;
98        }
99    }
100
101    /// Run sweep phase: remove all white objects and return count collected.
102    pub fn sweep(&mut self) -> usize {
103        let before = self.objects.len();
104        self.objects.retain(|o| o.state != GcState::White);
105        let freed = before - self.objects.len();
106        self.collected += freed;
107        freed
108    }
109
110    /// Full collection cycle (mark + sweep).
111    pub fn collect(&mut self) -> usize {
112        self.mark();
113        self.sweep()
114    }
115
116    /// Total number of live managed objects.
117    pub fn live_count(&self) -> usize {
118        self.objects.len()
119    }
120
121    /// Cumulative objects collected since creation.
122    pub fn total_collected(&self) -> usize {
123        self.collected
124    }
125}
126
127impl Default for GcStub {
128    fn default() -> Self {
129        Self::new()
130    }
131}
132
133/// Create a new GC stub.
134pub fn new_gc_stub() -> GcStub {
135    GcStub::new()
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_alloc() {
144        let mut gc = GcStub::new();
145        let id = gc.alloc();
146        assert_eq!(id, 0); /* first object has id 0 */
147    }
148
149    #[test]
150    fn test_collect_unreachable() {
151        let mut gc = GcStub::new();
152        gc.alloc(); /* unreachable object */
153        let freed = gc.collect();
154        assert_eq!(freed, 1); /* one object collected */
155    }
156
157    #[test]
158    fn test_root_not_collected() {
159        let mut gc = GcStub::new();
160        let id = gc.alloc();
161        gc.add_root(id);
162        let freed = gc.collect();
163        assert_eq!(freed, 0); /* root survives */
164    }
165
166    #[test]
167    fn test_reachable_via_root() {
168        let mut gc = GcStub::new();
169        let root = gc.alloc();
170        let child = gc.alloc();
171        gc.add_root(root);
172        gc.add_ref(root, child);
173        let freed = gc.collect();
174        assert_eq!(freed, 0); /* both reachable */
175    }
176
177    #[test]
178    fn test_unreachable_child() {
179        let mut gc = GcStub::new();
180        let root = gc.alloc();
181        let orphan = gc.alloc();
182        gc.add_root(root);
183        let _ = orphan;
184        let freed = gc.collect();
185        assert_eq!(freed, 1); /* orphan collected */
186    }
187
188    #[test]
189    fn test_live_count() {
190        let mut gc = GcStub::new();
191        let id = gc.alloc();
192        gc.add_root(id);
193        gc.collect();
194        assert_eq!(gc.live_count(), 1); /* root alive */
195    }
196
197    #[test]
198    fn test_total_collected() {
199        let mut gc = GcStub::new();
200        gc.alloc();
201        gc.alloc();
202        gc.collect();
203        assert_eq!(gc.total_collected(), 2); /* cumulative count */
204    }
205
206    #[test]
207    fn test_multiple_roots() {
208        let mut gc = GcStub::new();
209        let a = gc.alloc();
210        let b = gc.alloc();
211        gc.add_root(a);
212        gc.add_root(b);
213        let freed = gc.collect();
214        assert_eq!(freed, 0); /* both roots survive */
215    }
216
217    #[test]
218    fn test_default() {
219        let gc = GcStub::default();
220        assert_eq!(gc.live_count(), 0); /* default creates empty GC */
221    }
222
223    #[test]
224    fn test_new_helper() {
225        let gc = new_gc_stub();
226        assert_eq!(gc.live_count(), 0); /* helper creates empty GC */
227    }
228}