Skip to content

Commit

Permalink
feat: garbage collection
Browse files Browse the repository at this point in the history
  • Loading branch information
viddrobnic committed Jun 2, 2024
1 parent b6c8946 commit 842f6ff
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 58 deletions.
4 changes: 2 additions & 2 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use parser::ast;

pub mod error;
pub mod object;

mod bytecode;
mod compiler;
mod object;
mod vm;

#[cfg(test)]
Expand All @@ -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(())
Expand Down
71 changes: 47 additions & 24 deletions runtime/src/object.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -9,8 +10,51 @@ pub enum Object {
Float(f64),
Boolean(bool),
String(Rc<String>),
Array(Rc<Vec<Object>>),
HashMap(Rc<HashMap<HashKey, Object>>),
Array(Array),
Dictionary(Dictionary),
}

#[derive(Debug, Clone)]
pub struct Array(pub(crate) gc::Ref<Vec<Object>>);

// 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<Rc<...>>
self.0.value.upgrade() == other.0.value.upgrade()
}
}

#[derive(Debug, Clone)]
pub struct Dictionary(pub(crate) gc::Ref<HashMap<HashKey, Object>>);

// 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<String>),
}

impl TryFrom<Object> for HashKey {
type Error = ErrorKind;

fn try_from(value: Object) -> Result<Self, Self::Error> {
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)]
Expand All @@ -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,
}
}
}
Expand All @@ -57,24 +101,3 @@ impl Display for DataType {
}
}
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum HashKey {
Integer(i64),
Boolean(bool),
String(Rc<String>),
}

impl TryFrom<Object> for HashKey {
type Error = ErrorKind;

fn try_from(value: Object) -> Result<Self, Self::Error> {
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())),
}
}
}
107 changes: 80 additions & 27 deletions runtime/src/test/vm.rs
Original file line number Diff line number Diff line change
@@ -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<Object, Error>) {
Expand All @@ -15,7 +15,7 @@ fn run_test(input: &str, expected: Result<Object, Error>) {
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);
Expand Down Expand Up @@ -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 = [
Expand All @@ -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 = [(
Expand Down
123 changes: 123 additions & 0 deletions runtime/src/vm/gc.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
pub value: Weak<RefCell<T>>,
pub id: usize,
}

struct Owner {
value: Rc<dyn Any>,
/// 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<usize, Owner>,
}

impl GarbageCollector {
pub fn new() -> Self {
Self {
owners: HashMap::new(),
}
}

pub fn should_free(&self) -> bool {
self.owners.len() > MAX_OBJECTS
}

pub fn allocate<T: 'static>(&mut self, val: T) -> Ref<T> {
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;
}
}
}
Loading

0 comments on commit 842f6ff

Please sign in to comment.