use crate::runtime::Value;
#[derive(Debug, Clone)]
pub struct ConservativeGC {
tracked_objects: Vec<GCObject>,
collections_performed: u64,
objects_collected: u64,
collection_threshold: usize,
allocated_bytes: usize,
auto_collect_enabled: bool,
}
#[derive(Debug, Clone)]
pub struct GCObject {
pub id: usize,
pub size: usize,
pub marked: bool,
pub generation: u8,
pub value: Value,
}
impl ConservativeGC {
pub fn new() -> Self {
Self {
tracked_objects: Vec::new(),
collections_performed: 0,
objects_collected: 0,
collection_threshold: 10 * 1024 * 1024, allocated_bytes: 0,
auto_collect_enabled: true,
}
}
pub fn track_object(&mut self, value: Value) -> usize {
let size = Self::estimate_object_size(&value);
let id = self.next_object_id();
let obj = GCObject {
id,
size,
marked: false,
generation: 0,
value,
};
self.tracked_objects.push(obj);
self.allocated_bytes += size;
if self.auto_collect_enabled && self.allocated_bytes > self.collection_threshold {
self.collect();
}
id
}
pub fn collect(&mut self) {
self.mark_phase();
let collected = self.sweep_phase();
self.collections_performed += 1;
self.objects_collected += collected as u64;
}
fn mark_phase(&mut self) {
let root_ids: Vec<usize> = self
.tracked_objects
.iter()
.filter(|obj| self.is_root_object(obj.id))
.map(|obj| obj.id)
.collect();
for id in root_ids {
self.mark_object(id);
}
}
fn is_root_object(&self, _id: usize) -> bool {
true
}
fn mark_object(&mut self, id: usize) {
let value_clone = {
if let Some(obj) = self.tracked_objects.iter_mut().find(|o| o.id == id) {
if obj.marked {
return; }
obj.marked = true;
obj.value.clone()
} else {
return;
}
};
match &value_clone {
Value::Array(arr) => {
let ref_ids: Vec<usize> = arr
.iter()
.filter_map(|item| self.find_object_id(item))
.collect();
for ref_id in ref_ids {
self.mark_object(ref_id);
}
}
Value::Tuple(elements) => {
let ref_ids: Vec<usize> = elements
.iter()
.filter_map(|item| self.find_object_id(item))
.collect();
for ref_id in ref_ids {
self.mark_object(ref_id);
}
}
_ => {} }
}
fn find_object_id(&self, target: &Value) -> Option<usize> {
for obj in &self.tracked_objects {
if std::ptr::eq(&raw const obj.value, std::ptr::from_ref::<Value>(target)) {
return Some(obj.id);
}
}
None
}
fn sweep_phase(&mut self) -> usize {
let before_count = self.tracked_objects.len();
let mut freed_bytes = 0;
self.tracked_objects.retain(|obj| {
if obj.marked {
true
} else {
freed_bytes += obj.size;
false
}
});
for obj in &mut self.tracked_objects {
obj.marked = false;
}
self.allocated_bytes = self.allocated_bytes.saturating_sub(freed_bytes);
before_count - self.tracked_objects.len()
}
fn estimate_object_size(value: &Value) -> usize {
match value {
Value::Integer(_) => 8,
Value::Float(_) => 8,
Value::Bool(_) => 1,
Value::Byte(_) => 1,
Value::Nil => 0,
Value::String(s) => 24 + s.len(), Value::Array(arr) => {
24 + arr.len() * 8 + arr.iter().map(Self::estimate_object_size).sum::<usize>()
}
Value::Tuple(elements) => {
24 + elements.len() * 8
+ elements
.iter()
.map(Self::estimate_object_size)
.sum::<usize>()
}
Value::Closure { params, .. } => 48 + params.len() * 16,
Value::DataFrame { columns } => {
48 + columns
.iter()
.map(|c| 24 + c.name.len() + c.values.len() * 8)
.sum::<usize>()
}
Value::Object(map) => {
48 + map.len() * 32
+ map
.iter()
.map(|(k, v)| k.len() + Self::estimate_object_size(v))
.sum::<usize>()
}
Value::ObjectMut(cell) => {
let map = cell.lock().expect(
"Mutex poisoned in estimate_object_size - indicates panic in another thread",
);
56 + map.len() * 32 + map
.iter()
.map(|(k, v)| k.len() + Self::estimate_object_size(v))
.sum::<usize>()
}
Value::Range { .. } => 24,
Value::EnumVariant {
variant_name, data, ..
} => 24 + variant_name.len() + data.as_ref().map_or(0, |d| d.len() * 8),
Value::BuiltinFunction(name) => 24 + name.len(),
Value::Struct { name, fields } => {
48 + name.len()
+ fields.len() * 32
+ fields
.iter()
.map(|(k, v)| k.len() + Self::estimate_object_size(v))
.sum::<usize>()
}
Value::Class {
class_name,
fields,
methods,
} => {
let fields_read = fields.read().expect(
"RwLock poisoned in estimate_object_size - indicates panic in another thread",
);
48 + class_name.len()
+ fields_read.len() * 32
+ fields_read
.iter()
.map(|(k, v)| k.len() + Self::estimate_object_size(v))
.sum::<usize>()
+ methods.len() * 32
}
#[cfg(not(target_arch = "wasm32"))]
Value::HtmlDocument(_) => 128, #[cfg(not(target_arch = "wasm32"))]
Value::HtmlElement(_) => 64,
Value::Atom(s) => std::mem::size_of::<Value>() + s.len(),
}
}
fn next_object_id(&self) -> usize {
self.tracked_objects.len()
}
pub fn force_collect(&mut self) -> GCStats {
self.mark_phase();
let collected = self.sweep_phase();
self.collections_performed += 1;
self.objects_collected += collected as u64;
GCStats {
collections: self.collections_performed,
objects_collected: self.objects_collected,
current_objects: self.tracked_objects.len(),
allocated_bytes: self.allocated_bytes,
}
}
pub fn get_stats(&self) -> GCStats {
GCStats {
collections: self.collections_performed,
objects_collected: self.objects_collected,
current_objects: self.tracked_objects.len(),
allocated_bytes: self.allocated_bytes,
}
}
pub fn get_info(&self) -> GCInfo {
GCInfo {
threshold: self.collection_threshold,
auto_collect_enabled: self.auto_collect_enabled,
tracked_count: self.tracked_objects.len(),
}
}
pub fn set_collection_threshold(&mut self, threshold: usize) {
self.collection_threshold = threshold;
}
pub fn set_auto_collect(&mut self, enabled: bool) {
self.auto_collect_enabled = enabled;
}
pub fn clear(&mut self) {
self.tracked_objects.clear();
self.allocated_bytes = 0;
}
}
impl Default for ConservativeGC {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GCStats {
pub collections: u64,
pub objects_collected: u64,
pub current_objects: usize,
pub allocated_bytes: usize,
}
#[derive(Debug, Clone)]
pub struct GCInfo {
pub threshold: usize,
pub auto_collect_enabled: bool,
pub tracked_count: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gc_creation() {
let gc = ConservativeGC::new();
assert_eq!(gc.collections_performed, 0);
assert_eq!(gc.objects_collected, 0);
assert!(gc.auto_collect_enabled);
}
#[test]
fn test_track_object() {
let mut gc = ConservativeGC::new();
let val = Value::Integer(42);
let id = gc.track_object(val);
assert_eq!(id, 0);
assert_eq!(gc.tracked_objects.len(), 1);
}
#[test]
fn test_gc_collect() {
let mut gc = ConservativeGC::new();
gc.track_object(Value::Integer(1));
gc.track_object(Value::Integer(2));
gc.track_object(Value::Integer(3));
let stats_before = gc.get_stats();
assert_eq!(stats_before.current_objects, 3);
gc.collect();
let stats_after = gc.get_stats();
assert_eq!(stats_after.collections, 1);
assert_eq!(stats_after.current_objects, 3);
}
#[test]
fn test_gc_threshold() {
let mut gc = ConservativeGC::new();
gc.set_collection_threshold(100);
assert_eq!(gc.collection_threshold, 100);
gc.set_auto_collect(false);
assert!(!gc.auto_collect_enabled);
}
#[test]
fn test_estimate_object_size() {
assert_eq!(ConservativeGC::estimate_object_size(&Value::Integer(42)), 8);
assert_eq!(ConservativeGC::estimate_object_size(&Value::Float(3.15)), 8);
assert_eq!(ConservativeGC::estimate_object_size(&Value::Bool(true)), 1);
assert_eq!(ConservativeGC::estimate_object_size(&Value::Nil), 0);
let s = Value::from_string("hello".to_string());
assert_eq!(ConservativeGC::estimate_object_size(&s), 24 + 5);
}
#[test]
fn test_gc_clear() {
let mut gc = ConservativeGC::new();
gc.track_object(Value::Integer(1));
gc.track_object(Value::Integer(2));
assert_eq!(gc.tracked_objects.len(), 2);
gc.clear();
assert_eq!(gc.tracked_objects.len(), 0);
assert_eq!(gc.allocated_bytes, 0);
}
#[test]
fn test_gc_stats_and_info() {
let mut gc = ConservativeGC::new();
gc.track_object(Value::Integer(42));
let stats = gc.get_stats();
assert_eq!(stats.current_objects, 1);
assert_eq!(stats.allocated_bytes, 8);
let info = gc.get_info();
assert_eq!(info.tracked_count, 1);
assert!(info.auto_collect_enabled);
}
#[test]
fn test_gc_default() {
let gc = ConservativeGC::default();
assert_eq!(gc.collections_performed, 0);
assert_eq!(gc.tracked_objects.len(), 0);
}
#[test]
fn test_force_collect() {
let mut gc = ConservativeGC::new();
gc.track_object(Value::Integer(1));
gc.track_object(Value::Integer(2));
let stats = gc.force_collect();
assert_eq!(stats.collections, 1);
assert_eq!(stats.current_objects, 2); }
#[test]
fn test_estimate_object_size_byte() {
assert_eq!(ConservativeGC::estimate_object_size(&Value::Byte(255)), 1);
}
#[test]
fn test_estimate_object_size_array() {
let arr = Value::Array(vec![Value::Integer(1), Value::Integer(2)].into());
let size = ConservativeGC::estimate_object_size(&arr);
assert!(size > 24);
}
#[test]
fn test_estimate_object_size_tuple() {
let tuple = Value::Tuple(vec![Value::Integer(1), Value::Bool(true)].into());
let size = ConservativeGC::estimate_object_size(&tuple);
assert!(size > 24);
}
#[test]
fn test_estimate_object_size_closure() {
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
let closure = Value::Closure {
params: vec![("x".to_string(), None), ("y".to_string(), None)],
body: Arc::new(Expr::new(
ExprKind::Literal(Literal::Integer(0, None)),
Span::default(),
)),
env: Rc::new(RefCell::new(std::collections::HashMap::new())),
};
let size = ConservativeGC::estimate_object_size(&closure);
assert!(size >= 48);
}
#[test]
fn test_estimate_object_size_range() {
let range = Value::Range {
start: Box::new(Value::Integer(0)),
end: Box::new(Value::Integer(10)),
inclusive: false,
};
assert_eq!(ConservativeGC::estimate_object_size(&range), 24);
}
#[test]
fn test_estimate_object_size_enum_variant() {
let variant = Value::EnumVariant {
enum_name: "MyEnum".to_string(),
variant_name: "Variant".to_string(),
data: Some(vec![Value::Integer(42)]),
};
let size = ConservativeGC::estimate_object_size(&variant);
assert!(size > 24);
}
#[test]
fn test_estimate_object_size_enum_variant_no_data() {
let variant = Value::EnumVariant {
enum_name: "MyEnum".to_string(),
variant_name: "Empty".to_string(),
data: None,
};
let size = ConservativeGC::estimate_object_size(&variant);
assert!(size >= 24);
}
#[test]
fn test_estimate_object_size_builtin_function() {
let builtin = Value::BuiltinFunction("print".to_string());
let size = ConservativeGC::estimate_object_size(&builtin);
assert_eq!(size, 24 + 5);
}
#[test]
fn test_estimate_object_size_object() {
use std::sync::Arc;
let mut map = std::collections::HashMap::new();
map.insert("key".to_string(), Value::Integer(42));
let obj = Value::Object(Arc::new(map));
let size = ConservativeGC::estimate_object_size(&obj);
assert!(size > 48);
}
#[test]
fn test_estimate_object_size_object_mut() {
use std::sync::Arc;
use std::sync::Mutex;
let mut map = std::collections::HashMap::new();
map.insert("key".to_string(), Value::Integer(42));
let obj = Value::ObjectMut(Arc::new(Mutex::new(map)));
let size = ConservativeGC::estimate_object_size(&obj);
assert!(size > 56);
}
#[test]
fn test_estimate_object_size_struct() {
use std::sync::Arc;
let mut fields = std::collections::HashMap::new();
fields.insert("field1".to_string(), Value::Integer(1));
let s = Value::Struct {
name: "MyStruct".to_string(),
fields: Arc::new(fields),
};
let size = ConservativeGC::estimate_object_size(&s);
assert!(size > 48);
}
#[test]
fn test_estimate_object_size_class() {
use std::sync::{Arc, RwLock};
let fields = Arc::new(RwLock::new(std::collections::HashMap::new()));
let class = Value::Class {
class_name: "MyClass".to_string(),
fields,
methods: Arc::new(std::collections::HashMap::new()),
};
let size = ConservativeGC::estimate_object_size(&class);
assert!(size > 48);
}
#[test]
fn test_estimate_object_size_dataframe() {
use crate::runtime::DataFrameColumn;
let df = Value::DataFrame {
columns: vec![DataFrameColumn {
name: "col1".to_string(),
values: vec![Value::Integer(1), Value::Integer(2)],
}],
};
let size = ConservativeGC::estimate_object_size(&df);
assert!(size > 48);
}
#[test]
fn test_estimate_object_size_atom() {
let atom = Value::Atom("my_atom".to_string());
let size = ConservativeGC::estimate_object_size(&atom);
assert!(size > 0);
}
#[test]
fn test_gc_track_multiple_objects() {
let mut gc = ConservativeGC::new();
gc.track_object(Value::Integer(1));
gc.track_object(Value::from_string("hello".to_string()));
gc.track_object(Value::Bool(true));
gc.track_object(Value::Float(3.14));
assert_eq!(gc.tracked_objects.len(), 4);
let stats = gc.get_stats();
assert!(stats.allocated_bytes > 0);
}
#[test]
fn test_gc_stats_clone() {
let stats = GCStats {
collections: 5,
objects_collected: 100,
current_objects: 50,
allocated_bytes: 1024,
};
let cloned = stats.clone();
assert_eq!(cloned.collections, 5);
assert_eq!(cloned.objects_collected, 100);
}
#[test]
fn test_gc_info_clone() {
let info = GCInfo {
threshold: 1024,
auto_collect_enabled: true,
tracked_count: 10,
};
let cloned = info.clone();
assert_eq!(cloned.threshold, 1024);
assert!(cloned.auto_collect_enabled);
}
#[test]
fn test_gc_object_fields() {
let obj = GCObject {
id: 42,
size: 100,
marked: false,
generation: 0,
value: Value::Integer(99),
};
assert_eq!(obj.id, 42);
assert_eq!(obj.size, 100);
assert!(!obj.marked);
assert_eq!(obj.generation, 0);
}
#[test]
fn test_gc_object_clone() {
let obj = GCObject {
id: 1,
size: 8,
marked: true,
generation: 1,
value: Value::Integer(42),
};
let cloned = obj.clone();
assert_eq!(cloned.id, 1);
assert!(cloned.marked);
}
#[test]
fn test_gc_track_array_triggers_mark() {
let mut gc = ConservativeGC::new();
gc.track_object(Value::Array(
vec![Value::Integer(1), Value::Integer(2)].into(),
));
gc.collect();
let stats = gc.get_stats();
assert_eq!(stats.collections, 1);
}
#[test]
fn test_gc_track_tuple_triggers_mark() {
let mut gc = ConservativeGC::new();
gc.track_object(Value::Tuple(
vec![Value::Integer(1), Value::Bool(true)].into(),
));
gc.collect();
let stats = gc.get_stats();
assert_eq!(stats.collections, 1);
}
#[test]
fn test_gc_auto_collect_trigger() {
let mut gc = ConservativeGC::new();
gc.set_collection_threshold(10); gc.set_auto_collect(true);
gc.track_object(Value::from_string("a".repeat(100)));
let stats = gc.get_stats();
assert!(stats.collections > 0);
}
#[test]
fn test_gc_clone() {
let mut gc = ConservativeGC::new();
gc.track_object(Value::Integer(42));
gc.collect();
let cloned = gc.clone();
assert_eq!(cloned.collections_performed, gc.collections_performed);
assert_eq!(cloned.tracked_objects.len(), gc.tracked_objects.len());
}
}