Remove is_tail field from Call node

The `is_tail` field on the `BoundKind::Call` node was an intermediate
representation for tail-call optimization and is no longer needed as a
distinct field. The TCO pass now embeds this information directly into
the `RuntimeMetadata` of the `ExecNode`, which is the VM's internal AST.
This simplifies the `BoundKind::Call` structure and removes redundant
information.
This commit is contained in:
Michael Schimmel
2026-02-21 22:01:03 +01:00
parent 78dff07930
commit ff1024ee49
10 changed files with 236 additions and 561 deletions
+125 -446
View File
@@ -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<TypedNode>,
pub function_node: Rc<TypedNode>,
pub exec_node: Rc<ExecNode>,
pub upvalues: Vec<Rc<RefCell<Value>>>,
/// 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<u32>,
}
impl Closure {
#[inline]
pub fn new(params: Rc<TypedNode>, body: Rc<TypedNode>, exec: Rc<ExecNode>, upvalues: Vec<Rc<RefCell<Value>>>, positional_count: Option<u32>) -> 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<Rc<Closure>>,
}
/// 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<Value, String>) {}
fn before_eval(&mut self, _vm: &VM, _node: &ExecNode) {}
fn after_eval(&mut self, _vm: &VM, _node: &ExecNode, _res: &Result<Value, String>) {}
}
/// 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<String>,
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<Value, String>) {
fn after_eval(&mut self, vm: &VM, node: &ExecNode, res: &Result<Value, String>) {
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::<Closure>() {
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<RefCell<Vec<Value>>>) -> Self {
Self {
stack: Vec::new(),
globals,
frames: Vec::new(),
}
}
pub fn new(globals: Rc<RefCell<Vec<Value>>>) -> Self { Self { stack: Vec::new(), globals, frames: Vec::new() } }
pub fn run(&mut self, root: &TypedNode) -> Result<Value, String> {
self.stack.clear();
self.frames.clear();
self.frames.push(CallFrame {
stack_base: 0,
closure: None,
});
pub fn run(&mut self, root: &ExecNode) -> Result<Value, String> {
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::<Closure>() {
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<Value>) -> Result<Value, String> {
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<O: VMObserver>(&mut self, observer: &mut O, closure: &Closure, args: Vec<Value>) -> Result<Value, String> {
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<O: VMObserver>(&mut self, observer: &mut O, root: &TypedNode) -> Result<Value, String> {
self.stack.clear();
self.frames.clear();
self.frames.push(CallFrame {
stack_base: 0,
closure: None,
});
pub fn run_with_observer<O: VMObserver>(&mut self, observer: &mut O, root: &ExecNode) -> 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: &TypedNode) -> Result<Value, String> {
dispatch_eval!(self, node, eval)
}
#[inline(always)] fn eval(&mut self, node: &ExecNode) -> Result<Value, String> { dispatch_eval!(self, node, eval) }
/// The observed path for debugging.
fn eval_observed<O: VMObserver>(&mut self, observer: &mut O, node: &TypedNode) -> Result<Value, String> {
fn eval_observed<O: VMObserver>(&mut self, observer: &mut O, node: &ExecNode) -> Result<Value, String> {
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<Value, String> {
dispatch_eval!(vm, node, eval_observed, obs)
};
let wrapper = |vm: &mut Self, obs: &mut O| -> Result<Value, String> { 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<Rc<RefCell<Value>>, 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<Value>) {
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<Vec<Value>, String> {
fn prepare_args(&mut self, args: &ExecNode) -> Result<Vec<Value>, 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<O: VMObserver>(&mut self, observer: &mut O, args: &TypedNode) -> Result<Vec<Value>, String> {
fn prepare_args_observed<O: VMObserver>(&mut self, observer: &mut O, args: &ExecNode) -> Result<Vec<Value>, 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<Value>) -> Result<(), String> {
fn eval_and_flatten(&mut self, elements: &[ExecNode], into: &mut Vec<Value>) -> 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<O: VMObserver>(&mut self, observer: &mut O, elements: &[TypedNode], into: &mut Vec<Value>) -> Result<(), String> {
fn eval_observed_and_flatten<O: VMObserver>(&mut self, observer: &mut O, elements: &[ExecNode], into: &mut Vec<Value>) -> 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<T>(&mut self, pattern: &Node<BoundKind<T>, 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<NodeIdentity> {
Rc::new(NodeIdentity {
location: SourceLocation { line: 0, col: 0 },
})
}
fn make_dummy_identity() -> Rc<NodeIdentity> { 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)") }
}
}