Skip to main content

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