runmat_gc/
roots.rs

1//! GC root scanning and management
2//!
3//! Handles the identification and scanning of GC roots - objects that
4//! should not be collected because they are reachable from the program's
5//! execution context (stacks, global variables, etc.).
6
7use crate::Value;
8use crate::{GcError, GcPtr, Result};
9use std::collections::HashMap;
10use std::sync::atomic::{AtomicUsize, Ordering};
11
12/// Unique identifier for a GC root
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct RootId(pub usize);
15
16/// Trait for objects that can serve as GC roots
17pub trait GcRoot: Send + Sync {
18    /// Scan this root and return all reachable GC pointers
19    fn scan(&self) -> Vec<GcPtr<Value>>;
20
21    /// Get a human-readable description of this root
22    fn description(&self) -> String;
23
24    /// Get the estimated size of objects reachable from this root
25    fn estimated_size(&self) -> usize {
26        0 // Default implementation
27    }
28
29    /// Check if this root is still active
30    fn is_active(&self) -> bool {
31        true // Most roots are always active
32    }
33}
34
35/// A root representing an interpreter's value stack
36pub struct StackRoot {
37    /// Reference to the stack (non-owning)
38    stack_ptr: *const Vec<Value>,
39    description: String,
40}
41
42impl StackRoot {
43    /// Create a new stack root
44    ///
45    /// # Safety
46    ///
47    /// The stack pointer must remain valid for the lifetime of this root
48    pub unsafe fn new(stack: *const Vec<Value>, description: String) -> Self {
49        Self {
50            stack_ptr: stack,
51            description,
52        }
53    }
54}
55
56// Safety: StackRoot is used in a single-threaded context where the pointer
57// remains valid and is not shared across threads unsafely
58unsafe impl Send for StackRoot {}
59unsafe impl Sync for StackRoot {}
60
61impl GcRoot for StackRoot {
62    fn scan(&self) -> Vec<GcPtr<Value>> {
63        unsafe {
64            if self.stack_ptr.is_null() {
65                return Vec::new();
66            }
67
68            let stack = &*self.stack_ptr;
69            let mut roots = Vec::new();
70
71            for value in stack {
72                self.collect_value_roots(value, &mut roots);
73            }
74
75            roots
76        }
77    }
78
79    fn description(&self) -> String {
80        self.description.clone()
81    }
82
83    fn estimated_size(&self) -> usize {
84        unsafe {
85            if self.stack_ptr.is_null() {
86                return 0;
87            }
88
89            let stack = &*self.stack_ptr;
90            stack.len() * std::mem::size_of::<Value>()
91        }
92    }
93
94    fn is_active(&self) -> bool {
95        !self.stack_ptr.is_null()
96    }
97}
98
99impl StackRoot {
100    /// Recursively collect GC roots from a Value
101    #[allow(clippy::only_used_in_recursion)]
102    fn collect_value_roots(&self, value: &Value, _roots: &mut Vec<GcPtr<Value>>) {
103        if let Value::Cell(cells) = value {
104            for cell_value in &cells.data {
105                self.collect_value_roots(cell_value, _roots);
106            }
107        }
108        // For now, we don't track individual Value objects as GC pointers
109        // This would change when we integrate with the actual GC allocation
110    }
111}
112
113/// A root representing an array of variables
114pub struct VariableArrayRoot {
115    /// Reference to the variable array (non-owning)  
116    vars_ptr: *const Vec<Value>,
117    description: String,
118}
119
120impl VariableArrayRoot {
121    /// Create a new variable array root
122    ///
123    /// # Safety
124    ///
125    /// The variables pointer must remain valid for the lifetime of this root
126    pub unsafe fn new(vars: *const Vec<Value>, description: String) -> Self {
127        Self {
128            vars_ptr: vars,
129            description,
130        }
131    }
132}
133
134// Safety: VariableArrayRoot is used in a single-threaded context where the pointer
135// remains valid and is not shared across threads unsafely
136unsafe impl Send for VariableArrayRoot {}
137unsafe impl Sync for VariableArrayRoot {}
138
139impl GcRoot for VariableArrayRoot {
140    fn scan(&self) -> Vec<GcPtr<Value>> {
141        unsafe {
142            if self.vars_ptr.is_null() {
143                return Vec::new();
144            }
145
146            let vars = &*self.vars_ptr;
147            let mut roots = Vec::new();
148
149            for value in vars {
150                self.collect_value_roots(value, &mut roots);
151            }
152
153            roots
154        }
155    }
156
157    fn description(&self) -> String {
158        self.description.clone()
159    }
160
161    fn estimated_size(&self) -> usize {
162        unsafe {
163            if self.vars_ptr.is_null() {
164                return 0;
165            }
166
167            let vars = &*self.vars_ptr;
168            vars.len() * std::mem::size_of::<Value>()
169        }
170    }
171
172    fn is_active(&self) -> bool {
173        !self.vars_ptr.is_null()
174    }
175}
176
177impl VariableArrayRoot {
178    #[allow(clippy::only_used_in_recursion)]
179    fn collect_value_roots(&self, value: &Value, _roots: &mut Vec<GcPtr<Value>>) {
180        if let Value::Cell(cells) = value {
181            for cell_value in &cells.data {
182                self.collect_value_roots(cell_value, _roots);
183            }
184        }
185    }
186}
187
188/// A root for global/static values
189pub struct GlobalRoot {
190    /// Owned global values
191    values: Vec<Value>,
192    description: String,
193}
194
195impl GlobalRoot {
196    pub fn new(values: Vec<Value>, description: String) -> Self {
197        Self {
198            values,
199            description,
200        }
201    }
202
203    pub fn add_value(&mut self, value: Value) {
204        self.values.push(value);
205    }
206
207    pub fn remove_value(&mut self, index: usize) -> Option<Value> {
208        if index < self.values.len() {
209            Some(self.values.remove(index))
210        } else {
211            None
212        }
213    }
214}
215
216impl GcRoot for GlobalRoot {
217    fn scan(&self) -> Vec<GcPtr<Value>> {
218        let mut roots = Vec::new();
219        for value in &self.values {
220            self.collect_value_roots(value, &mut roots);
221        }
222        roots
223    }
224
225    fn description(&self) -> String {
226        self.description.clone()
227    }
228
229    fn estimated_size(&self) -> usize {
230        self.values.len() * std::mem::size_of::<Value>()
231    }
232}
233
234impl GlobalRoot {
235    #[allow(clippy::only_used_in_recursion)]
236    fn collect_value_roots(&self, value: &Value, _roots: &mut Vec<GcPtr<Value>>) {
237        if let Value::Cell(cells) = value {
238            for cell_value in &cells.data {
239                self.collect_value_roots(cell_value, _roots);
240            }
241        }
242    }
243}
244
245/// Manages all GC roots in the system
246pub struct RootScanner {
247    /// Registered roots
248    roots: parking_lot::RwLock<HashMap<RootId, Box<dyn GcRoot>>>,
249
250    /// Next root ID to assign
251    next_id: AtomicUsize,
252
253    /// Statistics
254    scans_performed: AtomicUsize,
255    total_roots_found: AtomicUsize,
256}
257
258impl RootScanner {
259    pub fn new() -> Self {
260        Self {
261            roots: parking_lot::RwLock::new(HashMap::new()),
262            next_id: AtomicUsize::new(1),
263            scans_performed: AtomicUsize::new(0),
264            total_roots_found: AtomicUsize::new(0),
265        }
266    }
267
268    /// Register a new GC root
269    pub fn register_root(&self, root: Box<dyn GcRoot>) -> Result<RootId> {
270        let id = RootId(self.next_id.fetch_add(1, Ordering::Relaxed));
271
272        log::debug!("Registering GC root {}: {}", id.0, root.description());
273
274        let mut roots = self.roots.write();
275        roots.insert(id, root);
276
277        Ok(id)
278    }
279
280    /// Unregister a GC root
281    pub fn unregister_root(&self, root_id: RootId) -> Result<()> {
282        log::debug!("Unregistering GC root {}", root_id.0);
283
284        let mut roots = self.roots.write();
285        match roots.remove(&root_id) {
286            Some(_) => Ok(()),
287            None => Err(GcError::RootRegistrationFailed(format!(
288                "Root {} not found",
289                root_id.0
290            ))),
291        }
292    }
293
294    /// Scan all roots and return reachable objects
295    pub fn scan_roots(&self) -> Result<Vec<GcPtr<Value>>> {
296        log::trace!("Starting root scan");
297        let scan_start = std::time::Instant::now();
298
299        let roots = self.roots.read();
300        let mut all_roots = Vec::new();
301        let mut inactive_roots = Vec::new();
302
303        for (&root_id, root) in roots.iter() {
304            if !root.is_active() {
305                inactive_roots.push(root_id);
306                continue;
307            }
308
309            log::trace!("Scanning root {}: {}", root_id.0, root.description());
310            let root_objects = root.scan();
311            log::trace!(
312                "Found {} objects from root {}",
313                root_objects.len(),
314                root_id.0
315            );
316
317            all_roots.extend(root_objects);
318        }
319
320        drop(roots);
321
322        // Clean up inactive roots
323        if !inactive_roots.is_empty() {
324            let mut roots = self.roots.write();
325            for root_id in inactive_roots {
326                log::debug!("Removing inactive root {}", root_id.0);
327                roots.remove(&root_id);
328            }
329        }
330
331        // Update statistics
332        self.scans_performed.fetch_add(1, Ordering::Relaxed);
333        self.total_roots_found
334            .fetch_add(all_roots.len(), Ordering::Relaxed);
335
336        let scan_duration = scan_start.elapsed();
337        log::debug!(
338            "Root scan completed: {} roots found in {:?}",
339            all_roots.len(),
340            scan_duration
341        );
342
343        Ok(all_roots)
344    }
345
346    /// Get information about all registered roots
347    pub fn root_info(&self) -> Vec<RootInfo> {
348        let roots = self.roots.read();
349        roots
350            .iter()
351            .map(|(&id, root)| RootInfo {
352                id,
353                description: root.description(),
354                estimated_size: root.estimated_size(),
355                is_active: root.is_active(),
356            })
357            .collect()
358    }
359
360    /// Get scanner statistics
361    pub fn stats(&self) -> RootScannerStats {
362        let roots = self.roots.read();
363        RootScannerStats {
364            registered_roots: roots.len(),
365            scans_performed: self.scans_performed.load(Ordering::Relaxed),
366            total_roots_found: self.total_roots_found.load(Ordering::Relaxed),
367            average_roots_per_scan: if self.scans_performed.load(Ordering::Relaxed) > 0 {
368                self.total_roots_found.load(Ordering::Relaxed) as f64
369                    / self.scans_performed.load(Ordering::Relaxed) as f64
370            } else {
371                0.0
372            },
373        }
374    }
375
376    /// Remove all inactive roots
377    pub fn cleanup_inactive_roots(&self) -> usize {
378        let mut roots = self.roots.write();
379        let initial_count = roots.len();
380
381        roots.retain(|_, root| root.is_active());
382
383        let removed_count = initial_count - roots.len();
384        if removed_count > 0 {
385            log::debug!("Cleaned up {removed_count} inactive roots");
386        }
387
388        removed_count
389    }
390}
391
392impl Default for RootScanner {
393    fn default() -> Self {
394        Self::new()
395    }
396}
397
398/// Information about a registered root
399#[derive(Debug, Clone)]
400pub struct RootInfo {
401    pub id: RootId,
402    pub description: String,
403    pub estimated_size: usize,
404    pub is_active: bool,
405}
406
407/// Statistics for the root scanner
408#[derive(Debug, Clone)]
409pub struct RootScannerStats {
410    pub registered_roots: usize,
411    pub scans_performed: usize,
412    pub total_roots_found: usize,
413    pub average_roots_per_scan: f64,
414}
415
416#[cfg(test)]
417mod tests {
418    use super::*;
419
420    #[test]
421    fn test_global_root() {
422        let values = vec![Value::Num(42.0), Value::String("test".to_string())];
423
424        let root = GlobalRoot::new(values, "test global".to_string());
425        assert_eq!(root.description(), "test global");
426        assert!(root.is_active());
427
428        let scanned = root.scan();
429        // Currently returns empty since we don't have GC-allocated Values yet
430        assert_eq!(scanned.len(), 0);
431    }
432
433    #[test]
434    fn test_root_scanner() {
435        let scanner = RootScanner::new();
436
437        let root = Box::new(GlobalRoot::new(vec![Value::Num(1.0)], "test".to_string()));
438
439        let root_id = scanner.register_root(root).expect("should register");
440
441        let roots = scanner.scan_roots().expect("should scan");
442        assert_eq!(roots.len(), 0); // No GC pointers yet
443
444        let info = scanner.root_info();
445        assert_eq!(info.len(), 1);
446        assert_eq!(info[0].description, "test");
447
448        scanner.unregister_root(root_id).expect("should unregister");
449
450        let info = scanner.root_info();
451        assert_eq!(info.len(), 0);
452    }
453
454    #[test]
455    fn test_stack_root() {
456        let stack = vec![Value::Num(1.0), Value::Bool(true)];
457
458        let root = unsafe { StackRoot::new(&stack as *const _, "test stack".to_string()) };
459
460        assert_eq!(root.description(), "test stack");
461        assert!(root.is_active());
462        assert!(root.estimated_size() > 0);
463
464        let scanned = root.scan();
465        assert_eq!(scanned.len(), 0); // No GC pointers in current implementation
466    }
467
468    #[test]
469    fn test_variable_array_root() {
470        let vars = vec![Value::Num(42.0), Value::String("test".to_string())];
471
472        let root = unsafe { VariableArrayRoot::new(&vars as *const _, "test vars".to_string()) };
473
474        assert_eq!(root.description(), "test vars");
475        assert!(root.is_active());
476        assert!(root.estimated_size() > 0);
477    }
478
479    #[test]
480    fn test_root_scanner_stats() {
481        let scanner = RootScanner::new();
482
483        let initial_stats = scanner.stats();
484        assert_eq!(initial_stats.registered_roots, 0);
485        assert_eq!(initial_stats.scans_performed, 0);
486
487        let _root_id = scanner
488            .register_root(Box::new(GlobalRoot::new(vec![], "test".to_string())))
489            .expect("should register");
490
491        let _roots = scanner.scan_roots().expect("should scan");
492
493        let stats = scanner.stats();
494        assert_eq!(stats.registered_roots, 1);
495        assert_eq!(stats.scans_performed, 1);
496    }
497}