glyph_runtime/
memory.rs

1//! Memory management for the Glyph VM
2
3use std::collections::HashMap;
4use std::sync::atomic::{AtomicUsize, Ordering};
5
6/// Memory statistics and limits
7#[derive(Debug)]
8pub struct MemoryManager {
9    /// Current memory usage in bytes
10    current_usage: AtomicUsize,
11    /// Maximum allowed memory in bytes
12    max_memory: usize,
13    /// Track allocations by type for debugging
14    allocations: std::sync::Mutex<HashMap<String, usize>>,
15}
16
17impl MemoryManager {
18    /// Create a new memory manager with the given limit
19    pub fn new(max_memory: usize) -> Self {
20        MemoryManager {
21            current_usage: AtomicUsize::new(0),
22            max_memory,
23            allocations: std::sync::Mutex::new(HashMap::new()),
24        }
25    }
26
27    /// Try to allocate memory, returning false if it would exceed the limit
28    pub fn try_allocate(&self, bytes: usize, allocation_type: &str) -> bool {
29        let old_usage = self.current_usage.fetch_add(bytes, Ordering::SeqCst);
30        let new_usage = old_usage + bytes;
31
32        if new_usage > self.max_memory {
33            // Rollback the allocation
34            self.current_usage.fetch_sub(bytes, Ordering::SeqCst);
35            false
36        } else {
37            // Track allocation
38            if let Ok(mut allocs) = self.allocations.lock() {
39                *allocs.entry(allocation_type.to_string()).or_insert(0) += bytes;
40            }
41            true
42        }
43    }
44
45    /// Free memory
46    pub fn free(&self, bytes: usize, allocation_type: &str) {
47        self.current_usage.fetch_sub(bytes, Ordering::SeqCst);
48
49        if let Ok(mut allocs) = self.allocations.lock() {
50            if let Some(current) = allocs.get_mut(allocation_type) {
51                *current = current.saturating_sub(bytes);
52            }
53        }
54    }
55
56    /// Get current memory usage
57    pub fn current_usage(&self) -> usize {
58        self.current_usage.load(Ordering::SeqCst)
59    }
60
61    /// Get maximum memory limit
62    pub fn max_memory(&self) -> usize {
63        self.max_memory
64    }
65
66    /// Get percentage of memory used
67    pub fn usage_percentage(&self) -> f64 {
68        (self.current_usage() as f64 / self.max_memory as f64) * 100.0
69    }
70
71    /// Get allocation breakdown
72    pub fn allocation_breakdown(&self) -> HashMap<String, usize> {
73        self.allocations.lock().unwrap().clone()
74    }
75
76    /// Check if we have enough memory for an allocation
77    pub fn has_capacity(&self, bytes: usize) -> bool {
78        self.current_usage() + bytes <= self.max_memory
79    }
80}
81
82/// Helper to estimate memory usage of values
83pub mod size_estimate {
84    use glyph_types::Value;
85    use std::mem;
86
87    /// Estimate the memory usage of a value in bytes
88    pub fn estimate_value_size(value: &Value) -> usize {
89        match value {
90            Value::None => mem::size_of::<Value>(),
91            Value::Bool(_) => mem::size_of::<Value>(),
92            Value::Int(_) => mem::size_of::<Value>(),
93            Value::Float(_) => mem::size_of::<Value>(),
94            Value::Str(s) => mem::size_of::<Value>() + s.len(),
95            Value::Bytes(b) => mem::size_of::<Value>() + b.len(),
96            Value::List(items) => {
97                mem::size_of::<Value>() + items.iter().map(estimate_value_size).sum::<usize>()
98            }
99            Value::Dict(map) => {
100                mem::size_of::<Value>()
101                    + map
102                        .iter()
103                        .map(|(k, v)| k.len() + estimate_value_size(v))
104                        .sum::<usize>()
105            }
106            Value::Optional(opt) => {
107                mem::size_of::<Value>() + opt.as_ref().map_or(0, |v| estimate_value_size(v))
108            }
109            Value::Promise(_) => mem::size_of::<Value>(),
110            Value::Result(res) => {
111                mem::size_of::<Value>()
112                    + match res {
113                        Ok(v) => estimate_value_size(v),
114                        Err(v) => estimate_value_size(v),
115                    }
116            }
117            Value::Function { params, body } => {
118                mem::size_of::<Value>() + params.iter().map(|p| p.len()).sum::<usize>() + body.len()
119            }
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_memory_allocation() {
130        let mm = MemoryManager::new(1000);
131
132        assert!(mm.try_allocate(100, "test"));
133        assert_eq!(mm.current_usage(), 100);
134
135        assert!(mm.try_allocate(400, "test"));
136        assert_eq!(mm.current_usage(), 500);
137
138        // Try to allocate more than available
139        assert!(!mm.try_allocate(600, "test"));
140        assert_eq!(mm.current_usage(), 500); // Should not change
141
142        mm.free(200, "test");
143        assert_eq!(mm.current_usage(), 300);
144    }
145
146    #[test]
147    fn test_allocation_tracking() {
148        let mm = MemoryManager::new(1000);
149
150        mm.try_allocate(100, "strings");
151        mm.try_allocate(200, "lists");
152        mm.try_allocate(150, "strings");
153
154        let breakdown = mm.allocation_breakdown();
155        assert_eq!(breakdown.get("strings"), Some(&250));
156        assert_eq!(breakdown.get("lists"), Some(&200));
157    }
158}