Refactor: Use Rc/RefCell for shared mutable state

This commit replaces `Arc<Mutex<T>>` with `Rc<RefCell<T>>` for managing
shared mutable state within the AST and VM. This change is primarily an
internal refactoring to leverage Rust's standard library more
effectively for single-threaded scenarios, improving performance by
avoiding the overhead of mutexes.

The following types and their usage have been updated:
- `Environment.global_names` and `Environment.global_values`
- `Binder.globals`
- `bound_nodes::BoundKind::Lambda.body` (now `Rc<Node>`)
- `ast::types::NodeIdentity` (now `Rc<NodeIdentity>`)
- `ast::types::Value::List`, `Value::Record`, `Value::Function`,
  `Value::Object`, `Value::Cell`
- `ast::nodes::Scope` and `ast::nodes::Context`
- `ast::vm::Closure.function_node` and `Closure.upvalues`
- `ast::vm::VM.globals`

This change does not alter the external behavior of the library but
streamlines internal data management.
This commit is contained in:
Michael Schimmel
2026-02-17 13:00:25 +01:00
parent ce166f39e3
commit d55422272b
9 changed files with 112 additions and 148 deletions
+26 -22
View File
@@ -1,4 +1,5 @@
use std::sync::{Arc, Mutex};
use std::rc::Rc;
use std::cell::RefCell;
use std::any::Any;
use crate::ast::compiler::bound_nodes::{Address, BoundKind};
use crate::ast::nodes::Node;
@@ -6,8 +7,8 @@ use crate::ast::types::{Value, Object, StaticType};
#[derive(Debug, Clone)]
pub struct Closure {
pub function_node: Arc<Node<BoundKind, StaticType>>,
pub upvalues: Vec<Arc<Mutex<Value>>>,
pub function_node: Rc<Node<BoundKind, StaticType>>,
pub upvalues: Vec<Rc<RefCell<Value>>>,
}
impl Object for Closure {
@@ -22,17 +23,20 @@ impl Object for Closure {
#[derive(Debug)]
struct CallFrame {
stack_base: usize,
closure: Option<Arc<Closure>>,
closure: Option<Rc<Closure>>,
}
pub struct VM {
stack: Vec<Value>,
globals: Arc<Mutex<Vec<Value>>>,
// Globals are mutable shared state within the VM thread.
// However, if we move to frozen roots, this might change to a read-only structure.
// For now, mutable RefCell Vec is fine for single threaded execution.
globals: Rc<RefCell<Vec<Value>>>,
frames: Vec<CallFrame>,
}
impl VM {
pub fn new(globals: Arc<Mutex<Vec<Value>>>) -> Self {
pub fn new(globals: Rc<RefCell<Vec<Value>>>) -> Self {
Self {
stack: Vec::new(),
globals,
@@ -64,7 +68,7 @@ impl VM {
BoundKind::DefGlobal { global_index, value } => {
let val = self.eval(value)?;
let idx = *global_index as usize;
let mut globals = self.globals.lock().unwrap();
let mut globals = self.globals.borrow_mut();
if idx >= globals.len() {
globals.resize(idx + 1, Value::Void);
}
@@ -110,7 +114,7 @@ impl VM {
upvalues: captured,
};
Ok(Value::Object(Arc::new(closure)))
Ok(Value::Object(Rc::new(closure)))
},
BoundKind::Call { callee, args } => {
@@ -126,13 +130,13 @@ impl VM {
Value::Object(obj) => {
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
let old_stack_top = self.stack.len();
let closure_arc = Arc::new(closure.clone());
let closure_rc = Rc::new(closure.clone());
self.stack.extend(arg_vals);
self.frames.push(CallFrame {
stack_base: old_stack_top,
closure: Some(closure_arc.clone()),
closure: Some(closure_rc.clone()),
});
let result = self.eval(&closure.function_node);
@@ -151,7 +155,7 @@ impl VM {
}
}
fn capture_upvalue(&mut self, addr: Address) -> Result<Arc<Mutex<Value>>, String> {
fn capture_upvalue(&mut self, addr: Address) -> Result<Rc<RefCell<Value>>, String> {
match addr {
Address::Local(idx) => {
let frame = self.frames.last().ok_or("No call frame")?;
@@ -163,7 +167,7 @@ impl VM {
} else {
// Box it
let val = self.stack[abs_index].clone();
let cell = Arc::new(Mutex::new(val));
let cell = Rc::new(RefCell::new(val));
self.stack[abs_index] = Value::Cell(cell.clone());
Ok(cell)
}
@@ -195,7 +199,7 @@ impl VM {
let abs_index = frame.stack_base + (idx as usize);
if abs_index < self.stack.len() {
match &self.stack[abs_index] {
Value::Cell(cell) => Ok(cell.lock().unwrap().clone()),
Value::Cell(cell) => Ok(cell.borrow().clone()),
val => Ok(val.clone()),
}
} else {
@@ -204,7 +208,7 @@ impl VM {
},
Address::Global(idx) => {
let idx = idx as usize;
let globals = self.globals.lock().unwrap();
let globals = self.globals.borrow();
if idx < globals.len() {
Ok(globals[idx].clone())
} else {
@@ -216,7 +220,7 @@ impl VM {
if let Some(closure) = &frame.closure {
let idx = idx as usize;
if idx < closure.upvalues.len() {
Ok(closure.upvalues[idx].lock().unwrap().clone())
Ok(closure.upvalues[idx].borrow().clone())
} else {
Err(format!("Upvalue access out of bounds {}", idx))
}
@@ -234,7 +238,7 @@ impl VM {
let abs_index = frame.stack_base + (idx as usize);
if abs_index < self.stack.len() {
if let Value::Cell(cell) = &self.stack[abs_index] {
*cell.lock().unwrap() = value;
*cell.borrow_mut() = value;
} else {
self.stack[abs_index] = value;
}
@@ -247,7 +251,7 @@ impl VM {
},
Address::Global(idx) => {
let idx = idx as usize;
let mut globals = self.globals.lock().unwrap();
let mut globals = self.globals.borrow_mut();
if idx >= globals.len() {
globals.resize(idx + 1, Value::Void);
}
@@ -259,7 +263,7 @@ impl VM {
if let Some(closure) = &frame.closure {
let idx = idx as usize;
if idx < closure.upvalues.len() {
*closure.upvalues[idx].lock().unwrap() = value;
*closure.upvalues[idx].borrow_mut() = value;
Ok(())
} else {
Err(format!("Upvalue assignment out of bounds {}", idx))
@@ -277,8 +281,8 @@ mod tests {
use super::*;
use crate::ast::types::{SourceLocation, NodeIdentity};
fn make_dummy_identity() -> Arc<NodeIdentity> {
Arc::new(NodeIdentity {
fn make_dummy_identity() -> Rc<NodeIdentity> {
Rc::new(NodeIdentity {
location: SourceLocation { line: 0, col: 0 },
})
}
@@ -338,7 +342,7 @@ mod tests {
kind: BoundKind::Lambda {
param_count: 0,
upvalues: vec![Address::Local(0)], // Capture x
body: Arc::new(lambda_body),
body: Rc::new(lambda_body),
},
}),
},
@@ -366,7 +370,7 @@ mod tests {
},
};
let globals = Arc::new(Mutex::new(Vec::new()));
let globals = Rc::new(RefCell::new(Vec::new()));
let mut vm = VM::new(globals);
let result = vm.run(&root);