Refactor VM evaluation to use macro
The `eval` and `eval_observed` methods in the `VM` struct were very similar, with the primary difference being the call to the `VMObserver` trait. This commit refactors these methods into a single macro, `dispatch_eval!`, which reduces code duplication and improves maintainability. The `Value::TailCallRequest` variant was also updated to use `Box` for its contents, which helps keep the `Value` enum size consistent. Finally, the benchmarks in the `examples` directory have been updated to reflect minor performance changes resulting from these code modifications.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
;; Closure Scope Test
|
;; Closure Scope Test
|
||||||
;; Benchmark: 9.0us
|
;; Benchmark: 9.4us
|
||||||
;; Output: 15
|
;; Output: 15
|
||||||
(do
|
(do
|
||||||
(def make-adder (fn [x]
|
(def make-adder (fn [x]
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
;; Complex Data Structure Test
|
;; Complex Data Structure Test
|
||||||
;; Benchmark: 72.7us
|
;; Benchmark: 64.4us
|
||||||
;; Output: {:input 10, :name "Fibonacci", :output 55}
|
;; Output: {:input 10, :name "Fibonacci", :output 55}
|
||||||
(do
|
(do
|
||||||
(def fib (fn [n]
|
(def fib (fn [n]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
;; Benchmark: 25.9us
|
||||||
;; Output: 36
|
;; Output: 36
|
||||||
(do
|
(do
|
||||||
;; Excessive capture test
|
;; Excessive capture test
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
;; Fibonacci Recursive
|
;; Fibonacci Recursive
|
||||||
;; Benchmark: 68.3us
|
;; Benchmark: 60.3us
|
||||||
;; Output: 55
|
;; Output: 55
|
||||||
(do
|
(do
|
||||||
(def fib (fn [n]
|
(def fib (fn [n]
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
;; Higher-Order Function Example
|
;; Higher-Order Function Example
|
||||||
;; Benchmark: 8.4us
|
;; Benchmark: 8.9us
|
||||||
;; Output: 25
|
;; Output: 25
|
||||||
(do
|
(do
|
||||||
(def apply (fn [f x] (f x)))
|
(def apply (fn [f x] (f x)))
|
||||||
|
|||||||
+3
-3
@@ -70,7 +70,7 @@ pub enum Value {
|
|||||||
Function(Rc<dyn Fn(Vec<Value>) -> Value>),
|
Function(Rc<dyn Fn(Vec<Value>) -> Value>),
|
||||||
Object(Rc<dyn Object>), // For compiled Closures and other opaque types
|
Object(Rc<dyn Object>), // For compiled Closures and other opaque types
|
||||||
Cell(Rc<RefCell<Value>>), // Boxed value for captures
|
Cell(Rc<RefCell<Value>>), // Boxed value for captures
|
||||||
TailCallRequest(Rc<dyn Object>, Vec<Value>), // Internal: For TCO
|
TailCallRequest(Box<(Rc<dyn Object>, Vec<Value>)>), // Internal: For TCO (Boxed to keep Value small)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
@@ -146,7 +146,7 @@ impl Value {
|
|||||||
Value::Function { .. } => StaticType::Any, // Dynamic function
|
Value::Function { .. } => StaticType::Any, // Dynamic function
|
||||||
Value::Object(o) => StaticType::Object(o.type_name()),
|
Value::Object(o) => StaticType::Object(o.type_name()),
|
||||||
Value::Cell(c) => c.borrow().static_type(),
|
Value::Cell(c) => c.borrow().static_type(),
|
||||||
Value::TailCallRequest(_, _) => panic!("Internal VM state leaked: TailCallRequest"),
|
Value::TailCallRequest(_) => panic!("Internal VM state leaked: TailCallRequest"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ impl fmt::Display for Value {
|
|||||||
Value::Function(_) => write!(f, "<native fn>"),
|
Value::Function(_) => write!(f, "<native fn>"),
|
||||||
Value::Object(o) => write!(f, "<{}>", o.type_name()),
|
Value::Object(o) => write!(f, "<{}>", o.type_name()),
|
||||||
Value::Cell(c) => write!(f, "{}", c.borrow()),
|
Value::Cell(c) => write!(f, "{}", c.borrow()),
|
||||||
Value::TailCallRequest(_, _) => panic!("Internal VM state leaked: TailCallRequest"),
|
Value::TailCallRequest(_) => panic!("Internal VM state leaked: TailCallRequest"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+103
-90
@@ -30,8 +30,8 @@ struct CallFrame {
|
|||||||
/// Hook for observing VM execution (zero-cost when O::ACTIVE is false)
|
/// Hook for observing VM execution (zero-cost when O::ACTIVE is false)
|
||||||
pub trait VMObserver {
|
pub trait VMObserver {
|
||||||
const ACTIVE: bool = false;
|
const ACTIVE: bool = false;
|
||||||
fn before_eval(&mut self, _vm: &VM, _node: &Node<BoundKind, StaticType>, _indent: usize) {}
|
fn before_eval(&mut self, _vm: &VM, _node: &Node<BoundKind, StaticType>) {}
|
||||||
fn after_eval(&mut self, _vm: &VM, _node: &Node<BoundKind, StaticType>, _res: &Result<Value, String>, _indent: usize) {}
|
fn after_eval(&mut self, _vm: &VM, _node: &Node<BoundKind, StaticType>, _res: &Result<Value, String>) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default observer that does nothing and should be optimized away
|
/// Default observer that does nothing and should be optimized away
|
||||||
@@ -41,15 +41,16 @@ impl VMObserver for NoOpObserver {}
|
|||||||
/// Observer that logs the execution tree and scope state
|
/// Observer that logs the execution tree and scope state
|
||||||
pub struct TracingObserver {
|
pub struct TracingObserver {
|
||||||
pub logs: Vec<String>,
|
pub logs: Vec<String>,
|
||||||
|
indent: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TracingObserver {
|
impl TracingObserver {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { logs: Vec::new() }
|
Self { logs: Vec::new(), indent: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pad(&self, indent: usize) -> String {
|
fn pad(&self) -> String {
|
||||||
"| ".repeat(indent)
|
"| ".repeat(self.indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,18 +63,20 @@ impl Default for TracingObserver {
|
|||||||
impl VMObserver for TracingObserver {
|
impl VMObserver for TracingObserver {
|
||||||
const ACTIVE: bool = true;
|
const ACTIVE: bool = true;
|
||||||
|
|
||||||
fn before_eval(&mut self, _vm: &VM, node: &Node<BoundKind, StaticType>, indent: usize) {
|
fn before_eval(&mut self, _vm: &VM, node: &Node<BoundKind, StaticType>) {
|
||||||
let pad = self.pad(indent);
|
let pad = self.pad();
|
||||||
self.logs.push(format!("{}{} [{}]: {{", pad, node.kind.display_name(), node.ty));
|
self.logs.push(format!("{}{} [{}]: {{", pad, node.kind.display_name(), node.ty));
|
||||||
|
self.indent += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn after_eval(&mut self, vm: &VM, node: &Node<BoundKind, StaticType>, res: &Result<Value, String>, indent: usize) {
|
fn after_eval(&mut self, vm: &VM, node: &Node<BoundKind, StaticType>, res: &Result<Value, String>) {
|
||||||
let pad = self.pad(indent);
|
self.indent = self.indent.saturating_sub(1);
|
||||||
|
let pad = self.pad();
|
||||||
|
|
||||||
// Show scope state on certain nodes (like Delphi ShowScope)
|
// Show scope state on certain nodes (like Delphi ShowScope)
|
||||||
match &node.kind {
|
match &node.kind {
|
||||||
BoundKind::DefLocal { .. } | BoundKind::Set { .. } | BoundKind::DefGlobal { .. } => {
|
BoundKind::DefLocal { .. } | BoundKind::Set { .. } | BoundKind::DefGlobal { .. } => {
|
||||||
let s_pad = self.pad(indent + 1);
|
let s_pad = format!("{}| ", pad);
|
||||||
self.logs.push(format!("{}--- Scope Status ---", s_pad));
|
self.logs.push(format!("{}--- Scope Status ---", s_pad));
|
||||||
self.logs.push(format!("{}Stack (top 5): {:?}", s_pad, vm.stack.iter().rev().take(5).collect::<Vec<_>>()));
|
self.logs.push(format!("{}Stack (top 5): {:?}", s_pad, vm.stack.iter().rev().take(5).collect::<Vec<_>>()));
|
||||||
},
|
},
|
||||||
@@ -94,58 +97,16 @@ pub struct VM {
|
|||||||
frames: Vec<CallFrame>,
|
frames: Vec<CallFrame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VM {
|
macro_rules! dispatch_eval {
|
||||||
pub fn new(globals: Rc<RefCell<Vec<Value>>>) -> Self {
|
($self:ident, $node:ident, $eval_method:ident $(, $observer:ident)?) => {
|
||||||
Self {
|
match &$node.kind {
|
||||||
stack: Vec::new(),
|
|
||||||
globals,
|
|
||||||
frames: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self, root: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
|
||||||
self.run_with_observer(&mut NoOpObserver, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_with_observer<O: VMObserver>(&mut self, observer: &mut O, root: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
|
||||||
self.stack.clear();
|
|
||||||
self.frames.clear();
|
|
||||||
|
|
||||||
// Push global script frame
|
|
||||||
self.frames.push(CallFrame {
|
|
||||||
stack_base: 0,
|
|
||||||
closure: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = self.eval(observer, 0, root);
|
|
||||||
|
|
||||||
self.frames.pop();
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval<O: VMObserver>(&mut self, observer: &mut O, indent: usize, node: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
|
||||||
if O::ACTIVE {
|
|
||||||
observer.before_eval(self, node, indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = self.eval_internal(observer, indent, node);
|
|
||||||
|
|
||||||
if O::ACTIVE {
|
|
||||||
observer.after_eval(self, node, &result, indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_internal<O: VMObserver>(&mut self, observer: &mut O, indent: usize, node: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
|
||||||
match &node.kind {
|
|
||||||
BoundKind::Nop => Ok(Value::Void),
|
BoundKind::Nop => Ok(Value::Void),
|
||||||
BoundKind::Constant(v) => Ok(v.clone()),
|
BoundKind::Constant(v) => Ok(v.clone()),
|
||||||
|
|
||||||
BoundKind::DefGlobal { global_index, value } => {
|
BoundKind::DefGlobal { global_index, value } => {
|
||||||
let val = self.eval(observer, indent + 1, value)?;
|
let val = $self.$eval_method($($observer,)? value)?;
|
||||||
let idx = *global_index as usize;
|
let idx = *global_index as usize;
|
||||||
let mut globals = self.globals.borrow_mut();
|
let mut globals = $self.globals.borrow_mut();
|
||||||
if idx >= globals.len() {
|
if idx >= globals.len() {
|
||||||
globals.resize(idx + 1, Value::Void);
|
globals.resize(idx + 1, Value::Void);
|
||||||
}
|
}
|
||||||
@@ -153,30 +114,29 @@ impl VM {
|
|||||||
Ok(val)
|
Ok(val)
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Get(addr) => self.get_value(*addr),
|
BoundKind::Get(addr) => $self.get_value(*addr),
|
||||||
|
|
||||||
BoundKind::Set { addr, value } => {
|
BoundKind::Set { addr, value } => {
|
||||||
let val = self.eval(observer, indent + 1, value)?;
|
let val = $self.$eval_method($($observer,)? value)?;
|
||||||
self.set_value(*addr, val.clone())?;
|
$self.set_value(*addr, val.clone())?;
|
||||||
Ok(val)
|
Ok(val)
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::DefLocal { slot, value, captured_by } => {
|
BoundKind::DefLocal { slot, value, captured_by } => {
|
||||||
let val = self.eval(observer, indent + 1, value)?;
|
let val = $self.$eval_method($($observer,)? value)?;
|
||||||
let final_val = if !captured_by.is_empty() {
|
let final_val = if !captured_by.is_empty() {
|
||||||
Value::Cell(Rc::new(RefCell::new(val)))
|
Value::Cell(Rc::new(RefCell::new(val)))
|
||||||
} else {
|
} else {
|
||||||
val
|
val
|
||||||
};
|
};
|
||||||
|
|
||||||
let frame = self.frames.last().ok_or("No call frame")?;
|
let frame = $self.frames.last().ok_or("No call frame")?;
|
||||||
let abs_index = frame.stack_base + (*slot as usize);
|
let abs_index = frame.stack_base + (*slot as usize);
|
||||||
|
|
||||||
// If it's the exact top of stack, push it. Otherwise, set it.
|
if abs_index == $self.stack.len() {
|
||||||
if abs_index == self.stack.len() {
|
$self.stack.push(final_val.clone());
|
||||||
self.stack.push(final_val.clone());
|
} else if abs_index < $self.stack.len() {
|
||||||
} else if abs_index < self.stack.len() {
|
$self.stack[abs_index] = final_val.clone();
|
||||||
self.stack[abs_index] = final_val.clone();
|
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("Stack gap at local {}", slot));
|
return Err(format!("Stack gap at local {}", slot));
|
||||||
}
|
}
|
||||||
@@ -184,11 +144,11 @@ impl VM {
|
|||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::If { cond, then_br, else_br } => {
|
BoundKind::If { cond, then_br, else_br } => {
|
||||||
let c = self.eval(observer, indent + 1, cond)?;
|
let c = $self.$eval_method($($observer,)? cond)?;
|
||||||
if c.is_truthy() {
|
if c.is_truthy() {
|
||||||
self.eval(observer, indent + 1, then_br)
|
$self.$eval_method($($observer,)? then_br)
|
||||||
} else if let Some(e) = else_br {
|
} else if let Some(e) = else_br {
|
||||||
self.eval(observer, indent + 1, e)
|
$self.$eval_method($($observer,)? e)
|
||||||
} else {
|
} else {
|
||||||
Ok(Value::Void)
|
Ok(Value::Void)
|
||||||
}
|
}
|
||||||
@@ -197,7 +157,7 @@ impl VM {
|
|||||||
BoundKind::Block { exprs } => {
|
BoundKind::Block { exprs } => {
|
||||||
let mut last = Value::Void;
|
let mut last = Value::Void;
|
||||||
for e in exprs {
|
for e in exprs {
|
||||||
last = self.eval(observer, indent + 1, e)?;
|
last = $self.$eval_method($($observer,)? e)?;
|
||||||
}
|
}
|
||||||
Ok(last)
|
Ok(last)
|
||||||
},
|
},
|
||||||
@@ -205,7 +165,7 @@ impl VM {
|
|||||||
BoundKind::Lambda { param_count: _, upvalues, body } => {
|
BoundKind::Lambda { param_count: _, upvalues, body } => {
|
||||||
let mut captured = Vec::with_capacity(upvalues.len());
|
let mut captured = Vec::with_capacity(upvalues.len());
|
||||||
for addr in upvalues {
|
for addr in upvalues {
|
||||||
captured.push(self.capture_upvalue(*addr)?);
|
captured.push($self.capture_upvalue(*addr)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let closure = Closure {
|
let closure = Closure {
|
||||||
@@ -217,52 +177,50 @@ impl VM {
|
|||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::TailCall { callee, args } => {
|
BoundKind::TailCall { callee, args } => {
|
||||||
let func_val = self.eval(observer, indent + 1, callee)?;
|
let func_val = $self.$eval_method($($observer,)? callee)?;
|
||||||
let mut arg_vals = Vec::with_capacity(args.len());
|
let mut arg_vals = Vec::with_capacity(args.len());
|
||||||
for arg in args {
|
for arg in args {
|
||||||
arg_vals.push(self.eval(observer, indent + 1, arg)?);
|
arg_vals.push($self.$eval_method($($observer,)? arg)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
match func_val {
|
match func_val {
|
||||||
Value::Object(obj) => Ok(Value::TailCallRequest(obj, arg_vals)),
|
Value::Object(obj) => Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))),
|
||||||
// Native functions can't be TCO'd this way (they return value directly),
|
|
||||||
// so we just execute them and return Value.
|
|
||||||
Value::Function(f) => Ok(f(arg_vals)),
|
Value::Function(f) => Ok(f(arg_vals)),
|
||||||
_ => Err(format!("Tail call target is not a function: {}", func_val)),
|
_ => Err(format!("Tail call target is not a function: {}", func_val)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Call { callee, args } => {
|
BoundKind::Call { callee, args } => {
|
||||||
let mut func_val = self.eval(observer, indent + 1, callee)?;
|
let mut func_val = $self.$eval_method($($observer,)? callee)?;
|
||||||
|
|
||||||
let mut arg_vals = Vec::with_capacity(args.len());
|
let mut arg_vals = Vec::with_capacity(args.len());
|
||||||
for arg in args {
|
for arg in args {
|
||||||
arg_vals.push(self.eval(observer, indent + 1, arg)?);
|
arg_vals.push($self.$eval_method($($observer,)? arg)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trampoline Loop
|
|
||||||
loop {
|
loop {
|
||||||
match func_val {
|
match func_val {
|
||||||
Value::Function(f) => return Ok(f(arg_vals)),
|
Value::Function(f) => return Ok(f(arg_vals)),
|
||||||
Value::Object(obj) => {
|
Value::Object(obj) => {
|
||||||
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
|
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
|
||||||
let old_stack_top = self.stack.len();
|
let old_stack_top = $self.stack.len();
|
||||||
let closure_rc = Rc::new(closure.clone());
|
let closure_rc = Rc::new(closure.clone());
|
||||||
|
|
||||||
self.stack.extend(arg_vals); // Moves arg_vals into stack
|
$self.stack.extend(arg_vals);
|
||||||
|
|
||||||
self.frames.push(CallFrame {
|
$self.frames.push(CallFrame {
|
||||||
stack_base: old_stack_top,
|
stack_base: old_stack_top,
|
||||||
closure: Some(closure_rc.clone()),
|
closure: Some(closure_rc.clone()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = self.eval(observer, indent + 1, &closure.function_node);
|
let result = $self.$eval_method($($observer,)? &closure.function_node);
|
||||||
|
|
||||||
self.frames.pop();
|
$self.frames.pop();
|
||||||
self.stack.truncate(old_stack_top);
|
$self.stack.truncate(old_stack_top);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(Value::TailCallRequest(next_obj, next_args)) => {
|
Ok(Value::TailCallRequest(payload)) => {
|
||||||
|
let (next_obj, next_args) = *payload;
|
||||||
func_val = Value::Object(next_obj);
|
func_val = Value::Object(next_obj);
|
||||||
arg_vals = next_args;
|
arg_vals = next_args;
|
||||||
continue;
|
continue;
|
||||||
@@ -282,7 +240,7 @@ impl VM {
|
|||||||
BoundKind::Tuple { elements } => {
|
BoundKind::Tuple { elements } => {
|
||||||
let mut vals = Vec::with_capacity(elements.len());
|
let mut vals = Vec::with_capacity(elements.len());
|
||||||
for e in elements {
|
for e in elements {
|
||||||
vals.push(self.eval(observer, indent + 1, e)?);
|
vals.push($self.$eval_method($($observer,)? e)?);
|
||||||
}
|
}
|
||||||
Ok(Value::List(Rc::new(vals)))
|
Ok(Value::List(Rc::new(vals)))
|
||||||
},
|
},
|
||||||
@@ -290,8 +248,8 @@ impl VM {
|
|||||||
BoundKind::Map { entries } => {
|
BoundKind::Map { entries } => {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
for (k, v) in entries {
|
for (k, v) in entries {
|
||||||
let key = self.eval(observer, indent + 1, k)?;
|
let key = $self.$eval_method($($observer,)? k)?;
|
||||||
let val = self.eval(observer, indent + 1, v)?;
|
let val = $self.$eval_method($($observer,)? v)?;
|
||||||
|
|
||||||
if let Value::Keyword(kw) = key {
|
if let Value::Keyword(kw) = key {
|
||||||
map.insert(kw, val);
|
map.insert(kw, val);
|
||||||
@@ -302,8 +260,63 @@ impl VM {
|
|||||||
Ok(Value::Record(Rc::new(map)))
|
Ok(Value::Record(Rc::new(map)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VM {
|
||||||
|
pub fn new(globals: Rc<RefCell<Vec<Value>>>) -> Self {
|
||||||
|
Self {
|
||||||
|
stack: Vec::new(),
|
||||||
|
globals,
|
||||||
|
frames: Vec::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self, root: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
||||||
|
self.stack.clear();
|
||||||
|
self.frames.clear();
|
||||||
|
|
||||||
|
self.frames.push(CallFrame {
|
||||||
|
stack_base: 0,
|
||||||
|
closure: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = self.eval(root);
|
||||||
|
|
||||||
|
self.frames.pop();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_with_observer<O: VMObserver>(&mut self, observer: &mut O, root: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
||||||
|
self.stack.clear();
|
||||||
|
self.frames.clear();
|
||||||
|
|
||||||
|
self.frames.push(CallFrame {
|
||||||
|
stack_base: 0,
|
||||||
|
closure: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = self.eval_observed(observer, root);
|
||||||
|
|
||||||
|
self.frames.pop();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pure, original, zero-cost hot path.
|
||||||
|
#[inline(always)]
|
||||||
|
fn eval(&mut self, node: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
||||||
|
dispatch_eval!(self, node, eval)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The observed path for debugging.
|
||||||
|
fn eval_observed<O: VMObserver>(&mut self, observer: &mut O, node: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
||||||
|
observer.before_eval(self, node);
|
||||||
|
let result = dispatch_eval!(self, node, eval_observed, observer);
|
||||||
|
observer.after_eval(self, node, &result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn capture_upvalue(&mut self, addr: Address) -> Result<Rc<RefCell<Value>>, String> {
|
fn capture_upvalue(&mut self, addr: Address) -> Result<Rc<RefCell<Value>>, String> {
|
||||||
match addr {
|
match addr {
|
||||||
Address::Local(idx) => {
|
Address::Local(idx) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user