glyph-runtime 0.0.1

Runtime execution engine for the Glyph programming language
Documentation
//! Memory management for the Glyph VM

use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};

/// Memory statistics and limits
#[derive(Debug)]
pub struct MemoryManager {
    /// Current memory usage in bytes
    current_usage: AtomicUsize,
    /// Maximum allowed memory in bytes
    max_memory: usize,
    /// Track allocations by type for debugging
    allocations: std::sync::Mutex<HashMap<String, usize>>,
}

impl MemoryManager {
    /// Create a new memory manager with the given limit
    pub fn new(max_memory: usize) -> Self {
        MemoryManager {
            current_usage: AtomicUsize::new(0),
            max_memory,
            allocations: std::sync::Mutex::new(HashMap::new()),
        }
    }

    /// Try to allocate memory, returning false if it would exceed the limit
    pub fn try_allocate(&self, bytes: usize, allocation_type: &str) -> bool {
        let old_usage = self.current_usage.fetch_add(bytes, Ordering::SeqCst);
        let new_usage = old_usage + bytes;

        if new_usage > self.max_memory {
            // Rollback the allocation
            self.current_usage.fetch_sub(bytes, Ordering::SeqCst);
            false
        } else {
            // Track allocation
            if let Ok(mut allocs) = self.allocations.lock() {
                *allocs.entry(allocation_type.to_string()).or_insert(0) += bytes;
            }
            true
        }
    }

    /// Free memory
    pub fn free(&self, bytes: usize, allocation_type: &str) {
        self.current_usage.fetch_sub(bytes, Ordering::SeqCst);

        if let Ok(mut allocs) = self.allocations.lock() {
            if let Some(current) = allocs.get_mut(allocation_type) {
                *current = current.saturating_sub(bytes);
            }
        }
    }

    /// Get current memory usage
    pub fn current_usage(&self) -> usize {
        self.current_usage.load(Ordering::SeqCst)
    }

    /// Get maximum memory limit
    pub fn max_memory(&self) -> usize {
        self.max_memory
    }

    /// Get percentage of memory used
    pub fn usage_percentage(&self) -> f64 {
        (self.current_usage() as f64 / self.max_memory as f64) * 100.0
    }

    /// Get allocation breakdown
    pub fn allocation_breakdown(&self) -> HashMap<String, usize> {
        self.allocations.lock().unwrap().clone()
    }

    /// Check if we have enough memory for an allocation
    pub fn has_capacity(&self, bytes: usize) -> bool {
        self.current_usage() + bytes <= self.max_memory
    }
}

/// Helper to estimate memory usage of values
pub mod size_estimate {
    use glyph_types::Value;
    use std::mem;

    /// Estimate the memory usage of a value in bytes
    pub fn estimate_value_size(value: &Value) -> usize {
        match value {
            Value::None => mem::size_of::<Value>(),
            Value::Bool(_) => mem::size_of::<Value>(),
            Value::Int(_) => mem::size_of::<Value>(),
            Value::Float(_) => mem::size_of::<Value>(),
            Value::Str(s) => mem::size_of::<Value>() + s.len(),
            Value::Bytes(b) => mem::size_of::<Value>() + b.len(),
            Value::List(items) => {
                mem::size_of::<Value>() + items.iter().map(estimate_value_size).sum::<usize>()
            }
            Value::Dict(map) => {
                mem::size_of::<Value>()
                    + map
                        .iter()
                        .map(|(k, v)| k.len() + estimate_value_size(v))
                        .sum::<usize>()
            }
            Value::Optional(opt) => {
                mem::size_of::<Value>() + opt.as_ref().map_or(0, |v| estimate_value_size(v))
            }
            Value::Promise(_) => mem::size_of::<Value>(),
            Value::Result(res) => {
                mem::size_of::<Value>()
                    + match res {
                        Ok(v) => estimate_value_size(v),
                        Err(v) => estimate_value_size(v),
                    }
            }
            Value::Function { params, body } => {
                mem::size_of::<Value>() + params.iter().map(|p| p.len()).sum::<usize>() + body.len()
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_memory_allocation() {
        let mm = MemoryManager::new(1000);

        assert!(mm.try_allocate(100, "test"));
        assert_eq!(mm.current_usage(), 100);

        assert!(mm.try_allocate(400, "test"));
        assert_eq!(mm.current_usage(), 500);

        // Try to allocate more than available
        assert!(!mm.try_allocate(600, "test"));
        assert_eq!(mm.current_usage(), 500); // Should not change

        mm.free(200, "test");
        assert_eq!(mm.current_usage(), 300);
    }

    #[test]
    fn test_allocation_tracking() {
        let mm = MemoryManager::new(1000);

        mm.try_allocate(100, "strings");
        mm.try_allocate(200, "lists");
        mm.try_allocate(150, "strings");

        let breakdown = mm.allocation_breakdown();
        assert_eq!(breakdown.get("strings"), Some(&250));
        assert_eq!(breakdown.get("lists"), Some(&200));
    }
}