diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9c64a20..34b5d0b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1,10 +1,10 @@ use parser::ast; pub mod error; -pub mod object; mod bytecode; mod compiler; +mod object; mod vm; #[cfg(test)] @@ -14,7 +14,7 @@ pub fn run(program: &ast::Program) -> Result<(), error::Error> { let compiler = compiler::Compiler::new(); let bytecode = compiler.compile(program); - let vm = vm::VirtualMachine::new(); + let mut vm = vm::VirtualMachine::new(); vm.run(&bytecode)?; Ok(()) diff --git a/runtime/src/object.rs b/runtime/src/object.rs index f437f99..ffade2e 100644 --- a/runtime/src/object.rs +++ b/runtime/src/object.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, fmt::Display, rc::Rc}; use crate::error::ErrorKind; +use crate::vm::gc; #[derive(Debug, PartialEq, Clone)] pub enum Object { @@ -9,8 +10,51 @@ pub enum Object { Float(f64), Boolean(bool), String(Rc), - Array(Rc>), - HashMap(Rc>), + Array(Array), + Dictionary(Dictionary), +} + +#[derive(Debug, Clone)] +pub struct Array(pub(crate) gc::Ref>); + +// Implement equality for test purposes. +impl PartialEq for Array { + fn eq(&self, other: &Self) -> bool { + // If gc works, we won't be checking non dropped weaks + // so it's fine to check equality on Option> + self.0.value.upgrade() == other.0.value.upgrade() + } +} + +#[derive(Debug, Clone)] +pub struct Dictionary(pub(crate) gc::Ref>); + +// Similar for array for testing purposes +impl PartialEq for Dictionary { + fn eq(&self, other: &Self) -> bool { + self.0.value.upgrade() == other.0.value.upgrade() + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub enum HashKey { + Integer(i64), + Boolean(bool), + String(Rc), +} + +impl TryFrom for HashKey { + type Error = ErrorKind; + + fn try_from(value: Object) -> Result { + match value { + Object::String(str) => Ok(Self::String(str)), + Object::Integer(i) => Ok(Self::Integer(i)), + Object::Boolean(b) => Ok(Self::Boolean(b)), + + _ => Err(ErrorKind::NotHashable(value.into())), + } + } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -33,7 +77,7 @@ impl From<&Object> for DataType { Object::Boolean(_) => Self::Boolean, Object::String(_) => Self::String, Object::Array(_) => Self::Array, - Object::HashMap(_) => Self::HashMap, + Object::Dictionary(_) => Self::HashMap, } } } @@ -57,24 +101,3 @@ impl Display for DataType { } } } - -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub enum HashKey { - Integer(i64), - Boolean(bool), - String(Rc), -} - -impl TryFrom for HashKey { - type Error = ErrorKind; - - fn try_from(value: Object) -> Result { - match value { - Object::String(str) => Ok(Self::String(str)), - Object::Integer(i) => Ok(Self::Integer(i)), - Object::Boolean(b) => Ok(Self::Boolean(b)), - - _ => Err(ErrorKind::NotHashable(value.into())), - } - } -} diff --git a/runtime/src/test/vm.rs b/runtime/src/test/vm.rs index 8d50f28..42c468b 100644 --- a/runtime/src/test/vm.rs +++ b/runtime/src/test/vm.rs @@ -1,12 +1,12 @@ -use std::{collections::HashMap, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use parser::position::{Position, Range}; use crate::{ compiler::Compiler, error::{Error, ErrorKind}, - object::{DataType, HashKey, Object}, - vm::VirtualMachine, + object::{Array, DataType, Dictionary, HashKey, Object}, + vm::{gc, VirtualMachine}, }; fn run_test(input: &str, expected: Result) { @@ -15,7 +15,7 @@ fn run_test(input: &str, expected: Result) { let compiler = Compiler::new(); let bytecode = compiler.compile(&program); - let vm = VirtualMachine::new(); + let mut vm = VirtualMachine::new(); let obj = vm.run(&bytecode); assert_eq!(obj, expected); @@ -47,21 +47,41 @@ fn array() { Object::String(Rc::new("foo".to_string())), ], ), - ( - "[1, [2, 3], 4]", - vec![ - Object::Integer(1), - Object::Array(Rc::new(vec![Object::Integer(2), Object::Integer(3)])), - Object::Integer(4), - ], - ), ]; for (input, expected) in tests { - run_test(input, Ok(Object::Array(Rc::new(expected)))); + let rc = Rc::new(RefCell::new(expected)); + let arr = Array(gc::Ref { + value: Rc::downgrade(&rc), + id: 0, + }); + run_test(input, Ok(Object::Array(arr))); } } +#[test] +fn nested_array() { + let input = "[1, [2, 3], 4]"; + + let inner = Rc::new(RefCell::new(vec![Object::Integer(2), Object::Integer(3)])); + let inner_arr = Object::Array(Array(gc::Ref { + value: Rc::downgrade(&inner), + id: 0, + })); + + let outer = Rc::new(RefCell::new(vec![ + Object::Integer(1), + inner_arr, + Object::Integer(4), + ])); + let outer_arr = Object::Array(Array(gc::Ref { + value: Rc::downgrade(&outer), + id: 0, + })); + + run_test(input, Ok(outer_arr)); +} + #[test] fn hash_map() { let tests = [ @@ -73,26 +93,59 @@ fn hash_map() { Object::Integer(1), )]), ), - ( - "{1: 2, 2: {3: 4}}", - HashMap::from([ - (HashKey::Integer(1), Object::Integer(2)), - ( - HashKey::Integer(2), - Object::HashMap(Rc::new(HashMap::from([( - HashKey::Integer(3), - Object::Integer(4), - )]))), - ), - ]), - ), + // ( + // "{1: 2, 2: {3: 4}}", + // HashMap::from([ + // (HashKey::Integer(1), Object::Integer(2)), + // ( + // HashKey::Integer(2), + // Object::Dictionary(Rc::new(HashMap::from([( + // HashKey::Integer(3), + // Object::Integer(4), + // )]))), + // ), + // ]), + // ), ]; for (input, expected) in tests { - run_test(input, Ok(Object::HashMap(Rc::new(expected)))); + let dict = Rc::new(RefCell::new(expected)); + let dict_ref = gc::Ref { + value: Rc::downgrade(&dict), + id: 0, + }; + run_test(input, Ok(Object::Dictionary(Dictionary(dict_ref)))); } } +#[test] +fn nested_hash_map() { + let input = "{1: 2, 2: {3: 4}}"; + + let inner = Rc::new(RefCell::new(HashMap::from([( + HashKey::Integer(3), + Object::Integer(4), + )]))); + let inner_ref = gc::Ref { + value: Rc::downgrade(&inner), + id: 0, + }; + + let outer = Rc::new(RefCell::new(HashMap::from([ + (HashKey::Integer(1), Object::Integer(2)), + ( + HashKey::Integer(2), + Object::Dictionary(Dictionary(inner_ref)), + ), + ]))); + let outer_ref = gc::Ref { + value: Rc::downgrade(&outer), + id: 0, + }; + + run_test(input, Ok(Object::Dictionary(Dictionary(outer_ref)))); +} + #[test] fn hash_map_error() { let tests = [( diff --git a/runtime/src/vm/gc.rs b/runtime/src/vm/gc.rs new file mode 100644 index 0000000..973457f --- /dev/null +++ b/runtime/src/vm/gc.rs @@ -0,0 +1,123 @@ +use std::{ + any::Any, + cell::RefCell, + collections::HashMap, + fmt::Debug, + rc::{Rc, Weak}, +}; + +use crate::object::Object; + +const MAX_OBJECTS: usize = 1024; + +#[derive(Debug, Clone)] +pub struct Ref { + pub value: Weak>, + pub id: usize, +} + +struct Owner { + value: Rc, + /// Is marked for dealocation. + marked: bool, +} + +impl Debug for Owner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Owner") + .field("value", &self.value) + .field("marked", &self.marked) + .finish() + } +} + +#[derive(Debug)] +pub struct GarbageCollector { + owners: HashMap, +} + +impl GarbageCollector { + pub fn new() -> Self { + Self { + owners: HashMap::new(), + } + } + + pub fn should_free(&self) -> bool { + self.owners.len() > MAX_OBJECTS + } + + pub fn allocate(&mut self, val: T) -> Ref { + let rc = Rc::new(RefCell::new(val)); + let id = rc.as_ptr() as usize; + + let rc_ref = Ref { + value: Rc::downgrade(&rc), + id, + }; + + self.owners.insert( + id, + Owner { + value: rc, + marked: false, + }, + ); + + rc_ref + } + + pub fn free(&mut self, used_stack: &[Object]) { + self.mark_all(true); + + for obj in used_stack { + self.traverse(obj); + } + + self.owners.retain(|_, owner| !owner.marked); + } + + fn traverse(&mut self, obj: &Object) { + match obj { + Object::Array(arr) => { + self.set_mark(arr.0.id, false); + for val in arr + .0 + .value + .upgrade() + .expect("Accessing freed value") + .borrow() + .iter() + { + self.traverse(val); + } + } + Object::Dictionary(dict) => { + self.set_mark(dict.0.id, false); + for val in dict + .0 + .value + .upgrade() + .expect("Accessing freed value") + .borrow() + .values() + { + self.traverse(val); + } + } + _ => (), + } + } + + fn mark_all(&mut self, value: bool) { + for (_, val) in self.owners.iter_mut() { + val.marked = value; + } + } + + fn set_mark(&mut self, id: usize, value: bool) { + if let Some(owner) = self.owners.get_mut(&id) { + owner.marked = value; + } + } +} diff --git a/runtime/src/vm.rs b/runtime/src/vm/mod.rs similarity index 85% rename from runtime/src/vm.rs rename to runtime/src/vm/mod.rs index 8ea0b4b..f782b07 100644 --- a/runtime/src/vm.rs +++ b/runtime/src/vm/mod.rs @@ -1,15 +1,21 @@ -use std::{collections::HashMap, rc::Rc}; +use std::collections::HashMap; use crate::{ bytecode::{Bytecode, Instruction}, error::{Error, ErrorKind}, - object::{HashKey, Object}, + object::{Array, Dictionary, HashKey, Object}, }; +use self::gc::GarbageCollector; + +pub(crate) mod gc; + const STACK_SIZE: usize = 4096; #[derive(Debug)] pub struct VirtualMachine { + gc: GarbageCollector, + stack: Vec, // StackPointer which points to the next value. // Top of the stack is stack[sp-1] @@ -19,6 +25,7 @@ pub struct VirtualMachine { impl VirtualMachine { pub fn new() -> Self { Self { + gc: GarbageCollector::new(), stack: vec![Object::Null; STACK_SIZE], sp: 0, } @@ -49,13 +56,17 @@ impl VirtualMachine { /// This is primarily used to test if the vm works correctly. /// The first element on the stack should be the last popped element /// if the compiler and vm both work correctly. - pub fn run(mut self, bytecode: &Bytecode) -> Result { + pub fn run(&mut self, bytecode: &Bytecode) -> Result { for ip in 0..bytecode.instructions.len() { self.execute_instruction(ip, bytecode) .map_err(|kind| Error { kind, range: bytecode.ranges[ip], })?; + + if self.gc.should_free() { + self.gc.free(&self.stack[0..self.sp]); + } } Ok(self.stack[0].clone()) @@ -82,7 +93,8 @@ impl VirtualMachine { let arr = self.stack[start..self.sp].to_vec(); self.sp -= len; - self.push(Object::Array(Rc::new(arr))) + let arr_ref = self.gc.allocate(arr); + self.push(Object::Array(Array(arr_ref))) } fn execute_hash_map(&mut self, len: usize) -> Result<(), ErrorKind> { @@ -101,7 +113,9 @@ impl VirtualMachine { .collect(); self.sp -= len; - self.push(Object::HashMap(Rc::new(hash_map?))) + + let dict_ref = self.gc.allocate(hash_map?); + self.push(Object::Dictionary(Dictionary(dict_ref))) } fn execute_minus(&mut self) -> Result<(), ErrorKind> {