diff --git a/example/garbage-collection-01.rst b/example/garbage-collection-01.rst new file mode 100644 index 0000000..d69102f --- /dev/null +++ b/example/garbage-collection-01.rst @@ -0,0 +1,4 @@ +{ + fn garbage() {} +} +// garbage is out of scope and not reachable, so the environment should be removed \ No newline at end of file diff --git a/example/garbage-collection-02.rst b/example/garbage-collection-02.rst new file mode 100644 index 0000000..2302f15 --- /dev/null +++ b/example/garbage-collection-02.rst @@ -0,0 +1,9 @@ +fn higher_order(x) { + return y => x + y; +} + +const add10 = higher_order(10); + +const result = add10(20); + +println(result); // 30 \ No newline at end of file diff --git a/src/bytecode/src/builtin/conv/atoi.rs b/src/bytecode/src/builtin/conv/atoi.rs index 047b94b..0edfd08 100644 --- a/src/bytecode/src/builtin/conv/atoi.rs +++ b/src/bytecode/src/builtin/conv/atoi.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const ATOI_SYM: &str = "atoi"; -pub fn atoi(global_env: Rc>) -> Value { +pub fn atoi() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: ATOI_SYM.into(), prms: vec!["s".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/conv/float_to_int.rs b/src/bytecode/src/builtin/conv/float_to_int.rs index 26ce790..3378329 100644 --- a/src/bytecode/src/builtin/conv/float_to_int.rs +++ b/src/bytecode/src/builtin/conv/float_to_int.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const FLOAT_TO_INT_SYM: &str = "float_to_int"; -pub fn float_to_int(global_env: Rc>) -> Value { +pub fn float_to_int() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: FLOAT_TO_INT_SYM.into(), prms: vec!["x".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/conv/int_to_float.rs b/src/bytecode/src/builtin/conv/int_to_float.rs index ad479cb..814fd5d 100644 --- a/src/bytecode/src/builtin/conv/int_to_float.rs +++ b/src/bytecode/src/builtin/conv/int_to_float.rs @@ -1,18 +1,17 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; - +use crate::{FnType, Value, W}; pub const INT_TO_FLOAT_SYM: &str = "int_to_float"; -pub fn int_to_float(global_env: Rc>) -> Value { +pub fn int_to_float() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: INT_TO_FLOAT_SYM.into(), prms: vec!["x".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/conv/itoa.rs b/src/bytecode/src/builtin/conv/itoa.rs index eaccf76..8b5bc7e 100644 --- a/src/bytecode/src/builtin/conv/itoa.rs +++ b/src/bytecode/src/builtin/conv/itoa.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const ITOA_SYM: &str = "itoa"; -pub fn itoa(global_env: Rc>) -> Value { +pub fn itoa() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: ITOA_SYM.into(), prms: vec!["i".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/math/abs.rs b/src/bytecode/src/builtin/math/abs.rs index e9bb776..b98ed5e 100644 --- a/src/bytecode/src/builtin/math/abs.rs +++ b/src/bytecode/src/builtin/math/abs.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{type_of, ByteCodeError, Environment, FnType, Value}; +use crate::{type_of, ByteCodeError, FnType, Value, W}; pub const ABS_SYM: &str = "abs"; -pub fn abs(global_env: Rc>) -> Value { +pub fn abs() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: ABS_SYM.into(), prms: vec!["x".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/math/cos.rs b/src/bytecode/src/builtin/math/cos.rs index 07db930..64ed210 100644 --- a/src/bytecode/src/builtin/math/cos.rs +++ b/src/bytecode/src/builtin/math/cos.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const COS_SYM: &str = "cos"; -pub fn cos(global_env: Rc>) -> Value { +pub fn cos() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: COS_SYM.into(), prms: vec!["x".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/math/log.rs b/src/bytecode/src/builtin/math/log.rs index f3c2120..6a2791b 100644 --- a/src/bytecode/src/builtin/math/log.rs +++ b/src/bytecode/src/builtin/math/log.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const LOG_SYM: &str = "log"; -pub fn log(global_env: Rc>) -> Value { +pub fn log() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: LOG_SYM.into(), prms: vec!["x".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/math/max.rs b/src/bytecode/src/builtin/math/max.rs index 048e53e..816985b 100644 --- a/src/bytecode/src/builtin/math/max.rs +++ b/src/bytecode/src/builtin/math/max.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const MAX_SYM: &str = "max"; -pub fn max(global_env: Rc>) -> Value { +pub fn max() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: MAX_SYM.into(), prms: vec!["v1".into(), "v2".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/math/min.rs b/src/bytecode/src/builtin/math/min.rs index 85a63a6..c53f8f2 100644 --- a/src/bytecode/src/builtin/math/min.rs +++ b/src/bytecode/src/builtin/math/min.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{type_of, ByteCodeError, Environment, FnType, Value}; +use crate::{type_of, ByteCodeError, FnType, Value, W}; pub const MIN_SYM: &str = "min"; -pub fn min(global_env: Rc>) -> Value { +pub fn min() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: MIN_SYM.into(), prms: vec!["v1".into(), "v2".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/math/pow.rs b/src/bytecode/src/builtin/math/pow.rs index 2c75a29..9f9f856 100644 --- a/src/bytecode/src/builtin/math/pow.rs +++ b/src/bytecode/src/builtin/math/pow.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const POW_SYM: &str = "pow"; -pub fn pow(global_env: Rc>) -> Value { +pub fn pow() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: POW_SYM.into(), prms: vec!["base".into(), "exp".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/math/sin.rs b/src/bytecode/src/builtin/math/sin.rs index a9297c7..32e630c 100644 --- a/src/bytecode/src/builtin/math/sin.rs +++ b/src/bytecode/src/builtin/math/sin.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const SIN_SYM: &str = "sin"; -pub fn sin(global_env: Rc>) -> Value { +pub fn sin() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: SIN_SYM.into(), prms: vec!["x".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/math/sqrt.rs b/src/bytecode/src/builtin/math/sqrt.rs index 4cd1283..35cf580 100644 --- a/src/bytecode/src/builtin/math/sqrt.rs +++ b/src/bytecode/src/builtin/math/sqrt.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const SQRT_SYM: &str = "sqrt"; -pub fn sqrt(global_env: Rc>) -> Value { +pub fn sqrt() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: SQRT_SYM.into(), prms: vec!["x".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/math/tan.rs b/src/bytecode/src/builtin/math/tan.rs index 456ac09..b5f0763 100644 --- a/src/bytecode/src/builtin/math/tan.rs +++ b/src/bytecode/src/builtin/math/tan.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const TAN_SYM: &str = "tan"; -pub fn tan(global_env: Rc>) -> Value { +pub fn tan() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: TAN_SYM.into(), prms: vec!["x".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/semaphore/sem_create.rs b/src/bytecode/src/builtin/semaphore/sem_create.rs index 532f668..41ec9f3 100644 --- a/src/bytecode/src/builtin/semaphore/sem_create.rs +++ b/src/bytecode/src/builtin/semaphore/sem_create.rs @@ -1,16 +1,16 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; -use crate::{Environment, FnType, Semaphore, Value}; +use crate::{FnType, Semaphore, Value, W}; pub const SEM_CREATE_SYM: &str = "sem_create"; -pub fn sem_create(global_env: Rc>) -> Value { +pub fn sem_create() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: SEM_CREATE_SYM.into(), prms: vec![], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/semaphore/sem_set.rs b/src/bytecode/src/builtin/semaphore/sem_set.rs index 0f3f818..71a0ba1 100644 --- a/src/bytecode/src/builtin/semaphore/sem_set.rs +++ b/src/bytecode/src/builtin/semaphore/sem_set.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Semaphore, Value}; +use crate::{FnType, Semaphore, Value, W}; pub const SEM_SET_SYM: &str = "sem_set"; -pub fn sem_set(global_env: Rc>) -> Value { +pub fn sem_set() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: SEM_SET_SYM.into(), prms: vec![], addr: 2, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/stdin/read_line.rs b/src/bytecode/src/builtin/stdin/read_line.rs index c789575..a401318 100644 --- a/src/bytecode/src/builtin/stdin/read_line.rs +++ b/src/bytecode/src/builtin/stdin/read_line.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const READ_LINE_SYM: &str = "read_line"; -pub fn read_line(global_env: Rc>) -> Value { +pub fn read_line() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: READ_LINE_SYM.into(), prms: vec![], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/stdout/print.rs b/src/bytecode/src/builtin/stdout/print.rs index 310b4ee..0c6be9a 100644 --- a/src/bytecode/src/builtin/stdout/print.rs +++ b/src/bytecode/src/builtin/stdout/print.rs @@ -1,16 +1,16 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const PRINT_SYM: &str = "print"; -pub fn print(global_env: Rc>) -> Value { +pub fn print() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: PRINT_SYM.into(), prms: vec!["s".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/stdout/println.rs b/src/bytecode/src/builtin/stdout/println.rs index 74a8b84..d7d72ef 100644 --- a/src/bytecode/src/builtin/stdout/println.rs +++ b/src/bytecode/src/builtin/stdout/println.rs @@ -1,16 +1,16 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const PRINTLN_SYM: &str = "println"; -pub fn println(global_env: Rc>) -> Value { +pub fn println() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: PRINTLN_SYM.into(), prms: vec!["s".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/builtin/string/len.rs b/src/bytecode/src/builtin/string/len.rs index 8df999a..921f36b 100644 --- a/src/bytecode/src/builtin/string/len.rs +++ b/src/bytecode/src/builtin/string/len.rs @@ -1,18 +1,18 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Weak; use anyhow::Result; -use crate::{Environment, FnType, Value}; +use crate::{FnType, Value, W}; pub const STRING_LEN_SYM: &str = "string_len"; -pub fn string_len(global_env: Rc>) -> Value { +pub fn string_len() -> Value { Value::Closure { fn_type: FnType::Builtin, sym: STRING_LEN_SYM.into(), prms: vec!["s".into()], addr: 0, - env: global_env, + env: W(Weak::new()), } } diff --git a/src/bytecode/src/environment.rs b/src/bytecode/src/environment/env.rs similarity index 54% rename from src/bytecode/src/environment.rs rename to src/bytecode/src/environment/env.rs index 985a14d..11ed633 100644 --- a/src/bytecode/src/environment.rs +++ b/src/bytecode/src/environment/env.rs @@ -2,19 +2,25 @@ use std::{ cell::RefCell, collections::{hash_map::Entry, HashMap}, fmt::Debug, - rc::Rc, + rc::{Rc, Weak}, }; use anyhow::Result; use crate::{builtin, ByteCodeError, Symbol, Value}; -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone, Default)] pub struct Environment { - pub parent: Option>>, + pub parent: Option>>, pub env: HashMap, } +impl PartialEq for Environment { + fn eq(&self, other: &Self) -> bool { + self.env == other.env + } +} + impl Environment { /// Create a new frame with no parent, i.e. the root frame. pub fn new() -> Self { @@ -40,7 +46,7 @@ impl Environment { /// # Returns /// /// A wrapped reference to the global environment. - pub fn new_global() -> Rc> { + pub fn new_global_wrapped() -> Rc> { let env = Environment::new_wrapped(); // Global constants @@ -62,60 +68,40 @@ impl Environment { // Built in functions // Math functions - env.borrow_mut() - .set(builtin::ABS_SYM, builtin::abs(Rc::clone(&env))); - env.borrow_mut() - .set(builtin::COS_SYM, builtin::cos(Rc::clone(&env))); - env.borrow_mut() - .set(builtin::SIN_SYM, builtin::sin(Rc::clone(&env))); - env.borrow_mut() - .set(builtin::TAN_SYM, builtin::tan(Rc::clone(&env))); - env.borrow_mut() - .set(builtin::LOG_SYM, builtin::log(Rc::clone(&env))); - env.borrow_mut() - .set(builtin::POW_SYM, builtin::pow(Rc::clone(&env))); - env.borrow_mut() - .set(builtin::SQRT_SYM, builtin::sqrt(Rc::clone(&env))); - env.borrow_mut() - .set(builtin::MAX_SYM, builtin::max(Rc::clone(&env))); - env.borrow_mut() - .set(builtin::MIN_SYM, builtin::min(Rc::clone(&env))); + env.borrow_mut().set(builtin::ABS_SYM, builtin::abs()); + env.borrow_mut().set(builtin::COS_SYM, builtin::cos()); + env.borrow_mut().set(builtin::SIN_SYM, builtin::sin()); + env.borrow_mut().set(builtin::TAN_SYM, builtin::tan()); + env.borrow_mut().set(builtin::LOG_SYM, builtin::log()); + env.borrow_mut().set(builtin::POW_SYM, builtin::pow()); + env.borrow_mut().set(builtin::SQRT_SYM, builtin::sqrt()); + env.borrow_mut().set(builtin::MAX_SYM, builtin::max()); + env.borrow_mut().set(builtin::MIN_SYM, builtin::min()); // String functions - env.borrow_mut().set( - builtin::STRING_LEN_SYM, - builtin::string_len(Rc::clone(&env)), - ); + env.borrow_mut() + .set(builtin::STRING_LEN_SYM, builtin::string_len()); // Type conversion functions - env.borrow_mut().set( - builtin::INT_TO_FLOAT_SYM, - builtin::int_to_float(Rc::clone(&env)), - ); - env.borrow_mut().set( - builtin::FLOAT_TO_INT_SYM, - builtin::float_to_int(Rc::clone(&env)), - ); env.borrow_mut() - .set(builtin::ATOI_SYM, builtin::atoi(Rc::clone(&env))); + .set(builtin::INT_TO_FLOAT_SYM, builtin::int_to_float()); env.borrow_mut() - .set(builtin::ITOA_SYM, builtin::itoa(Rc::clone(&env))); + .set(builtin::FLOAT_TO_INT_SYM, builtin::float_to_int()); + env.borrow_mut().set(builtin::ATOI_SYM, builtin::atoi()); + env.borrow_mut().set(builtin::ITOA_SYM, builtin::itoa()); // stdin, stdout env.borrow_mut() - .set(builtin::READ_LINE_SYM, builtin::read_line(Rc::clone(&env))); + .set(builtin::READ_LINE_SYM, builtin::read_line()); + env.borrow_mut().set(builtin::PRINT_SYM, builtin::print()); env.borrow_mut() - .set(builtin::PRINT_SYM, builtin::print(Rc::clone(&env))); - env.borrow_mut() - .set(builtin::PRINTLN_SYM, builtin::println(Rc::clone(&env))); + .set(builtin::PRINTLN_SYM, builtin::println()); // Semaphore functions - env.borrow_mut().set( - builtin::SEM_CREATE_SYM, - builtin::sem_create(Rc::clone(&env)), - ); env.borrow_mut() - .set(builtin::SEM_SET_SYM, builtin::sem_set(Rc::clone(&env))); + .set(builtin::SEM_CREATE_SYM, builtin::sem_create()); + env.borrow_mut() + .set(builtin::SEM_SET_SYM, builtin::sem_set()); env } @@ -124,23 +110,35 @@ impl Environment { pub fn new_wrapped() -> Rc> { Rc::new(RefCell::new(Environment::new())) } +} +impl Environment { /// Set the parent of the frame. - pub fn set_parent(&mut self, parent: Rc>) { + pub fn set_parent(&mut self, parent: Weak>) { self.parent = Some(parent); } -} -impl Environment { /// Get a snapshot of the value of a symbol in the frame at the time of the call. - pub fn get(&self, sym: &Symbol) -> Option { + pub fn get(&self, sym: &Symbol) -> Result { + // If the symbol is found in the current environment, return the value. if let Some(val) = self.env.get(sym) { - Some(val.clone()) - } else if let Some(parent) = &self.parent { - parent.borrow().get(sym) - } else { - None + return Ok(val.clone()); } + + // If the symbol is not found in the current environment, search the parent environment. + let Some(parent) = &self.parent else { + // If the parent environment is not found, return an error. + return Err(ByteCodeError::UnboundedName { name: sym.clone() }.into()); + }; + + // If the parent environment is found, search the parent environment. + let Some(parent) = parent.upgrade() else { + // If the parent environment is dropped prematurely, return an error. + return Err(ByteCodeError::EnvironmentDroppedError.into()); + }; + + let parent_ref = parent.borrow(); + parent_ref.get(sym) } /// Set the value of a symbol in the current environment. @@ -172,17 +170,34 @@ impl Environment { pub fn update(&mut self, sym: impl Into, val: impl Into) -> Result<()> { let sym = sym.into(); + // If the symbol is found in the current environment, update the value. if let Entry::Occupied(mut entry) = self.env.entry(sym.clone()) { entry.insert(val.into()); - Ok(()) - } else if let Some(parent) = &self.parent { - parent.borrow_mut().update(sym, val) - } else { - Err(ByteCodeError::UnboundedName { name: sym }.into()) + return Ok(()); } + + // If the symbol is not found in the current environment, search the parent environment. + let Some(parent) = &self.parent else { + // If the parent environment is not found, return an error. + return Err(ByteCodeError::UnboundedName { name: sym }.into()); + }; + + // If the parent environment is found, search the parent environment. + let Some(parent) = parent.upgrade() else { + // If the parent environment is dropped prematurely, return an error. + return Err(ByteCodeError::EnvironmentDroppedError.into()); + }; + + let mut parent_ref = parent.borrow_mut(); + parent_ref.update(sym, val) } } +pub fn weak_clone(env: &Rc>) -> Weak> { + let env = Rc::clone(env); + Rc::downgrade(&env) +} + #[cfg(test)] mod tests { use super::*; @@ -191,25 +206,26 @@ mod tests { fn test_environment() { let env = Environment::new_wrapped(); env.borrow_mut().set("x", 42); - assert_eq!(env.borrow().get(&"x".to_string()), Some(Value::Int(42))); + assert_eq!(env.borrow().get(&"x".to_string()).unwrap(), Value::Int(42)); } #[test] fn test_set_environment() { let parent_env = Environment::new_wrapped(); parent_env.borrow_mut().set("x", 42); + let parent_env_weak = weak_clone(&parent_env); let child_env = Environment::new_wrapped(); - child_env.borrow_mut().set_parent(parent_env); + child_env.borrow_mut().set_parent(parent_env_weak); child_env.borrow_mut().set("y", 43); assert_eq!( - child_env.borrow().get(&"x".to_string()), - Some(Value::Int(42)) + child_env.borrow().get(&"x".to_string()).unwrap(), + Value::Int(42) ); assert_eq!( - child_env.borrow().get(&"y".to_string()), - Some(Value::Int(43)) + child_env.borrow().get(&"y".to_string()).unwrap(), + Value::Int(43) ); } @@ -217,19 +233,20 @@ mod tests { fn test_update_environment() { let parent_env = Environment::new_wrapped(); parent_env.borrow_mut().set("x", 42); + let parent_env_weak = weak_clone(&parent_env); let child_env = Environment::new_wrapped(); - child_env.borrow_mut().set_parent(parent_env); + child_env.borrow_mut().set_parent(parent_env_weak); child_env.borrow_mut().set("y", 43); child_env.borrow_mut().update("x", 44).unwrap(); assert_eq!( - child_env.borrow().get(&"x".to_string()), - Some(Value::Int(44)) + child_env.borrow().get(&"x".to_string()).unwrap(), + Value::Int(44) ); assert_eq!( - child_env.borrow().get(&"y".to_string()), - Some(Value::Int(43)) + child_env.borrow().get(&"y".to_string()).unwrap(), + Value::Int(43) ); assert!(!child_env.borrow().env.contains_key(&"x".to_string())); } diff --git a/src/bytecode/src/environment/mod.rs b/src/bytecode/src/environment/mod.rs new file mode 100644 index 0000000..82423c0 --- /dev/null +++ b/src/bytecode/src/environment/mod.rs @@ -0,0 +1,7 @@ +pub use env::*; +pub use strong::*; +pub use weak::*; + +mod env; +mod strong; +mod weak; diff --git a/src/bytecode/src/environment/strong.rs b/src/bytecode/src/environment/strong.rs new file mode 100644 index 0000000..974c9ae --- /dev/null +++ b/src/bytecode/src/environment/strong.rs @@ -0,0 +1,23 @@ +use std::{ + cell::RefCell, + hash::{Hash, Hasher}, + rc::Rc, +}; + +use crate::{Environment, W}; + +pub type EnvStrong = W>>; + +impl PartialEq for EnvStrong { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for EnvStrong {} + +impl Hash for EnvStrong { + fn hash(&self, state: &mut H) { + Rc::as_ptr(&self.0).hash(state) + } +} diff --git a/src/bytecode/src/environment/weak.rs b/src/bytecode/src/environment/weak.rs new file mode 100644 index 0000000..7fa7bdd --- /dev/null +++ b/src/bytecode/src/environment/weak.rs @@ -0,0 +1,58 @@ +use std::{ + cell::RefCell, + fmt::Debug, + hash::{Hash, Hasher}, + rc::{Rc, Weak}, +}; + +use crate::{Environment, W}; + +pub type EnvWeak = W>>; + +impl Clone for EnvWeak { + fn clone(&self) -> Self { + W(self.0.clone()) + } +} + +impl Debug for EnvWeak { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let this = self.0.upgrade(); + + match this { + Some(this) => write!(f, "{:?}", this.borrow()), + None => write!(f, "None"), + } + } +} + +impl Default for EnvWeak { + fn default() -> Self { + W(Weak::new()) + } +} + +impl PartialEq for EnvWeak { + fn eq(&self, other: &Self) -> bool { + let this = self.0.upgrade(); + let other = other.0.upgrade(); + + match (this, other) { + (Some(this), Some(other)) => Rc::ptr_eq(&this, &other), + _ => false, + } + } +} + +impl Eq for EnvWeak {} + +impl Hash for EnvWeak { + fn hash(&self, state: &mut H) { + let this = self.0.upgrade(); + + match this { + Some(this) => Rc::as_ptr(&this).hash(state), + None => state.write_usize(0), + } + } +} diff --git a/src/bytecode/src/error.rs b/src/bytecode/src/error.rs index bce25d9..98a35dc 100644 --- a/src/bytecode/src/error.rs +++ b/src/bytecode/src/error.rs @@ -10,4 +10,7 @@ pub enum ByteCodeError { #[error("Unbounded name: {name}")] UnboundedName { name: String }, + + #[error("Environment access after drop")] + EnvironmentDroppedError, } diff --git a/src/bytecode/src/stack_frame.rs b/src/bytecode/src/stack_frame.rs index 46de77a..e6b542c 100644 --- a/src/bytecode/src/stack_frame.rs +++ b/src/bytecode/src/stack_frame.rs @@ -1,8 +1,6 @@ -use std::{cell::RefCell, rc::Rc}; - use serde::{Deserialize, Serialize}; -use crate::Environment; +use crate::EnvWeak; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum FrameType { @@ -14,11 +12,11 @@ pub enum FrameType { pub struct StackFrame { pub frame_type: FrameType, pub address: Option, - pub env: Rc>, + pub env: EnvWeak, } impl StackFrame { - pub fn new(frame_type: FrameType, env: Rc>) -> Self { + pub fn new(frame_type: FrameType, env: EnvWeak) -> Self { StackFrame { frame_type, address: None, @@ -26,11 +24,7 @@ impl StackFrame { } } - pub fn new_with_address( - frame_type: FrameType, - env: Rc>, - address: usize, - ) -> Self { + pub fn new_with_address(frame_type: FrameType, env: EnvWeak, address: usize) -> Self { StackFrame { frame_type, address: Some(address), diff --git a/src/bytecode/src/value.rs b/src/bytecode/src/value.rs index 92a8083..4f92c11 100644 --- a/src/bytecode/src/value.rs +++ b/src/bytecode/src/value.rs @@ -1,11 +1,11 @@ -use std::{cell::RefCell, fmt::Display, rc::Rc}; +use std::fmt::{Debug, Display}; use serde::{Deserialize, Serialize}; -use crate::{ByteCodeError, Environment, Semaphore, Symbol}; +use crate::{ByteCodeError, EnvWeak, Semaphore, Symbol}; /// The values that can be stored on the operant stack. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, PartialEq)] pub enum Value { Unitialized, Unit, @@ -21,7 +21,7 @@ pub enum Value { sym: Symbol, prms: Vec, addr: usize, - env: Rc>, + env: EnvWeak, }, } @@ -62,6 +62,32 @@ impl Display for Value { } } +impl Debug for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let res = match self { + Value::Unitialized => "uninitialized".to_string(), + Value::Unit => "()".to_string(), + Value::String(s) => s.to_string(), + Value::Bool(b) => b.to_string(), + Value::Int(i) => i.to_string(), + Value::Float(f) => f.to_string(), + Value::Semaphore(_) => "semaphore".to_string(), + Value::Closure { + sym, + fn_type, + prms, + addr, + .. + } => format!( + "Closure {{ sym: {}, fn_type: {:?}, prms: {:?}, addr: {} }}", + sym, fn_type, prms, addr + ), + }; + + write!(f, "{}", res) + } +} + impl From for Value { fn from(v: i64) -> Self { Value::Int(v) diff --git a/vm/ignite/src/error.rs b/vm/ignite/src/error.rs index f78468c..459b9b6 100644 --- a/vm/ignite/src/error.rs +++ b/vm/ignite/src/error.rs @@ -44,6 +44,9 @@ pub enum VmError { #[error("Insufficient arguments: expected {expected}, got {got}")] InsufficientArguments { expected: usize, got: usize }, + #[error("Environment access after drop")] + EnvironmentDroppedError, + #[error("Unknown builtin: {sym}")] UnknownBuiltin { sym: String }, } diff --git a/vm/ignite/src/main.rs b/vm/ignite/src/main.rs index 15ba5c1..04d04ba 100644 --- a/vm/ignite/src/main.rs +++ b/vm/ignite/src/main.rs @@ -28,14 +28,19 @@ struct Args { #[arg(long, short)] repl: bool, - /// Set custom time quantum for the VM. - /// Default is 1000. + /// Set custom time quantum for the VM in milliseconds. + /// Default is 100ms. #[arg(short, long)] - quantum: Option, + quantum: Option, + + /// Set custom garbage collection interval for the VM in milliseconds. + /// Default is 1000ms. + #[arg(short, long)] + gc_interval: Option, /// Turn debugging information on - #[arg(short, long, action = clap::ArgAction::Count)] - debug: u8, + #[arg(short, long)] + debug: bool, /// If present, does not type check in REPL. Ignored if only running bytecode. #[arg(short)] @@ -73,10 +78,14 @@ fn main() -> Result<()> { let mut rt = Runtime::new(bytecode_vec); if let Some(quantum) = args.quantum { - rt.set_time_quantum(Duration::from_millis(quantum as u64)); + rt.set_time_quantum(Duration::from_millis(quantum)); + } + + if let Some(gc_interval) = args.gc_interval { + rt.set_gc_interval(Duration::from_millis(gc_interval)); } - if args.debug > 0 { + if args.debug { rt.set_debug_mode(); } diff --git a/vm/ignite/src/micro_code/assign.rs b/vm/ignite/src/micro_code/assign.rs index b2cfe1e..8672348 100644 --- a/vm/ignite/src/micro_code/assign.rs +++ b/vm/ignite/src/micro_code/assign.rs @@ -21,23 +21,29 @@ pub fn assign(mut rt: Runtime, sym: Symbol) -> Result { .operand_stack .pop() .ok_or(VmError::OperandStackUnderflow)?; - rt.current_thread.env.borrow_mut().update(sym, val)?; + rt.current_thread + .env + .upgrade() + .ok_or(VmError::EnvironmentDroppedError)? + .borrow_mut() + .update(sym, val)?; + Ok(rt) } #[cfg(test)] mod tests { - use std::rc::Rc; - - use bytecode::{Environment, Value}; + use bytecode::{weak_clone, Environment, Value}; use super::*; #[test] - fn test_assign() { + fn test_assign() -> Result<()> { let mut rt = Runtime::new(vec![]); rt.current_thread .env + .upgrade() + .unwrap() .borrow_mut() .set("x", Value::Unitialized); rt.current_thread.operand_stack.push(Value::Int(42)); @@ -45,48 +51,63 @@ mod tests { rt = assign(rt, "x".to_string()).unwrap(); assert_ne!( - rt.current_thread.env.borrow().get(&"x".to_string()), - Some(Value::Unitialized) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"x".to_string())?, + Value::Unitialized ); assert_eq!( - rt.current_thread.env.borrow().get(&"x".to_string()), - Some(Value::Int(42)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"x".to_string())?, + Value::Int(42) ); + + Ok(()) } #[test] - fn test_assign_with_parent() { + fn test_assign_with_parent() -> Result<()> { let mut rt = Runtime::new(vec![]); let parent_env = Environment::new_wrapped(); + let parent_weak = weak_clone(&parent_env); parent_env.borrow_mut().set("x", 42); let child_env = Environment::new_wrapped(); - child_env.borrow_mut().set_parent(Rc::clone(&parent_env)); + child_env.borrow_mut().set_parent(parent_weak); child_env.borrow_mut().set("y", Value::Unitialized); + let child_weak = weak_clone(&child_env); - rt.current_thread.env = Rc::clone(&child_env); + rt.current_thread.env = child_weak; rt.current_thread.operand_stack.push(Value::Int(123)); rt = assign(rt, "x".to_string()).unwrap(); - assert_eq!( - parent_env.borrow().get(&"x".to_string()), - Some(Value::Int(123)) - ); + assert_eq!(parent_env.borrow().get(&"x".to_string())?, Value::Int(123)); // The child environment should not be updated. assert!(!child_env.borrow().env.contains_key(&"x".to_string())); rt.current_thread.operand_stack.push(Value::Int(789)); rt = assign(rt, "y".to_string()).unwrap(); - assert!(parent_env.borrow().get(&"y".to_string()).is_none()); - assert_eq!( - child_env.borrow().get(&"y".to_string()), - Some(Value::Int(789)) - ); + assert!(parent_env.borrow().get(&"y".to_string()).is_err()); + assert_eq!(child_env.borrow().get(&"y".to_string())?, Value::Int(789)); assert_eq!( - rt.current_thread.env.borrow().get(&"y".to_string()), - Some(Value::Int(789)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"y".to_string())?, + Value::Int(789) ); + + Ok(()) } } diff --git a/vm/ignite/src/micro_code/call.rs b/vm/ignite/src/micro_code/call.rs index aaea02e..e43a6e5 100644 --- a/vm/ignite/src/micro_code/call.rs +++ b/vm/ignite/src/micro_code/call.rs @@ -1,9 +1,7 @@ -use std::rc::Rc; - use anyhow::Result; use bytecode::{type_of, FnType, FrameType, StackFrame, Value}; -use crate::{Runtime, VmError}; +use crate::{extend_environment, Runtime, VmError}; use super::apply_builtin; @@ -78,12 +76,12 @@ pub fn call(mut rt: Runtime, arity: usize) -> Result { let frame = StackFrame { frame_type: FrameType::CallFrame, - env: Rc::clone(&env), + env: env.clone(), address: Some(rt.current_thread.pc), }; rt.current_thread.runtime_stack.push(frame); - rt.current_thread.extend_environment(prms, args)?; + rt = extend_environment(rt, env.0, prms, args)?; rt.current_thread.pc = addr; Ok(rt) @@ -92,10 +90,10 @@ pub fn call(mut rt: Runtime, arity: usize) -> Result { #[cfg(test)] mod tests { use super::*; - use bytecode::{ByteCode, Environment, FnType}; + use bytecode::{ByteCode, FnType}; #[test] - fn test_call() { + fn test_call() -> Result<()> { let rt = Runtime::new(vec![ByteCode::CALL(0), ByteCode::DONE]); let result = call(rt, 0); assert!(result.is_err()); @@ -106,12 +104,12 @@ mod tests { sym: "Closure".to_string(), prms: vec![], addr: 123, - env: Environment::new_wrapped(), + env: Default::default(), }); - let result = call(rt, 0); - assert!(result.is_ok()); - rt = result.unwrap(); + let rt = call(rt, 0)?; assert_eq!(rt.current_thread.pc, 123); + + Ok(()) } } diff --git a/vm/ignite/src/micro_code/enter_scope.rs b/vm/ignite/src/micro_code/enter_scope.rs index 8e14eba..1a8f0ad 100644 --- a/vm/ignite/src/micro_code/enter_scope.rs +++ b/vm/ignite/src/micro_code/enter_scope.rs @@ -1,9 +1,7 @@ -use std::rc::Rc; - use anyhow::Result; -use bytecode::{FrameType, StackFrame, Symbol, Value}; +use bytecode::{FrameType, StackFrame, Symbol, Value, W}; -use crate::Runtime; +use crate::{extend_environment, Runtime}; /// Create a new scope in the current environment. The new environment will be a child of the current /// environment. All symbols in the new scope will be initialized to `Value::Unitialized`. @@ -18,10 +16,10 @@ use crate::Runtime; /// /// Infallible. pub fn enter_scope(mut rt: Runtime, syms: Vec) -> Result { - let current_env = Rc::clone(&rt.current_thread.env); + let current_env = rt.current_thread.env.clone(); // Preserve the current environment in a stack frame - let frame = StackFrame::new(FrameType::BlockFrame, Rc::clone(¤t_env)); + let frame = StackFrame::new(FrameType::BlockFrame, W(current_env)); // Push the stack frame onto the runtime stack rt.current_thread.runtime_stack.push(frame); @@ -31,7 +29,8 @@ pub fn enter_scope(mut rt: Runtime, syms: Vec) -> Result { .map(|_| Value::Unitialized) .collect::>(); - rt.current_thread.extend_environment(syms, uninitialized)?; + let current_env = rt.current_thread.env.clone(); + rt = extend_environment(rt, current_env, syms, uninitialized)?; Ok(rt) } @@ -43,44 +42,70 @@ mod tests { use super::*; #[test] - fn test_enter_scope() { + fn test_enter_scope() -> Result<()> { let mut rt = Runtime::new(vec![]); - let env = Rc::clone(&rt.current_thread.env); - rt.current_thread.env.borrow_mut().set("a", 42); - rt.current_thread.env.borrow_mut().set("b", 123); + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow_mut() + .set("a", 42); + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow_mut() + .set("b", 123); rt = enter_scope(rt, vec!["c".to_string(), "d".to_string()]).unwrap(); assert_eq!(rt.current_thread.runtime_stack.len(), 1); + assert!(rt + .current_thread + .env + .upgrade() + .unwrap() + .borrow() + .parent + .is_some()); assert_eq!( - rt.current_thread.env.borrow().get(&"a".to_string()), - Some(Value::Int(42)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"a".to_string())?, + Value::Int(42) ); assert_eq!( - rt.current_thread.env.borrow().get(&"b".to_string()), - Some(Value::Int(123)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"b".to_string())?, + Value::Int(123) ); assert_eq!( - rt.current_thread.env.borrow().get(&"c".to_string()), - Some(Value::Unitialized) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"c".to_string())?, + Value::Unitialized ); assert_eq!( - rt.current_thread.env.borrow().get(&"d".to_string()), - Some(Value::Unitialized) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"d".to_string())?, + Value::Unitialized ); - rt.current_thread.env = env; - - assert_eq!( - rt.current_thread.env.borrow().get(&"a".to_string()), - Some(Value::Int(42)) - ); - assert_eq!( - rt.current_thread.env.borrow().get(&"b".to_string()), - Some(Value::Int(123)) - ); - assert_eq!(rt.current_thread.env.borrow().get(&"c".to_string()), None); - assert_eq!(rt.current_thread.env.borrow().get(&"d".to_string()), None); + Ok(()) } } diff --git a/vm/ignite/src/micro_code/exit_scope.rs b/vm/ignite/src/micro_code/exit_scope.rs index 256e708..624c757 100644 --- a/vm/ignite/src/micro_code/exit_scope.rs +++ b/vm/ignite/src/micro_code/exit_scope.rs @@ -18,41 +18,55 @@ pub fn exit_scope(mut rt: Runtime) -> Result { .pop() .ok_or(VmError::RuntimeStackUnderflow)?; - rt.current_thread.env = prev_frame.env; + rt.current_thread.env = prev_frame.env.0; Ok(rt) } #[cfg(test)] mod tests { - use bytecode::{Environment, FrameType, StackFrame, Value}; + use bytecode::{weak_clone, Environment, FrameType, StackFrame, Value, W}; use super::*; #[test] - fn test_exit_scope() { + fn test_exit_scope() -> Result<()> { let mut rt = Runtime::new(vec![]); let env_a = Environment::new_wrapped(); env_a.borrow_mut().set("a", 42); + let env_a_weak = weak_clone(&env_a); let env_b = Environment::new_wrapped(); env_b.borrow_mut().set("a", 123); + let env_b_weak = weak_clone(&env_b); rt.current_thread .runtime_stack - .push(StackFrame::new(FrameType::BlockFrame, env_a)); - rt.current_thread.env = env_b; + .push(StackFrame::new(FrameType::BlockFrame, W(env_a_weak))); + rt.current_thread.env = env_b_weak; assert_eq!( - rt.current_thread.env.borrow().get(&"a".to_string()), - Some(Value::Int(123)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"a".to_string())?, + Value::Int(123) ); rt = exit_scope(rt).unwrap(); assert_eq!(rt.current_thread.runtime_stack.len(), 0); assert_eq!( - rt.current_thread.env.borrow().get(&"a".to_string()), - Some(Value::Int(42)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"a".to_string())?, + Value::Int(42) ); + + Ok(()) } } diff --git a/vm/ignite/src/micro_code/ld.rs b/vm/ignite/src/micro_code/ld.rs index a655708..4a882cc 100644 --- a/vm/ignite/src/micro_code/ld.rs +++ b/vm/ignite/src/micro_code/ld.rs @@ -18,23 +18,30 @@ pub fn ld(mut rt: Runtime, sym: Symbol) -> Result { let val = rt .current_thread .env + .upgrade() + .ok_or(VmError::EnvironmentDroppedError)? .borrow() - .get(&sym) - .ok_or_else(|| VmError::UnboundedName(sym.clone()))?; + .get(&sym)?; + rt.current_thread.operand_stack.push(val); Ok(rt) } #[cfg(test)] mod tests { - use bytecode::{Environment, Value}; + use bytecode::{weak_clone, Environment, Value}; use super::*; #[test] fn test_ld() { let mut rt = Runtime::new(vec![]); - rt.current_thread.env.borrow_mut().set("x".to_string(), 42); + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow_mut() + .set("x".to_string(), 42); rt = ld(rt, "x".to_string()).unwrap(); assert_eq!(rt.current_thread.operand_stack.pop(), Some(Value::Int(42))); } @@ -42,11 +49,13 @@ mod tests { #[test] fn test_ld_with_parent() { let parent = Environment::new_wrapped(); + let parent_weak = weak_clone(&parent); parent.borrow_mut().set("x", 42); let mut rt = Runtime::new(vec![]); - let frame = Environment::new_wrapped(); - frame.borrow_mut().set_parent(parent); - rt.current_thread.env = frame; + let env = Environment::new_wrapped(); + let env_weak = weak_clone(&env); + env.borrow_mut().set_parent(parent_weak); + rt.current_thread.env = env_weak; rt = ld(rt, "x".to_string()).unwrap(); assert_eq!(rt.current_thread.operand_stack.pop(), Some(Value::Int(42))); } diff --git a/vm/ignite/src/micro_code/ldf.rs b/vm/ignite/src/micro_code/ldf.rs index 58fcc54..5ea903e 100644 --- a/vm/ignite/src/micro_code/ldf.rs +++ b/vm/ignite/src/micro_code/ldf.rs @@ -1,7 +1,5 @@ -use std::rc::Rc; - use anyhow::Result; -use bytecode::{FnType, Symbol, Value}; +use bytecode::{FnType, Symbol, Value, W}; use crate::Runtime; @@ -24,7 +22,7 @@ pub fn ldf(mut rt: Runtime, addr: usize, prms: Vec) -> Result { sym: "Closure".to_string(), prms, addr, - env: Rc::clone(&rt.current_thread.env), + env: W(rt.current_thread.env.clone()), }; rt.current_thread.operand_stack.push(closure); @@ -48,7 +46,7 @@ mod tests { sym: "Closure".to_string(), prms: vec!["y".to_string()], addr: 0, - env: Rc::clone(&rt.current_thread.env), + env: W(rt.current_thread.env.clone()), } ) } diff --git a/vm/ignite/src/micro_code/post.rs b/vm/ignite/src/micro_code/post.rs index d54292a..3645f58 100644 --- a/vm/ignite/src/micro_code/post.rs +++ b/vm/ignite/src/micro_code/post.rs @@ -51,6 +51,7 @@ pub fn post(mut rt: Runtime) -> Result { #[cfg(test)] mod tests { use crate::{ + extend_environment, micro_code::{ld, spawn, wait, yield_}, MAIN_THREAD_ID, }; @@ -61,8 +62,8 @@ mod tests { fn test_post_01() -> Result<()> { let mut rt = Runtime::default(); let sem = Semaphore::new(0); - rt.current_thread - .extend_environment(vec!["sem"], vec![sem.clone()])?; + let current_env = rt.current_thread.env.clone(); + rt = extend_environment(rt, current_env, vec!["sem"], vec![sem.clone()])?; rt = spawn(rt, 0)?; // spawn a child thread to populate ready queue rt = ld(rt, "sem".into())?; rt = post(rt)?; @@ -79,8 +80,8 @@ mod tests { fn test_post_02() -> Result<()> { let mut rt = Runtime::default(); let sem = Semaphore::new(0); - rt.current_thread - .extend_environment(vec!["sem"], vec![sem.clone()])?; + let current_env = rt.current_thread.env.clone(); + rt = extend_environment(rt, current_env, vec!["sem"], vec![sem.clone()])?; rt = spawn(rt, 0)?; // spawn a child thread to populate ready queue rt = yield_(rt)?; // yield the current thread to child thread rt = ld(rt, "sem".into())?; diff --git a/vm/ignite/src/micro_code/reset.rs b/vm/ignite/src/micro_code/reset.rs index 4fc73b8..41f8cda 100644 --- a/vm/ignite/src/micro_code/reset.rs +++ b/vm/ignite/src/micro_code/reset.rs @@ -30,7 +30,7 @@ pub fn reset(mut rt: Runtime, ft: FrameType) -> Result { rt.current_thread.pc = address; } - rt.current_thread.env = frame.env; + rt.current_thread.env = frame.env.0; break; } @@ -39,23 +39,24 @@ pub fn reset(mut rt: Runtime, ft: FrameType) -> Result { #[cfg(test)] mod tests { - use std::rc::Rc; - use super::*; - use bytecode::{ByteCode, Environment, FrameType, StackFrame, Value}; + use bytecode::{weak_clone, ByteCode, Environment, FrameType, StackFrame, Value, W}; #[test] - fn test_reset_restore_env() { + fn test_reset_restore_env() -> Result<()> { let mut rt = Runtime::new(vec![ByteCode::RESET(FrameType::BlockFrame)]); let env_a = Environment::new_wrapped(); let env_b = Environment::new_wrapped(); let env_c = Environment::new_wrapped(); + let env_a_weak = W(weak_clone(&env_a)); + let env_b_weak = W(weak_clone(&env_c)); + let env_c_weak = W(weak_clone(&env_b)); env_c.borrow_mut().set("a", 42); - let some_frame = StackFrame::new(FrameType::CallFrame, Rc::clone(&env_a)); - let block_frame = StackFrame::new(FrameType::BlockFrame, Rc::clone(&env_c)); - let call_frame = StackFrame::new(FrameType::CallFrame, Rc::clone(&env_b)); + let some_frame = StackFrame::new(FrameType::CallFrame, env_a_weak); + let block_frame = StackFrame::new(FrameType::BlockFrame, env_b_weak); + let call_frame = StackFrame::new(FrameType::CallFrame, env_c_weak); rt.current_thread.runtime_stack.push(some_frame); rt.current_thread.runtime_stack.push(block_frame); @@ -67,9 +68,16 @@ mod tests { assert!(rt.current_thread.runtime_stack.len() == 1); assert_eq!( - rt.current_thread.env.borrow().get(&"a".to_string()), - Some(Value::Int(42)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"a".to_string())?, + Value::Int(42) ); + + Ok(()) } #[test] @@ -79,12 +87,14 @@ mod tests { let env_a = Environment::new_wrapped(); let env_b = Environment::new_wrapped(); let env_c = Environment::new_wrapped(); + let env_a_weak = W(weak_clone(&env_a)); + let env_b_weak = W(weak_clone(&env_c)); + let env_c_weak = W(weak_clone(&env_b)); env_c.borrow_mut().set("a", 42); - let some_frame = StackFrame::new(FrameType::CallFrame, Rc::clone(&env_a)); - let block_frame = - StackFrame::new_with_address(FrameType::BlockFrame, Rc::clone(&env_c), 123); - let call_frame = StackFrame::new(FrameType::CallFrame, Rc::clone(&env_b)); + let some_frame = StackFrame::new(FrameType::CallFrame, env_a_weak); + let block_frame = StackFrame::new_with_address(FrameType::BlockFrame, env_c_weak, 123); + let call_frame = StackFrame::new(FrameType::CallFrame, env_b_weak); rt.current_thread.runtime_stack.push(some_frame); rt.current_thread.runtime_stack.push(block_frame); diff --git a/vm/ignite/src/micro_code/wait.rs b/vm/ignite/src/micro_code/wait.rs index 162ae0a..9087dd9 100644 --- a/vm/ignite/src/micro_code/wait.rs +++ b/vm/ignite/src/micro_code/wait.rs @@ -55,6 +55,7 @@ pub fn wait(mut rt: Runtime) -> Result { #[cfg(test)] mod tests { use crate::{ + extend_environment, micro_code::{self, ld}, MAIN_THREAD_ID, }; @@ -65,8 +66,8 @@ mod tests { fn test_wait_01() -> Result<()> { let mut rt = Runtime::default(); let sem = Semaphore::new(1); - rt.current_thread - .extend_environment(vec!["sem"], vec![sem.clone()])?; + let current_env = rt.current_thread.env.clone(); + rt = extend_environment(rt, current_env, vec!["sem"], vec![sem.clone()])?; rt = micro_code::spawn(rt, 0)?; // spawn a child thread to populate ready queue rt = ld(rt, "sem".into())?; rt = wait(rt)?; @@ -82,8 +83,8 @@ mod tests { fn test_wait_02() -> Result<()> { let mut rt = Runtime::default(); let sem = Semaphore::new(0); - rt.current_thread - .extend_environment(vec!["sem"], vec![sem.clone()])?; + let current_env = rt.current_thread.env.clone(); + rt = extend_environment(rt, current_env, vec!["sem"], vec![sem.clone()])?; rt = micro_code::spawn(rt, 0)?; // spawn a child thread to populate ready queue rt = ld(rt, "sem".into())?; rt = wait(rt)?; diff --git a/vm/ignite/src/runtime/gc.rs b/vm/ignite/src/runtime/gc.rs new file mode 100644 index 0000000..9162964 --- /dev/null +++ b/vm/ignite/src/runtime/gc.rs @@ -0,0 +1,240 @@ +use std::{cell::RefCell, collections::HashMap, rc::Weak}; + +use bytecode::{weak_clone, EnvWeak, Environment, StackFrame, Value, W}; + +use crate::{Runtime, Thread}; + +/// Runtime methods at runtime. +impl Runtime { + /// Mark and sweep the environment registry. + /// This will remove all environments that are no longer referenced. + /// + /// - Mark environment x -> env_registry.get(x) = true + /// - Sweep environment x -> env_registry.remove(x) if env_registry.get(x) = false + /// - Clean up -> reset env_registry.get(x) = false + /// + /// Traverse through all the threads, for each thread: + /// - Mark its current environment and the environment of closure values in the current environment, + /// and the chain of parent environments. + /// - Go through the runtime stack and mark all the environments and environment of closure values in + /// their respective environment, and the chain of parent environments + /// - Go through the operand stack and mark all the environments of closure values, and the chain of parent environments + pub fn mark_and_weep(self) -> Self { + let marked = mark(&self); + sweep(self, marked) + } +} + +fn mark(rt: &Runtime) -> HashMap { + if rt.debug { + println!("Mark begin") + } + + let mut marked = env_hashmap(rt); + + // Mark the current thread + marked = mark_thread(marked, &rt.current_thread); + + // Mark the ready queue + for thread in rt.ready_queue.iter() { + marked = mark_thread(marked, thread); + } + + // Mark the blocked queue + for (thread, _) in rt.blocked_queue.iter() { + marked = mark_thread(marked, thread); + } + + // Zombie threads will be ignored + + marked +} + +fn sweep(mut rt: Runtime, m: HashMap) -> Runtime { + if rt.debug { + println!("Sweep begin") + } + + let registry = rt + .env_registry + .drain() + .filter(|env| *m.get(&W(weak_clone(env))).unwrap_or(&false)) + .collect(); + rt.env_registry = registry; + + if rt.debug { + println!( + "Sweep end, {} environments removed", + m.len() - rt.env_registry.len() + ) + } + + rt // Any environment that is not marked will be removed from the registry and dropped +} + +fn env_hashmap(rt: &Runtime) -> HashMap { + let mut m = HashMap::new(); + for env in rt.env_registry.iter() { + m.insert(W(weak_clone(env)), false); + } + m +} + +fn mark_thread(mut m: HashMap, t: &Thread) -> HashMap { + m = mark_env(m, &t.env); + m = mark_operand_stack(m, &t.operand_stack); + m = mark_runtime_stack(m, &t.runtime_stack); + m +} + +fn mark_env( + mut m: HashMap, + env: &Weak>, +) -> HashMap { + let is_marked = m + .get_mut(&W(env.clone())) + .expect("Environment must be in the registry"); + + match is_marked { + true => return m, // Already marked + false => *is_marked = true, + } + + let env = env + .upgrade() + .expect("Environment must still be referenced to be marked"); + + if let Some(parent) = &env.borrow().parent { + m = mark_env(m, parent); + } + + m +} + +fn mark_operand_stack(mut m: HashMap, os: &[Value]) -> HashMap { + for val in os.iter() { + if let Value::Closure { env, .. } = val { + m = mark_env(m, env); + } + } + m +} + +fn mark_runtime_stack(mut m: HashMap, rs: &[StackFrame]) -> HashMap { + for frame in rs.iter() { + m = mark_env(m, &frame.env); + } + m +} + +#[cfg(test)] +mod tests { + use crate::run; + + use super::*; + + use anyhow::Result; + use bytecode::*; + + #[test] + fn test_gc_01() -> Result<()> { + // { + // fn garbage() {} + // } + // // garbage is out of scope and not reachable, so the environment should be removed + let empty_vec: Vec = vec![]; + + let instrs = vec![ + ByteCode::enterscope(empty_vec.clone()), // Program scope + ByteCode::enterscope(vec!["garbage"]), // Block scope + ByteCode::ldf(0, empty_vec.clone()), + ByteCode::assign("garbage"), + ByteCode::EXITSCOPE, + ByteCode::EXITSCOPE, + ByteCode::DONE, + ]; + + let mut rt = Runtime::new(instrs); + rt.set_debug_mode(); + let rt = run(rt)?; + assert_eq!(rt.env_registry.len(), 3); // Global env, program env, block env + + let rt = rt.mark_and_weep(); + assert_eq!(rt.env_registry.len(), 1); // Only the global environment should be left + + Ok(()) + } + + #[test] + fn test_gc_02() -> Result<()> { + // fn higher_order(x) { + // return y => x + y; + // } + // + // const add10 = higher_order(10); + // + // const result = add10(20); + // + // println(result); // 30 + + let instrs = vec![ + // PC: 0 + ByteCode::enterscope(vec!["higher_order", "add10", "result"]), // Program scope + // PC: 1 + ByteCode::ldf(4, vec!["x"]), // higher_order + // PC: 2 + ByteCode::assign("higher_order"), + // PC: 3 + ByteCode::GOTO(11), // Jump past higher_order body + // PC: 4 + ByteCode::ldf(6, vec!["y"]), // higher_order annonymous function + // PC: 5 + ByteCode::GOTO(10), // Jump past annonymous function body + // PC: 6 + ByteCode::ld("x"), + // PC: 7 + ByteCode::ld("y"), + // PC: 8 + ByteCode::BINOP(BinOp::Add), + // PC: 9 + ByteCode::RESET(FrameType::CallFrame), // reset instruction for annonymous function + // PC: 10 + ByteCode::RESET(FrameType::CallFrame), // reset instruction for higher_order + // PC: 11 + ByteCode::ld("higher_order"), + // PC: 12 + ByteCode::ldc(10), + // PC: 13 + ByteCode::CALL(1), + // PC: 14 + ByteCode::assign("add10"), + // PC: 15 + ByteCode::ld("add10"), + // PC: 16 + ByteCode::ldc(20), + // PC: 17 + ByteCode::CALL(1), + // PC: 18 + ByteCode::assign("result"), + // PC: 19 + ByteCode::ld("println"), + // PC: 20 + ByteCode::ld("result"), + // PC: 21 + ByteCode::CALL(1), + // PC: 22 + ByteCode::EXITSCOPE, + // PC: 23 + ByteCode::DONE, + ]; + + let mut rt = Runtime::new(instrs); + rt.set_debug_mode(); + let rt = run(rt)?; + + let rt = rt.mark_and_weep(); + assert_eq!(rt.env_registry.len(), 1); // Only the global environment should be left + + Ok(()) + } +} diff --git a/vm/ignite/src/runtime/mod.rs b/vm/ignite/src/runtime/mod.rs new file mode 100644 index 0000000..a6e9483 --- /dev/null +++ b/vm/ignite/src/runtime/mod.rs @@ -0,0 +1,98 @@ +use std::{ + collections::{HashMap, HashSet, VecDeque}, + time::{Duration, Instant}, +}; + +use bytecode::{weak_clone, ByteCode, EnvStrong, Environment, Semaphore, ThreadID, W}; + +use crate::Thread; +pub use run::*; + +mod gc; +mod run; + +pub const DEFAULT_TIME_QUANTUM: Duration = Duration::from_millis(100); +pub const DEFAULT_GC_INTERVAL: Duration = Duration::from_secs(1); +pub const MAIN_THREAD_ID: i64 = 1; + +/// The runtime of the virtual machine. +/// It contains the instructions to execute, the current thread, and the ready and blocked threads. +/// The instructions are the bytecode instructions to execute. +/// The ready queue is a queue of threads that are ready to run. +/// The blocked queue is a queue of threads that are waiting for some event to occur. +/// The zombie threads are threads that have finished executing and are waiting to be joined. +pub struct Runtime { + /// If the program is done. + pub done: bool, + /// If the program is in debug mode. + pub debug: bool, + /// The time the program started, used for calculating the time quantum. + pub time: Instant, + /// The maximum amount of time a thread can run before it is preempted. + pub time_quantum: Duration, + /// The time the garbage collector was last run. + pub gc_timer: Instant, + /// The interval at which to run the mark and sweep garbage collector. + pub gc_interval: Duration, + /// The instructions to execute. + pub instrs: Vec, + /// The environment registry, holds strong references to environments. + pub env_registry: HashSet, + /// The number of threads that have been created. + pub thread_count: i64, + /// The current thread that is executing. + pub current_thread: Thread, + /// The threads that are ready to run. + pub ready_queue: VecDeque, + /// The threads that are blocked. + pub blocked_queue: VecDeque<(Thread, Semaphore)>, + /// The threads that have finished executing, waiting to be joined. + pub zombie_threads: HashMap, +} + +/// Constructors for the runtime. +impl Runtime { + pub fn new(instrs: Vec) -> Self { + let global_env = Environment::new_global_wrapped(); + let global_env_weak = weak_clone(&global_env); + let mut envs = HashSet::new(); + envs.insert(W(global_env)); + + Runtime { + debug: false, + done: false, + time: Instant::now(), + time_quantum: DEFAULT_TIME_QUANTUM, + gc_timer: Instant::now(), + gc_interval: DEFAULT_GC_INTERVAL, + instrs, + env_registry: envs, + thread_count: 1, + current_thread: Thread::new(MAIN_THREAD_ID, global_env_weak), + ready_queue: VecDeque::new(), + blocked_queue: VecDeque::new(), + zombie_threads: HashMap::new(), + } + } +} + +impl Default for Runtime { + fn default() -> Self { + Runtime::new(vec![]) + } +} + +/// Configuration for the runtime. +impl Runtime { + pub fn set_time_quantum(&mut self, time_quantum: Duration) { + self.time_quantum = time_quantum; + } + + pub fn set_gc_interval(&mut self, gc_interval: Duration) { + self.gc_interval = gc_interval; + } + + pub fn set_debug_mode(&mut self) { + self.debug = true; + } +} diff --git a/vm/ignite/src/runtime.rs b/vm/ignite/src/runtime/run.rs similarity index 91% rename from vm/ignite/src/runtime.rs rename to vm/ignite/src/runtime/run.rs index 5e16e95..371c9ca 100644 --- a/vm/ignite/src/runtime.rs +++ b/vm/ignite/src/runtime/run.rs @@ -1,63 +1,64 @@ -use std::{ - collections::{HashMap, VecDeque}, - time::{Duration, Instant}, -}; +use std::time::Instant; use anyhow::Result; -use bytecode::{ByteCode, Semaphore, ThreadID}; - -use crate::{micro_code, Thread, VmError}; - -pub const DEFAULT_TIME_QUANTUM: Duration = Duration::from_millis(100); -pub const MAIN_THREAD_ID: i64 = 1; - -/// The runtime of the virtual machine. -/// It contains the instructions to execute, the current thread, and the ready and blocked threads. -/// The instructions are the bytecode instructions to execute. -/// The ready queue is a queue of threads that are ready to run. -/// The blocked queue is a queue of threads that are waiting for some event to occur. -/// The zombie threads are threads that have finished executing and are waiting to be joined. -pub struct Runtime { - pub done: bool, - pub debug: bool, - pub time: Instant, - pub time_quantum: Duration, - pub instrs: Vec, - pub thread_count: i64, - pub current_thread: Thread, - pub ready_queue: VecDeque, - pub blocked_queue: VecDeque<(Thread, Semaphore)>, - pub zombie_threads: HashMap, -} +use bytecode::ByteCode; + +use crate::{micro_code, Runtime, VmError}; +/// Runtime methods at runtime. impl Runtime { - pub fn new(instrs: Vec) -> Self { - Runtime { - time: Instant::now(), - time_quantum: DEFAULT_TIME_QUANTUM, - instrs, - debug: false, - done: false, - thread_count: 1, - current_thread: Thread::new(MAIN_THREAD_ID), - ready_queue: VecDeque::new(), - blocked_queue: VecDeque::new(), - zombie_threads: HashMap::new(), - } + /// Fetch the next instruction to execute. + /// This will increment the program counter of the current thread. + /// + /// # Returns + /// + /// The next instruction to execute. + /// + /// # Errors + /// + /// If the program counter is out of bounds. + pub fn fetch_instr(&mut self) -> Result { + let instr = self + .instrs + .get(self.current_thread.pc) + .cloned() + .ok_or(VmError::PcOutOfBounds(self.current_thread.pc))?; + self.current_thread.pc += 1; + Ok(instr) + } + /// Check if the time quantum has expired. + /// The time quantum is the maximum amount of time a thread can run before it is preempted. + pub fn time_quantum_expired(&self) -> bool { + self.time.elapsed() >= self.time_quantum } - pub fn set_time_quantum(&mut self, time_quantum: Duration) { - self.time_quantum = time_quantum; + pub fn should_garbage_collect(&self) -> bool { + self.gc_timer.elapsed() >= self.gc_interval } - pub fn set_debug_mode(&mut self) { - self.debug = true; + pub fn garbage_collect(mut self) -> Self { + self = self.mark_and_weep(); + self.gc_timer = Instant::now(); + self } -} -impl Default for Runtime { - fn default() -> Self { - Runtime::new(vec![]) + /// The program is done if the current thread is the main thread and the current thread is done. + pub fn is_done(&self) -> bool { + self.done + } + + pub fn debug_print(&self) { + let thread_id = self.current_thread.thread_id; + let pc = self.current_thread.pc; + let instruction = self.instrs.get(pc).expect("PC out of bounds"); + println!("Thread: {}, PC: {}, {:?}", thread_id, pc, instruction); + println!("Operand Stack: {:?}", self.current_thread.operand_stack); + println!("Runtime Stack: {:?}", self.current_thread.runtime_stack); + println!( + "Environment: {:?}", + self.current_thread.env.upgrade().unwrap().borrow() + ); + println!(); } } @@ -80,20 +81,21 @@ pub fn run(mut rt: Runtime) -> Result { break; } + if rt.should_garbage_collect() { + rt = rt.garbage_collect(); + } + if rt.time_quantum_expired() { rt = micro_code::yield_(rt)?; continue; } - let instr = rt.fetch_instr()?; - if rt.debug { - let thread_id = rt.current_thread.thread_id; - let pc = rt.current_thread.pc - 1; - let instruction = instr.clone(); - println!("Thread: {}, PC: {}, {:?}", thread_id, pc, instruction); + rt.debug_print(); } + let instr = rt.fetch_instr()?; + rt = execute(rt, instr)?; } @@ -140,41 +142,11 @@ pub fn execute(rt: Runtime, instr: ByteCode) -> Result { } } -impl Runtime { - /// Fetch the next instruction to execute. - /// This will increment the program counter of the current thread. - /// - /// # Returns - /// - /// The next instruction to execute. - /// - /// # Errors - /// - /// If the program counter is out of bounds. - pub fn fetch_instr(&mut self) -> Result { - let instr = self - .instrs - .get(self.current_thread.pc) - .cloned() - .ok_or(VmError::PcOutOfBounds(self.current_thread.pc))?; - self.current_thread.pc += 1; - Ok(instr) - } - /// Check if the time quantum has expired. - /// The time quantum is the maximum amount of time a thread can run before it is preempted. - pub fn time_quantum_expired(&self) -> bool { - self.time.elapsed() >= self.time_quantum - } - - /// The program is done if the current thread is the main thread and the current thread is done. - pub fn is_done(&self) -> bool { - self.done - } -} - #[cfg(test)] mod tests { - use std::vec; + use std::time::Duration; + + use crate::MAIN_THREAD_ID; use super::*; use anyhow::{Ok, Result}; @@ -261,7 +233,7 @@ mod tests { } #[test] - fn test_assignment() { + fn test_assignment() -> Result<()> { let instrs = vec![ ByteCode::ldc(42), ByteCode::assign("x"), @@ -275,22 +247,38 @@ mod tests { let rt = Runtime::new(instrs); rt.current_thread .env + .upgrade() + .unwrap() .borrow_mut() .set("x", Value::Unitialized); rt.current_thread .env + .upgrade() + .unwrap() .borrow_mut() .set("y", Value::Unitialized); let rt = run(rt).unwrap(); assert_eq!( - rt.current_thread.env.borrow().get(&"x".to_string()), - Some(Value::Int(44)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"x".to_string())?, + Value::Int(44) ); assert_eq!( - rt.current_thread.env.borrow().get(&"y".to_string()), - Some(Value::Int(43)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"y".to_string())?, + Value::Int(43) ); + + Ok(()) } #[test] @@ -481,6 +469,8 @@ mod tests { let final_count: i64 = rt .current_thread .env + .upgrade() + .unwrap() .borrow() .get(&"count".to_string()) .expect("Count not in environment") @@ -632,6 +622,8 @@ mod tests { let final_count: i64 = rt .current_thread .env + .upgrade() + .unwrap() .borrow() .get(&"count".to_string()) .expect("Count not in environment") @@ -801,6 +793,8 @@ mod tests { let final_count: i64 = rt .current_thread .env + .upgrade() + .unwrap() .borrow() .get(&"count".to_string()) .expect("Count not in environment") @@ -819,6 +813,8 @@ mod tests { let final_count: i64 = rt .current_thread .env + .upgrade() + .unwrap() .borrow() .get(&"count".to_string()) .expect("Count not in environment") diff --git a/vm/ignite/src/thread.rs b/vm/ignite/src/thread.rs index ae374b1..14e3aaf 100644 --- a/vm/ignite/src/thread.rs +++ b/vm/ignite/src/thread.rs @@ -1,26 +1,26 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, rc::Weak}; use anyhow::Result; -use bytecode::{Environment, StackFrame, Symbol, ThreadID, Value}; +use bytecode::{weak_clone, Environment, StackFrame, Symbol, ThreadID, Value, W}; -use crate::VmError; +use crate::{Runtime, VmError}; /// A thread of execution. /// Each thread has its own environment, operand stack, runtime stack, and program counter. #[derive(Debug, Default, Clone)] pub struct Thread { pub thread_id: ThreadID, - pub env: Rc>, + pub env: Weak>, pub operand_stack: Vec, pub runtime_stack: Vec, pub pc: usize, } impl Thread { - pub fn new(thread_id: i64) -> Self { + pub fn new(thread_id: i64, env: Weak>) -> Self { Thread { thread_id, - env: Environment::new_global(), + env, operand_stack: Vec::new(), runtime_stack: Vec::new(), ..Default::default() @@ -32,7 +32,7 @@ impl Thread { pub fn spawn_child(&self, thread_id: i64, pc: usize) -> Self { Thread { thread_id, - env: Rc::clone(&self.env), + env: Weak::clone(&self.env), operand_stack: Vec::new(), runtime_stack: Vec::new(), pc, @@ -40,42 +40,34 @@ impl Thread { } } -impl Thread { - /// Extend the current environment of the thread with new symbols and values. - /// - /// # Arguments - /// - /// * `syms` - The symbols to add to the environment. - /// - /// * `vals` - The values to add to the environment. - /// - /// # Errors - /// - /// If the symbols and values are not the same length. - pub fn extend_environment(&mut self, syms: Vec, vals: Vec) -> Result<()> - where - S: Into, - V: Into, - { - if syms.len() != vals.len() { - return Err(VmError::IllegalArgument( - "symbols and values must be the same length".to_string(), - ) - .into()); - } +pub fn extend_environment( + mut rt: Runtime, + env: Weak>, + syms: Vec, + vals: Vec, +) -> Result +where + S: Into, + V: Into, +{ + if syms.len() != vals.len() { + return Err(VmError::IllegalArgument( + "symbols and values must be the same length".to_string(), + ) + .into()); + } - let current_env = Rc::clone(&self.env); - let new_env = Environment::new_wrapped(); - new_env.borrow_mut().set_parent(current_env); + let new_env = Environment::new_wrapped(); + new_env.borrow_mut().set_parent(env); - for (sym, val) in syms.into_iter().zip(vals.into_iter()) { - new_env.borrow_mut().set(sym, val); - } + for (sym, val) in syms.into_iter().zip(vals.into_iter()) { + new_env.borrow_mut().set(sym, val); + } - self.env = new_env; + rt.current_thread.env = weak_clone(&new_env); + rt.env_registry.insert(W(new_env)); - Ok(()) - } + Ok(rt) } #[cfg(test)] @@ -84,25 +76,97 @@ mod tests { use bytecode::Value; #[test] - fn test_extend_environment() -> Result<()> { - let mut t = Thread::new(1); - t.env.borrow_mut().set("a", 42); - t.env.borrow_mut().set("b", 123); + fn test_extend_environment_err() -> Result<()> { + let mut rt = Runtime::default(); + let env = Environment::new_wrapped(); + rt.current_thread.env = weak_clone(&env); + + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow_mut() + .set("a", 42); + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow_mut() + .set("b", 123); let empty: Vec = Vec::new(); - assert!(t.extend_environment(vec!["c", "d"], empty).is_err()); + let current_env = rt.current_thread.env.clone(); + let result = extend_environment(rt, current_env, vec!["c", "d"], empty); + assert!(result.is_err()); + + Ok(()) + } + + #[test] + fn test_extend_environment() -> Result<()> { + let mut rt = Runtime::default(); + let env = Environment::new_wrapped(); + rt.current_thread.env = weak_clone(&env); + + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow_mut() + .set("a", 42); + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow_mut() + .set("b", 123); + + let current_env = rt.current_thread.env.clone(); + let rt = extend_environment( + rt, + current_env, + vec!["c", "d"], + vec![Value::Float(12.3), Value::Bool(true)], + )?; + + assert_eq!( + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"a".to_string())?, + Value::Int(42) + ); - t.extend_environment(vec!["c", "d"], vec![Value::Float(12.3), Value::Bool(true)])?; + assert_eq!( + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"b".to_string())?, + Value::Int(123) + ); - assert_eq!(t.env.borrow().get(&"a".to_string()), Some(Value::Int(42))); - assert_eq!(t.env.borrow().get(&"b".to_string()), Some(Value::Int(123))); assert_eq!( - t.env.borrow().get(&"c".to_string()), - Some(Value::Float(12.3)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"c".to_string())?, + Value::Float(12.3) ); + assert_eq!( - t.env.borrow().get(&"d".to_string()), - Some(Value::Bool(true)) + rt.current_thread + .env + .upgrade() + .unwrap() + .borrow() + .get(&"d".to_string())?, + Value::Bool(true) ); Ok(())