diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index f12f9c1..b62badd 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -233,8 +233,7 @@ impl Binder { Ok(self.make_node(node.identity.clone(), BoundKind::Call { callee: Box::new(callee), - args: Box::new(args), - is_tail: false + args: Box::new(args) })) }, diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index 797522a..60f5171 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -83,7 +83,6 @@ pub enum BoundKind { Call { callee: Box>, args: Box>, - is_tail: bool, }, Block { @@ -141,8 +140,8 @@ where T: PartialEq { BoundKind::Lambda { params: pb, upvalues: ub, body: bb, positional_count: pcb }) => { pa == pb && ua == ub && ba == bb && pca == pcb } - (BoundKind::Call { callee: ca, args: aa, is_tail: ta }, BoundKind::Call { callee: cb, args: ab, is_tail: tb }) => { - ca == cb && aa == ab && ta == tb + (BoundKind::Call { callee: ca, args: aa }, BoundKind::Call { callee: cb, args: ab }) => { + ca == cb && aa == ab } (BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => { ea == eb @@ -184,7 +183,7 @@ impl BoundKind { }; format!("LAMBDA({}, Captures:{})", p_str, upvalues.len()) }, - BoundKind::Call { is_tail, .. } => if *is_tail { "T-CALL".to_string() } else { "CALL".to_string() }, + BoundKind::Call { .. } => "CALL".to_string(), BoundKind::Block { .. } => "BLOCK".to_string(), BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()), BoundKind::Record { fields } => format!("RECORD({})", fields.len()), diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index 1ccc9b8..927ada7 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -137,9 +137,8 @@ impl Dumper { self.log(&format!("Parameter (Name: '{}', Slot: {})", name.name, slot), node); } - BoundKind::Call { callee, args, is_tail } => { - let label = if *is_tail { "Call (TCO)" } else { "Call" }; - self.log(label, node); + BoundKind::Call { callee, args } => { + self.log("Call", node); self.indent += 1; self.write_indent(); diff --git a/src/ast/compiler/lambda_collector.rs b/src/ast/compiler/lambda_collector.rs index 6c6e889..8e8fc4e 100644 --- a/src/ast/compiler/lambda_collector.rs +++ b/src/ast/compiler/lambda_collector.rs @@ -57,7 +57,7 @@ impl<'a> LambdaCollector<'a> { self.visit(value); } - BoundKind::Call { callee, args, .. } => { + BoundKind::Call { callee, args } => { self.visit(callee); self.visit(args); } diff --git a/src/ast/compiler/optimizer.rs b/src/ast/compiler/optimizer.rs index 3b2be06..ecf9a7a 100644 --- a/src/ast/compiler/optimizer.rs +++ b/src/ast/compiler/optimizer.rs @@ -34,7 +34,7 @@ impl Optimizer { fn visit_node(&self, node: TypedNode) -> TypedNode { let (new_kind, new_ty) = match node.kind { - BoundKind::Call { callee, args, is_tail } => { + BoundKind::Call { callee, args } => { let callee = self.visit_node(*callee); let args = self.visit_node(*args); @@ -52,6 +52,7 @@ impl Optimizer { for (i, cell) in closure.upvalues.iter().enumerate() { sub.add_upvalue(i as u32, cell.borrow().clone()); } + // DIRECT ACCESS: Use the original TypedNode from the closure let inlined_body = sub.inline((*closure.function_node).clone()); // Try to collapse the now-stateless lambda call @@ -73,6 +74,7 @@ impl Optimizer { for (i, cell) in closure.upvalues.iter().enumerate() { sub.add_upvalue(i as u32, cell.borrow().clone()); } + // DIRECT ACCESS: Use the original TypedNode let inlined_body = sub.inline((*closure.function_node).clone()); let cracked_lambda = Node { @@ -87,12 +89,12 @@ impl Optimizer { }; return Node { identity: node.identity, - kind: BoundKind::Call { callee: Box::new(cracked_lambda), args: Box::new(args), is_tail }, + kind: BoundKind::Call { callee: Box::new(cracked_lambda), args: Box::new(args) }, ty: node.ty, }; } - (BoundKind::Call { callee: Box::new(callee), args: Box::new(args), is_tail }, node.ty) + (BoundKind::Call { callee: Box::new(callee), args: Box::new(args) }, node.ty) }, BoundKind::If { cond, then_br, else_br } => { @@ -269,7 +271,7 @@ impl Optimizer { self.contains_def_local(cond) || self.contains_def_local(then_br) || else_br.as_ref().is_some_and(|e| self.contains_def_local(e)) } BoundKind::Block { exprs } => exprs.iter().any(|e| self.contains_def_local(e)), - BoundKind::Call { callee, args, .. } => self.contains_def_local(callee) || self.contains_def_local(args), + BoundKind::Call { callee, args } => self.contains_def_local(callee) || self.contains_def_local(args), BoundKind::Lambda { .. } => false, // Nested lambda definitions are safe (they have their own frame) BoundKind::Tuple { elements } => elements.iter().any(|e| self.contains_def_local(e)), BoundKind::Record { fields } => fields.iter().any(|(k, v)| self.contains_def_local(k) || self.contains_def_local(v)), @@ -388,10 +390,10 @@ impl SubstitutionMap { let exprs = exprs.into_iter().map(|e| self.inline_recursive(e, depth, upvalue_subs)).collect(); (BoundKind::Block { exprs }, node.ty) }, - BoundKind::Call { callee, args, is_tail } => { + BoundKind::Call { callee, args } => { let callee = Box::new(self.inline_recursive(*callee, depth, upvalue_subs)); let args = Box::new(self.inline_recursive(*args, depth, upvalue_subs)); - (BoundKind::Call { callee, args, is_tail }, node.ty) + (BoundKind::Call { callee, args }, node.ty) }, BoundKind::DefLocal { name, slot, value, captured_by } => { let value = Box::new(self.inline_recursive(*value, depth, upvalue_subs)); @@ -444,10 +446,10 @@ impl SubstitutionMap { let exprs = exprs.into_iter().map(|e| self.reindex_upvalues(e, mapping)).collect(); (BoundKind::Block { exprs }, node.ty) }, - BoundKind::Call { callee, args, is_tail } => { + BoundKind::Call { callee, args } => { let callee = Box::new(self.reindex_upvalues(*callee, mapping)); let args = Box::new(self.reindex_upvalues(*args, mapping)); - (BoundKind::Call { callee, args, is_tail }, node.ty) + (BoundKind::Call { callee, args }, node.ty) }, BoundKind::DefLocal { name, slot, value, captured_by } => { let value = Box::new(self.reindex_upvalues(*value, mapping)); diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs index 0220129..5138f72 100644 --- a/src/ast/compiler/specializer.rs +++ b/src/ast/compiler/specializer.rs @@ -48,9 +48,9 @@ impl Specializer { fn visit_node(&self, node: TypedNode) -> TypedNode { let (new_kind, new_ty) = match node.kind { - BoundKind::Call { callee, args, is_tail } => { + BoundKind::Call { callee, args } => { let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, *args, node.ty.clone()); - (BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args), is_tail }, ret_ty) + (BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty) }, // Recursive traversal for other nodes diff --git a/src/ast/compiler/tco.rs b/src/ast/compiler/tco.rs index 7e5633c..6f1271f 100644 --- a/src/ast/compiler/tco.rs +++ b/src/ast/compiler/tco.rs @@ -1,123 +1,120 @@ use std::rc::Rc; use crate::ast::nodes::Node; -use crate::ast::compiler::bound_nodes::BoundKind; +use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode}; +use crate::ast::types::StaticType; + +#[derive(Debug, Clone)] +pub struct RuntimeMetadata { + pub ty: StaticType, + pub is_tail: bool, + /// The original, high-level typed node. Perfect for Debuggers and Optimizers. + pub original: Rc, +} + +/// The ExecNode is the AST used by the VM. It carries TCO flags and links to source. +pub type ExecNode = Node, RuntimeMetadata>; pub struct TCO; impl TCO { - pub fn optimize(node: Node, T>) -> Node, T> { - // Top-level starts NOT in tail position usually, unless the script result is returned immediately - // which implies tail position for the script body if viewed as a function. - // Let's assume standard execution context (not tail call) for the script root. - Self::transform(node, false) + /// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions. + pub fn optimize(node: TypedNode) -> ExecNode { + Self::transform(Rc::new(node), true) } - fn transform(node: Node, T>, is_tail_position: bool) -> Node, T> { - match node.kind { - BoundKind::Call { callee, args, .. } => { - let new_callee = Box::new(Self::transform(*callee, false)); - let new_args = Box::new(Self::transform(*args, false)); - - Node { - kind: BoundKind::Call { - callee: new_callee, - args: new_args, - is_tail: is_tail_position, - }, - ..node + fn transform(node_rc: Rc, is_tail_position: bool) -> ExecNode { + let node = &*node_rc; + let new_kind = match &node.kind { + BoundKind::Call { callee, args } => { + BoundKind::Call { + callee: Box::new(Self::transform(Rc::new((**callee).clone()), false)), + args: Box::new(Self::transform(Rc::new((**args).clone()), false)), } }, BoundKind::If { cond, then_br, else_br } => { - let new_cond = Box::new(Self::transform(*cond, false)); - let new_then = Box::new(Self::transform(*then_br, is_tail_position)); - let new_else = else_br.map(|e| Box::new(Self::transform(*e, is_tail_position))); - - Node { - kind: BoundKind::If { - cond: new_cond, - then_br: new_then, - else_br: new_else, - }, - ..node + BoundKind::If { + cond: Box::new(Self::transform(Rc::new((**cond).clone()), false)), + then_br: Box::new(Self::transform(Rc::new((**then_br).clone()), is_tail_position)), + else_br: else_br.as_ref().map(|e| Box::new(Self::transform(Rc::new((**e).clone()), is_tail_position))), } }, BoundKind::Block { exprs } => { if exprs.is_empty() { - return Node { - kind: BoundKind::Block { exprs }, - ..node - }; - } - - let last_idx = exprs.len() - 1; - let mut new_exprs = Vec::with_capacity(exprs.len()); - - for (i, expr) in exprs.into_iter().enumerate() { - let is_last = i == last_idx; - // Only the last expression inherits the tail context - new_exprs.push(Self::transform(expr, is_tail_position && is_last)); - } - - Node { - kind: BoundKind::Block { exprs: new_exprs }, - ..node + BoundKind::Block { exprs: vec![] } + } else { + let last_idx = exprs.len() - 1; + let mut new_exprs = Vec::with_capacity(exprs.len()); + + for (i, expr) in exprs.iter().enumerate() { + let is_last = i == last_idx; + new_exprs.push(Self::transform(Rc::new(expr.clone()), is_tail_position && is_last)); + } + BoundKind::Block { exprs: new_exprs } } }, BoundKind::Lambda { params, upvalues, body, positional_count } => { - // The body of a lambda is implicitly in tail position when the lambda is called. - let new_body = Rc::new(Self::transform((*body).clone(), true)); - - Node { - kind: BoundKind::Lambda { - params: params.clone(), - upvalues, - body: new_body, - positional_count, - }, - ..node + BoundKind::Lambda { + params: Rc::new(Self::transform(params.clone(), false)), + upvalues: upvalues.clone(), + body: Rc::new(Self::transform(body.clone(), true)), + positional_count: *positional_count, } }, BoundKind::Set { addr, value } => { - Node { - kind: BoundKind::Set { addr, value: Box::new(Self::transform(*value, false)) }, - ..node - } + BoundKind::Set { addr: *addr, value: Box::new(Self::transform(Rc::new((**value).clone()), false)) } }, BoundKind::DefLocal { name, slot, value, captured_by } => { - Node { - kind: BoundKind::DefLocal { name, slot, value: Box::new(Self::transform(*value, false)), captured_by }, - ..node + BoundKind::DefLocal { + name: name.clone(), + slot: *slot, + value: Box::new(Self::transform(Rc::new((**value).clone()), false)), + captured_by: captured_by.clone() } }, BoundKind::DefGlobal { name, global_index, value } => { - Node { - kind: BoundKind::DefGlobal { name, global_index, value: Box::new(Self::transform(*value, false)) }, - ..node + BoundKind::DefGlobal { + name: name.clone(), + global_index: *global_index, + value: Box::new(Self::transform(Rc::new((**value).clone()), false)) } }, BoundKind::Record { fields } => { - let new_fields = fields.into_iter().map(|(k, v)| { - (Self::transform(k, false), Self::transform(v, false)) + let new_fields = fields.iter().map(|(k, v)| { + (Self::transform(Rc::new(k.clone()), false), Self::transform(Rc::new(v.clone()), false)) }).collect(); - Node { - kind: BoundKind::Record { fields: new_fields }, - ..node - } + BoundKind::Record { fields: new_fields } }, BoundKind::Tuple { elements } => { - let new_elements = elements.into_iter().map(|e| Self::transform(e, false)).collect(); - Node { - kind: BoundKind::Tuple { elements: new_elements }, - ..node - } + let new_elements = elements.iter().map(|e| Self::transform(Rc::new(e.clone()), false)).collect(); + BoundKind::Tuple { elements: new_elements } + }, + BoundKind::Parameter { name, slot } => BoundKind::Parameter { name: name.clone(), slot: *slot }, + BoundKind::Constant(v) => BoundKind::Constant(v.clone()), + BoundKind::Get { addr, name } => BoundKind::Get { addr: *addr, name: name.clone() }, + BoundKind::Nop => BoundKind::Nop, + BoundKind::Expansion { original_call, bound_expanded } => { + BoundKind::Expansion { + original_call: original_call.clone(), + bound_expanded: Box::new(Self::transform(Rc::new((**bound_expanded).clone()), is_tail_position)) + } + } + BoundKind::Extension(_) => { + BoundKind::Nop + } + }; + + Node { + identity: node.identity.clone(), + kind: new_kind, + ty: RuntimeMetadata { + ty: node.ty.clone(), + is_tail: is_tail_position, + original: node_rc, }, - - // Atoms and variables don't change - _ => node, } } } diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 718a93f..134a6ea 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -263,7 +263,7 @@ impl TypeChecker { }, fn_ty) }, - BoundKind::Call { callee, args, is_tail } => { + BoundKind::Call { callee, args } => { let callee_typed = self.check_node(*callee, ctx)?; let args_typed = self.check_node(*args, ctx)?; @@ -271,8 +271,7 @@ impl TypeChecker { (BoundKind::Call { callee: Box::new(callee_typed), - args: Box::new(args_typed), - is_tail + args: Box::new(args_typed) }, ret_ty) }, diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 4b034c7..be6e532 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -14,7 +14,7 @@ use crate::ast::compiler::lambda_collector::LambdaCollector; use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; use crate::ast::compiler::optimizer::Optimizer; use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer}; -use crate::ast::compiler::tco::TCO; +use crate::ast::compiler::tco::{TCO, ExecNode}; use crate::ast::rtl; use crate::ast::rtl::intrinsics; @@ -67,9 +67,10 @@ impl MacroEvaluator for RuntimeMacroEvaluator { let checker = TypeChecker::new(self.global_types.clone()); let typed_ast = checker.check(bound_ast, &[])?; + let exec_ast = TCO::optimize(typed_ast); let mut vm = VM::new(self.global_values.clone()); - vm.run(&typed_ast) + vm.run(&exec_ast) } } @@ -176,7 +177,7 @@ impl Environment { } /// Backend: Optimization (TCO, etc.) - pub fn link(&self, node: TypedNode) -> TypedNode { + pub fn link(&self, node: TypedNode) -> ExecNode { // 1. Specialize (Always performed for correctness) let specialized = self.specialize_node(node); @@ -184,7 +185,7 @@ impl Environment { let optimizer = Optimizer::new(self.optimization_level); let optimized = optimizer.optimize(specialized); - // 3. TCO (Always performed) + // 3. TCO (Always performed, converts to ExecNode) TCO::optimize(optimized) } @@ -229,7 +230,7 @@ impl Environment { let optimizer = Optimizer::new(opt_level); let optimized_ast = optimizer.optimize(specialized_ast); - // 4. TCO + // 4. TCO (converts to ExecNode) let tco_ast = TCO::optimize(optimized_ast); // 5. Compile to Value (VM) @@ -240,7 +241,7 @@ impl Environment { }; // 6. Determine correct return type from the newly inferred function signature - let ret_type = if let StaticType::Function(sig) = &tco_ast.ty { + let ret_type = if let StaticType::Function(sig) = &tco_ast.ty.ty { sig.ret.clone() } else { StaticType::Any @@ -261,7 +262,7 @@ impl Environment { } /// Runtime: Execute the linked AST in the VM - pub fn run(&self, node: &TypedNode) -> Result { + pub fn run(&self, node: &ExecNode) -> Result { let mut vm = VM::new(self.global_values.clone()); let mut result = vm.run(node)?; @@ -269,7 +270,7 @@ impl Environment { if let Value::Object(obj) = &result && let Some(closure) = obj.as_any().downcast_ref::() { - result = vm.run(&closure.function_node)?; + result = vm.run(&closure.exec_node)?; } // IMPORTANT: Resolve any pending tail call requests from the top-level execution @@ -314,7 +315,7 @@ impl Environment { if let Ok(Value::Object(obj)) = &result && let Some(closure) = obj.as_any().downcast_ref::() { - result = vm.run_with_observer(&mut observer, &closure.function_node); + result = vm.run_with_observer(&mut observer, &closure.exec_node); } // Resolve top-level tail calls diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 85dcdee..bd0ee90 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -2,25 +2,29 @@ use std::rc::Rc; use std::cell::RefCell; use std::any::Any; use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; +use crate::ast::compiler::tco::ExecNode; use crate::ast::types::{Value, Object}; +use crate::ast::nodes::Node; #[derive(Debug, Clone)] pub struct Closure { pub parameter_node: Rc, pub function_node: Rc, + pub exec_node: Rc, pub upvalues: Vec>>, - /// Optimization: If the parameter pattern is a simple flat tuple, - /// store the count to skip recursive unpacking in the hot path. pub positional_count: Option, } +impl Closure { + #[inline] + pub fn new(params: Rc, body: Rc, exec: Rc, upvalues: Vec>>, positional_count: Option) -> Self { + Self { parameter_node: params, function_node: body, exec_node: exec, upvalues, positional_count } + } +} + impl Object for Closure { - fn type_name(&self) -> &'static str { - "closure" - } - fn as_any(&self) -> &dyn Any { - self - } + fn type_name(&self) -> &'static str { "closure" } + fn as_any(&self) -> &dyn Any { self } } #[derive(Debug)] @@ -29,53 +33,37 @@ struct CallFrame { closure: Option>, } -/// Hook for observing VM execution (zero-cost when O::ACTIVE is false) pub trait VMObserver { const ACTIVE: bool = false; - fn before_eval(&mut self, _vm: &VM, _node: &TypedNode) {} - fn after_eval(&mut self, _vm: &VM, _node: &TypedNode, _res: &Result) {} + fn before_eval(&mut self, _vm: &VM, _node: &ExecNode) {} + fn after_eval(&mut self, _vm: &VM, _node: &ExecNode, _res: &Result) {} } -/// Default observer that does nothing and should be optimized away pub struct NoOpObserver; impl VMObserver for NoOpObserver {} -/// Observer that logs the execution tree and scope state pub struct TracingObserver { pub logs: Vec, indent: usize, } impl TracingObserver { - pub fn new() -> Self { - Self { logs: Vec::new(), indent: 0 } - } - - fn pad(&self) -> String { - "| ".repeat(self.indent) - } + pub fn new() -> Self { Self { logs: Vec::new(), indent: 0 } } + fn pad(&self) -> String { "| ".repeat(self.indent) } } -impl Default for TracingObserver { - fn default() -> Self { - Self::new() - } -} +impl Default for TracingObserver { fn default() -> Self { Self::new() } } impl VMObserver for TracingObserver { const ACTIVE: bool = true; - - fn before_eval(&mut self, _vm: &VM, node: &TypedNode) { + fn before_eval(&mut self, _vm: &VM, node: &ExecNode) { 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.ty)); self.indent += 1; } - - fn after_eval(&mut self, vm: &VM, node: &TypedNode, res: &Result) { + fn after_eval(&mut self, vm: &VM, node: &ExecNode, res: &Result) { self.indent = self.indent.saturating_sub(1); let pad = self.pad(); - - // Show scope state on certain nodes (like Delphi ShowScope) match &node.kind { BoundKind::DefLocal { .. } | BoundKind::Set { .. } | BoundKind::DefGlobal { .. } => { let s_pad = format!("{}| ", pad); @@ -84,11 +72,7 @@ impl VMObserver for TracingObserver { }, _ => {} } - - let res_str = match res { - Ok(v) => format!("{}", v), - Err(e) => format!("ERROR: {}", e), - }; + let res_str = match res { Ok(v) => format!("{}", v), Err(e) => format!("ERROR: {}", e) }; self.logs.push(format!("{}}} -> {}", pad, res_str)); } } @@ -104,124 +88,71 @@ macro_rules! dispatch_eval { match &$node.kind { BoundKind::Nop => Ok(Value::Void), BoundKind::Constant(v) => Ok(v.clone()), - BoundKind::Parameter { .. } => Ok(Value::Void), - BoundKind::DefGlobal { global_index, value, .. } => { let val = $self.$eval_method($($observer,)? value)?; let idx = *global_index as usize; let mut globals = $self.globals.borrow_mut(); - if idx >= globals.len() { - globals.resize(idx + 1, Value::Void); - } + if idx >= globals.len() { globals.resize(idx + 1, Value::Void); } globals[idx] = val.clone(); Ok(val) }, - BoundKind::Get { addr, .. } => $self.get_value(*addr), - BoundKind::Set { addr, value } => { let val = $self.$eval_method($($observer,)? value)?; $self.set_value(*addr, val.clone())?; Ok(val) }, - BoundKind::DefLocal { slot, value, captured_by, .. } => { let val = $self.$eval_method($($observer,)? value)?; - let final_val = if !captured_by.is_empty() { - Value::Cell(Rc::new(RefCell::new(val))) - } else { - val - }; - + let final_val = if !captured_by.is_empty() { Value::Cell(Rc::new(RefCell::new(val))) } else { val }; let frame = $self.frames.last().ok_or("No call frame")?; let abs_index = frame.stack_base + (*slot as usize); - - if abs_index == $self.stack.len() { - $self.stack.push(final_val.clone()); - } else if abs_index < $self.stack.len() { - $self.stack[abs_index] = final_val.clone(); - } else { - return Err(format!("Stack gap at local {}", slot)); - } + if abs_index == $self.stack.len() { $self.stack.push(final_val.clone()); } + else if abs_index < $self.stack.len() { $self.stack[abs_index] = final_val.clone(); } + else { return Err(format!("Stack gap at local {}", slot)); } Ok(final_val) }, - BoundKind::If { cond, then_br, else_br } => { let c = $self.$eval_method($($observer,)? cond)?; - if c.is_truthy() { - $self.$eval_method($($observer,)? then_br) - } else if let Some(e) = else_br { - $self.$eval_method($($observer,)? e) - } else { - Ok(Value::Void) - } + if c.is_truthy() { $self.$eval_method($($observer,)? then_br) } + else if let Some(e) = else_br { $self.$eval_method($($observer,)? e) } + else { Ok(Value::Void) } }, - BoundKind::Block { exprs } => { let mut last = Value::Void; - for e in exprs { - last = $self.$eval_method($($observer,)? e)?; - } + for e in exprs { last = $self.$eval_method($($observer,)? e)?; } Ok(last) }, - BoundKind::Lambda { params, upvalues, body, positional_count } => { - // PERFORMANCE: Pre-calculated in Binder. Just copy. - let positional_count = *positional_count; - - // PERFORMANCE: Creating a closure captures upvalues. - // The actual execution of the lambda (in Call branch) now skips - // the Lambda node itself and jumps directly to the body. let mut captured = Vec::with_capacity(upvalues.len()); - for addr in upvalues { - captured.push($self.capture_upvalue(*addr)?); - } - - let closure = Closure { - parameter_node: params.clone(), - function_node: body.clone(), - upvalues: captured, - positional_count, - }; - + for addr in upvalues { captured.push($self.capture_upvalue(*addr)?); } + // CRITICAL FIX: body.clone() is O(1), body.as_ref().clone() was O(N)! + let closure = Closure::new(params.ty.original.clone(), body.ty.original.clone(), body.clone(), captured, *positional_count); Ok(Value::Object(Rc::new(closure))) }, - - BoundKind::Call { callee, args, is_tail } => { + BoundKind::Call { callee, args } => { let mut func_val = $self.$eval_method($($observer,)? callee)?; + macro_rules! get_arg_vals { + ($s:ident, $a:ident) => { $s.prepare_args($a)? }; + ($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? }; + } + let mut arg_vals = match &args.kind { BoundKind::Tuple { elements } => { let mut vals = Vec::with_capacity(elements.len()); let mut is_complex = false; for e in elements { - if matches!(e.kind, BoundKind::Tuple { .. }) { - is_complex = true; - break; - } + if matches!(e.kind, BoundKind::Tuple { .. }) { is_complex = true; break; } vals.push($self.$eval_method($($observer,)? e)?); } - if is_complex { - macro_rules! get_args { - ($s:ident, $a:ident) => { $s.prepare_args($a)? }; - ($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? }; - } - get_args!($self, $($observer,)? args) - } else { - vals - } - } - _ => { - macro_rules! get_args { - ($s:ident, $a:ident) => { $s.prepare_args($a)? }; - ($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? }; - } - get_args!($self, $($observer,)? args) + if is_complex { get_arg_vals!($self, $($observer,)? args) } else { vals } } + _ => { get_arg_vals!($self, $($observer,)? args) } }; - if *is_tail { + if $node.ty.is_tail { match func_val { Value::Object(obj) => return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))), Value::Function(f) => return Ok(f(arg_vals)), @@ -236,27 +167,15 @@ macro_rules! dispatch_eval { if let Some(closure) = obj.as_any().downcast_ref::() { let old_stack_top = $self.stack.len(); let closure_rc = Rc::new(closure.clone()); - - $self.frames.push(CallFrame { - stack_base: old_stack_top, - closure: Some(closure_rc.clone()), - }); - - // PERFORMANCE FAST-PATH: If the function is purely positional and arguments match, just extend. - if let Some(count) = closure.positional_count - && arg_vals.len() == count as usize - { + $self.frames.push(CallFrame { stack_base: old_stack_top, closure: Some(closure_rc.clone()) }); + if let Some(count) = closure.positional_count && arg_vals.len() == count as usize { $self.stack.extend(arg_vals); } else { - // Unpack arguments into slots based on the closure's parameter pattern $self.unpack(&closure.parameter_node, &arg_vals, &mut 0)?; } - - let result = $self.$eval_method($($observer,)? &closure.function_node); - + let result = $self.$eval_method($($observer,)? &closure.exec_node); $self.frames.pop(); $self.stack.truncate(old_stack_top); - match result { Ok(Value::TailCallRequest(payload)) => { let (next_obj, next_args) = *payload; @@ -266,103 +185,57 @@ macro_rules! dispatch_eval { }, res => break res, } - } else { - break Err(format!("Object is not a closure: {}", obj.type_name())); - } + } else { break Err(format!("Object is not a closure: {}", obj.type_name())); } }, _ => break Err(format!("Attempt to call non-function: {}", func_val)), } } }, - BoundKind::Tuple { elements } => { let mut vals = Vec::with_capacity(elements.len()); - for e in elements { - vals.push($self.$eval_method($($observer,)? e)?); - } + for e in elements { vals.push($self.$eval_method($($observer,)? e)?); } Ok(Value::make_tuple(vals)) }, - BoundKind::Record { fields } => { let mut keys = Vec::with_capacity(fields.len()); let mut values = Vec::with_capacity(fields.len()); for (k, v) in fields { let key = $self.$eval_method($($observer,)? k)?; let val = $self.$eval_method($($observer,)? v)?; - - if let Value::Keyword(kw) = key { - keys.push(kw); - values.push(val); - } else { - return Err(format!("Record key must be keyword, got {}", key)); - } + if let Value::Keyword(kw) = key { keys.push(kw); values.push(val); } + else { return Err(format!("Record key must be keyword, got {}", key)); } } Ok(Value::make_record(keys, values)) }, - - BoundKind::Expansion { original_call: _, bound_expanded } => { - $self.$eval_method($($observer,)? bound_expanded) - } - - BoundKind::Extension(ext) => { - // Future: Delegate execution to extension - Err(format!("Execution of extension '{}' not implemented yet", ext.display_name())) - } + BoundKind::Expansion { bound_expanded, .. } => { $self.$eval_method($($observer,)? bound_expanded) } + BoundKind::Extension(ext) => { Err(format!("Execution of extension '{}' not implemented yet", ext.display_name())) } } }; } impl VM { - pub fn new(globals: Rc>>) -> Self { - Self { - stack: Vec::new(), - globals, - frames: Vec::new(), - } - } + pub fn new(globals: Rc>>) -> Self { Self { stack: Vec::new(), globals, frames: Vec::new() } } - pub fn run(&mut self, root: &TypedNode) -> Result { - self.stack.clear(); - self.frames.clear(); - - self.frames.push(CallFrame { - stack_base: 0, - closure: None, - }); - + pub fn run(&mut self, root: &ExecNode) -> Result { + self.stack.clear(); self.frames.clear(); + self.frames.push(CallFrame { stack_base: 0, closure: None }); let mut result = self.eval(root); self.frames.pop(); - loop { match result { Ok(Value::TailCallRequest(payload)) => { let (next_obj, next_args) = *payload; if let Some(closure) = next_obj.as_any().downcast_ref::() { - let old_stack_top = 0; // Root is always base 0 - let closure_rc = Rc::new(closure.clone()); - - // Reset stack for the next call (TCO) self.stack.clear(); - - self.frames.push(CallFrame { - stack_base: old_stack_top, - closure: Some(closure_rc), - }); - - if let Some(count) = closure.positional_count - && next_args.len() == count as usize - { + self.frames.push(CallFrame { stack_base: 0, closure: Some(Rc::new(closure.clone())) }); + if let Some(count) = closure.positional_count && next_args.len() == count as usize { self.stack.extend(next_args); } else { self.unpack(&closure.parameter_node, &next_args, &mut 0)?; } - - result = self.eval(&closure.function_node); - + result = self.eval(&closure.exec_node); self.frames.pop(); - } else { - return Err(format!("Tail call target is not a closure: {}", next_obj.type_name())); - } + } else { return Err(format!("Tail call target is not a closure: {}", next_obj.type_name())); } } _ => return result, } @@ -370,115 +243,61 @@ impl VM { } pub fn run_with_args(&mut self, closure: &Closure, args: Vec) -> Result { - self.stack.clear(); - self.frames.clear(); - - let closure_rc = Rc::new(closure.clone()); - self.frames.push(CallFrame { - stack_base: 0, - closure: Some(closure_rc), - }); - - if let Some(count) = closure.positional_count - && args.len() == count as usize - { - self.stack.extend(args); - } else { - self.unpack(&closure.parameter_node, &args, &mut 0)?; - } - - self.eval(&closure.function_node) + self.stack.clear(); self.frames.clear(); + self.frames.push(CallFrame { stack_base: 0, closure: Some(Rc::new(closure.clone())) }); + if let Some(count) = closure.positional_count && args.len() == count as usize { self.stack.extend(args); } + else { self.unpack(&closure.parameter_node, &args, &mut 0)?; } + self.eval(&closure.exec_node) } pub fn run_with_args_observed(&mut self, observer: &mut O, closure: &Closure, args: Vec) -> Result { - self.stack.clear(); - self.frames.clear(); - - let closure_rc = Rc::new(closure.clone()); - self.frames.push(CallFrame { - stack_base: 0, - closure: Some(closure_rc), - }); - - if let Some(count) = closure.positional_count - && args.len() == count as usize - { - self.stack.extend(args); - } else { - self.unpack(&closure.parameter_node, &args, &mut 0)?; - } - - self.eval_observed(observer, &closure.function_node) + self.stack.clear(); self.frames.clear(); + self.frames.push(CallFrame { stack_base: 0, closure: Some(Rc::new(closure.clone())) }); + if let Some(count) = closure.positional_count && args.len() == count as usize { self.stack.extend(args); } + else { self.unpack(&closure.parameter_node, &args, &mut 0)?; } + self.eval_observed(observer, &closure.exec_node) } - pub fn run_with_observer(&mut self, observer: &mut O, root: &TypedNode) -> Result { - self.stack.clear(); - self.frames.clear(); - - self.frames.push(CallFrame { - stack_base: 0, - closure: None, - }); - + pub fn run_with_observer(&mut self, observer: &mut O, root: &ExecNode) -> Result { + 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: &TypedNode) -> Result { - dispatch_eval!(self, node, eval) - } + #[inline(always)] fn eval(&mut self, node: &ExecNode) -> Result { dispatch_eval!(self, node, eval) } - /// The observed path for debugging. - fn eval_observed(&mut self, observer: &mut O, node: &TypedNode) -> Result { + fn eval_observed(&mut self, observer: &mut O, node: &ExecNode) -> Result { observer.before_eval(self, node); - - // Wrap in a closure to ensure after_eval is called even on early returns (e.g. from ?) - let wrapper = |vm: &mut Self, obs: &mut O| -> Result { - dispatch_eval!(vm, node, eval_observed, obs) - }; - + let wrapper = |vm: &mut Self, obs: &mut O| -> Result { dispatch_eval!(vm, node, eval_observed, obs) }; let result = wrapper(self, observer); observer.after_eval(self, node, &result); result } - fn capture_upvalue(&mut self, addr: Address) -> Result>, String> { match addr { Address::Local(idx) => { let frame = self.frames.last().ok_or("No call frame")?; let abs_index = frame.stack_base + (idx as usize); if abs_index < self.stack.len() { - // Check if already boxed - if let Value::Cell(cell) = &self.stack[abs_index] { - Ok(cell.clone()) - } else { - // Box it + if let Value::Cell(cell) = &self.stack[abs_index] { Ok(cell.clone()) } + else { let val = self.stack[abs_index].clone(); let cell = Rc::new(RefCell::new(val)); self.stack[abs_index] = Value::Cell(cell.clone()); Ok(cell) } - } else { - Err(format!("Stack underflow capture local {}", idx)) - } + } else { Err(format!("Stack underflow capture local {}", idx)) } }, Address::Upvalue(idx) => { let frame = self.frames.last().ok_or("No call frame")?; if let Some(closure) = &frame.closure { let idx = idx as usize; - if idx < closure.upvalues.len() { - Ok(closure.upvalues[idx].clone()) - } else { - Err(format!("Upvalue access out of bounds capture {}", idx)) - } - } else { - Err("Current frame has no closure".to_string()) - } + if idx < closure.upvalues.len() { Ok(closure.upvalues[idx].clone()) } + else { Err(format!("Upvalue access out of bounds capture {}", idx)) } + } else { Err("Current frame has no closure".to_string()) } }, Address::Global(_) => Err("Cannot capture global directly".to_string()), } @@ -490,35 +309,22 @@ impl VM { let frame = self.frames.last().ok_or("No call frame")?; 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.borrow().clone()), - val => Ok(val.clone()), - } - } else { - Err(format!("Stack underflow access local {}", idx)) - } + match &self.stack[abs_index] { Value::Cell(cell) => Ok(cell.borrow().clone()), val => Ok(val.clone()) } + } else { Err(format!("Stack underflow access local {}", idx)) } }, Address::Global(idx) => { let idx = idx as usize; let globals = self.globals.borrow(); - if idx < globals.len() { - Ok(globals[idx].clone()) - } else { - Err(format!("Global access out of bounds {}", idx)) - } + if idx < globals.len() { Ok(globals[idx].clone()) } + else { Err(format!("Global access out of bounds {}", idx)) } }, Address::Upvalue(idx) => { let frame = self.frames.last().ok_or("No call frame")?; if let Some(closure) = &frame.closure { let idx = idx as usize; - if idx < closure.upvalues.len() { - Ok(closure.upvalues[idx].borrow().clone()) - } else { - Err(format!("Upvalue access out of bounds {}", idx)) - } - } else { - Err("Current frame has no closure (cannot access upvalues)".to_string()) - } + if idx < closure.upvalues.len() { Ok(closure.upvalues[idx].borrow().clone()) } + else { Err(format!("Upvalue access out of bounds {}", idx)) } + } else { Err("Current frame has no closure (cannot access upvalues)".to_string()) } } } } @@ -529,24 +335,16 @@ impl VM { let frame = self.frames.last().ok_or("No call frame")?; 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.borrow_mut() = value; - } else { - self.stack[abs_index] = value; - } - } else if abs_index == self.stack.len() { - self.stack.push(value); - } else { - return Err(format!("Stack gap write local {}", idx)); - } + if let Value::Cell(cell) = &self.stack[abs_index] { *cell.borrow_mut() = value; } + else { self.stack[abs_index] = value; } + } else if abs_index == self.stack.len() { self.stack.push(value); } + else { return Err(format!("Stack gap write local {}", idx)); } Ok(()) }, Address::Global(idx) => { let idx = idx as usize; let mut globals = self.globals.borrow_mut(); - if idx >= globals.len() { - globals.resize(idx + 1, Value::Void); - } + if idx >= globals.len() { globals.resize(idx + 1, Value::Void); } globals[idx] = value; Ok(()) }, @@ -554,58 +352,37 @@ impl VM { let frame = self.frames.last().ok_or("No call frame")?; if let Some(closure) = &frame.closure { let idx = idx as usize; - if idx < closure.upvalues.len() { - *closure.upvalues[idx].borrow_mut() = value; - Ok(()) - } else { - Err(format!("Upvalue assignment out of bounds {}", idx)) - } - } else { - Err("Current frame has no closure".to_string()) - } + if idx < closure.upvalues.len() { *closure.upvalues[idx].borrow_mut() = value; Ok(()) } + else { Err(format!("Upvalue assignment out of bounds {}", idx)) } + } else { Err("Current frame has no closure".to_string()) } }, } } fn flatten_value(val: Value, into: &mut Vec) { - if let Some(values) = val.as_slice() { - for item in values.iter() { - Self::flatten_value(item.clone(), into); - } - } else { - into.push(val); - } + if let Some(values) = val.as_slice() { for item in values.iter() { Self::flatten_value(item.clone(), into); } } + else { into.push(val); } } - fn prepare_args(&mut self, args: &TypedNode) -> Result, String> { + fn prepare_args(&mut self, args: &ExecNode) -> Result, String> { let mut arg_vals = Vec::new(); match &args.kind { - BoundKind::Tuple { elements } => { - self.eval_and_flatten(elements, &mut arg_vals)?; - } - _ => { - let v = self.eval(args)?; - VM::flatten_value(v, &mut arg_vals); - } + BoundKind::Tuple { elements } => { self.eval_and_flatten(elements, &mut arg_vals)?; } + _ => { VM::flatten_value(self.eval(args)?, &mut arg_vals); } } Ok(arg_vals) } - fn prepare_args_observed(&mut self, observer: &mut O, args: &TypedNode) -> Result, String> { + fn prepare_args_observed(&mut self, observer: &mut O, args: &ExecNode) -> Result, String> { let mut arg_vals = Vec::new(); match &args.kind { - BoundKind::Tuple { elements } => { - self.eval_observed_and_flatten(observer, elements, &mut arg_vals)?; - } - _ => { - let v = self.eval_observed(observer, args)?; - VM::flatten_value(v, &mut arg_vals); - } + BoundKind::Tuple { elements } => { self.eval_observed_and_flatten(observer, elements, &mut arg_vals)?; } + _ => { VM::flatten_value(self.eval_observed(observer, args)?, &mut arg_vals); } } Ok(arg_vals) } - fn eval_and_flatten(&mut self, elements: &[TypedNode], into: &mut Vec) -> Result<(), String> { + fn eval_and_flatten(&mut self, elements: &[ExecNode], into: &mut Vec) -> Result<(), String> { for e in elements { match &e.kind { BoundKind::Tuple { elements: sub } => self.eval_and_flatten(sub, into)?, @@ -615,7 +392,7 @@ impl VM { Ok(()) } - fn eval_observed_and_flatten(&mut self, observer: &mut O, elements: &[TypedNode], into: &mut Vec) -> Result<(), String> { + fn eval_observed_and_flatten(&mut self, observer: &mut O, elements: &[ExecNode], into: &mut Vec) -> Result<(), String> { for e in elements { match &e.kind { BoundKind::Tuple { elements: sub } => self.eval_observed_and_flatten(observer, sub, into)?, @@ -625,41 +402,26 @@ impl VM { Ok(()) } - /// Maps values into stack slots based on the parameter pattern. - /// Returns the number of slots filled. - fn unpack(&mut self, pattern: &TypedNode, values: &[Value], offset: &mut usize) -> Result<(), String> { + fn unpack(&mut self, pattern: &Node, T>, values: &[Value], offset: &mut usize) -> Result<(), String> { match &pattern.kind { BoundKind::Parameter { slot, .. } => { let val = values.get(*offset).cloned().unwrap_or(Value::Void); *offset += 1; - let frame = self.frames.last().ok_or("No call frame")?; let abs_index = frame.stack_base + (*slot as usize); - - if abs_index == self.stack.len() { - self.stack.push(val); - } else if abs_index < self.stack.len() { - self.stack[abs_index] = val; - } else { - return Err(format!("Stack gap during unpack at slot {}", slot)); - } + if abs_index == self.stack.len() { self.stack.push(val); } + else if abs_index < self.stack.len() { self.stack[abs_index] = val; } + else { return Err(format!("Stack gap during unpack at slot {}", slot)); } Ok(()) } BoundKind::Tuple { elements } => { - // If the current value at offset is a Tuple or Record, we dive into it. - // Otherwise, we assume the sequence was already flattened (e.g. by Specializer). if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) { *offset += 1; let mut sub_offset = 0; - for el in elements { - self.unpack(el, sub_values, &mut sub_offset)?; - } + for el in elements { self.unpack(el, sub_values, &mut sub_offset)?; } return Ok(()); } - - for el in elements { - self.unpack(el, values, offset)?; - } + for el in elements { self.unpack(el, values, offset)?; } Ok(()) } _ => Err("Invalid node in parameter pattern".to_string()), @@ -671,116 +433,33 @@ impl VM { mod tests { use super::*; use crate::ast::nodes::{Node, Symbol}; + use crate::ast::compiler::tco::TCO; use crate::ast::types::{SourceLocation, NodeIdentity, StaticType}; - fn make_dummy_identity() -> Rc { - Rc::new(NodeIdentity { - location: SourceLocation { line: 0, col: 0 }, - }) - } + fn make_dummy_identity() -> Rc { Rc::new(NodeIdentity { location: SourceLocation { line: 0, col: 0 } }) } #[test] fn test_capture_boxing_modification() { - // Goal: - // var x = 10; (Local 0) - // var f = lambda { x = 20; }; (Local 1) - // f(); - // return x; -> Should be 20 - let id = make_dummy_identity(); - - // 1. Define Lambda Body: x = 20 (Upvalue 0) let lambda_body = Node { - identity: id.clone(), - ty: StaticType::Void, - kind: BoundKind::Set { - addr: Address::Upvalue(0), - value: Box::new(Node { - identity: id.clone(), - ty: StaticType::Int, - kind: BoundKind::Constant(Value::Int(20)), - }), - }, + identity: id.clone(), ty: StaticType::Void, + kind: BoundKind::Set { addr: Address::Upvalue(0), value: Box::new(Node { identity: id.clone(), ty: StaticType::Int, kind: BoundKind::Constant(Value::Int(20)) }) }, }; - - // 2. Define Main Script let root = Node { - identity: id.clone(), - ty: StaticType::Int, + identity: id.clone(), ty: StaticType::Int, kind: BoundKind::Block { exprs: vec![ - // x = 10 (Local 0) - simulate definition by assignment to stack gap - Node { - identity: id.clone(), - ty: StaticType::Int, - kind: BoundKind::Set { - addr: Address::Local(0), - value: Box::new(Node { - identity: id.clone(), - ty: StaticType::Int, - kind: BoundKind::Constant(Value::Int(10)), - }), - }, - }, - // f = lambda capturing x (Local 1) - Node { - identity: id.clone(), - ty: StaticType::Any, - kind: BoundKind::Set { - addr: Address::Local(1), - value: Box::new(Node { - identity: id.clone(), - ty: StaticType::Any, - kind: BoundKind::Lambda { - params: Rc::new(Node { - identity: id.clone(), - ty: StaticType::Tuple(vec![]), - kind: BoundKind::Tuple { elements: vec![] }, - }), - upvalues: vec![Address::Local(0)], // Capture x - body: Rc::new(lambda_body), - positional_count: Some(0), - }, - }), - }, - }, - // f() - Node { - identity: id.clone(), - ty: StaticType::Void, - kind: BoundKind::Call { - callee: Box::new(Node { - identity: id.clone(), - ty: StaticType::Any, - kind: BoundKind::Get { addr: Address::Local(1), name: Symbol::from("f") }, - }), - args: Box::new(Node { - identity: id.clone(), - ty: StaticType::Tuple(vec![]), - kind: BoundKind::Tuple { elements: vec![] }, - }), - is_tail: false, - }, - }, - // return x (Local 0) - Node { - identity: id.clone(), - ty: StaticType::Int, - kind: BoundKind::Get { addr: Address::Local(0), name: Symbol::from("x") }, - }, + Node { identity: id.clone(), ty: StaticType::Int, kind: BoundKind::Set { addr: Address::Local(0), value: Box::new(Node { identity: id.clone(), ty: StaticType::Int, kind: BoundKind::Constant(Value::Int(10)) }) } }, + Node { identity: id.clone(), ty: StaticType::Any, kind: BoundKind::Set { addr: Address::Local(1), value: Box::new(Node { identity: id.clone(), ty: StaticType::Any, kind: BoundKind::Lambda { params: Rc::new(Node { identity: id.clone(), ty: StaticType::Tuple(vec![]), kind: BoundKind::Tuple { elements: vec![] } }), upvalues: vec![Address::Local(0)], body: Rc::new(lambda_body), positional_count: Some(0) } }) } }, + Node { identity: id.clone(), ty: StaticType::Void, kind: BoundKind::Call { callee: Box::new(Node { identity: id.clone(), ty: StaticType::Any, kind: BoundKind::Get { addr: Address::Local(1), name: Symbol::from("f") } }), args: Box::new(Node { identity: id.clone(), ty: StaticType::Tuple(vec![]), kind: BoundKind::Tuple { elements: vec![] } }) } }, + Node { identity: id.clone(), ty: StaticType::Int, kind: BoundKind::Get { addr: Address::Local(0), name: Symbol::from("x") } }, ], }, }; - let globals = Rc::new(RefCell::new(Vec::new())); let mut vm = VM::new(globals); - - let result = vm.run(&root); - - match result { - Ok(Value::Int(val)) => assert_eq!(val, 20, "Captured variable should be modified to 20"), - Ok(val) => panic!("Expected Int(20), got {:?}", val), - Err(e) => panic!("VM Error: {}", e), - } + let exec_root = TCO::optimize(root); + let result = vm.run(&exec_root); + match result { Ok(Value::Int(val)) => assert_eq!(val, 20), _ => panic!("Expected Int(20)") } } }