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:
@@ -233,8 +233,7 @@ impl Binder {
|
|||||||
|
|
||||||
Ok(self.make_node(node.identity.clone(), BoundKind::Call {
|
Ok(self.make_node(node.identity.clone(), BoundKind::Call {
|
||||||
callee: Box::new(callee),
|
callee: Box::new(callee),
|
||||||
args: Box::new(args),
|
args: Box::new(args)
|
||||||
is_tail: false
|
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ pub enum BoundKind<T = ()> {
|
|||||||
Call {
|
Call {
|
||||||
callee: Box<BoundNode<T>>,
|
callee: Box<BoundNode<T>>,
|
||||||
args: Box<BoundNode<T>>,
|
args: Box<BoundNode<T>>,
|
||||||
is_tail: bool,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Block {
|
Block {
|
||||||
@@ -141,8 +140,8 @@ where T: PartialEq {
|
|||||||
BoundKind::Lambda { params: pb, upvalues: ub, body: bb, positional_count: pcb }) => {
|
BoundKind::Lambda { params: pb, upvalues: ub, body: bb, positional_count: pcb }) => {
|
||||||
pa == pb && ua == ub && ba == bb && pca == 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 }) => {
|
(BoundKind::Call { callee: ca, args: aa }, BoundKind::Call { callee: cb, args: ab }) => {
|
||||||
ca == cb && aa == ab && ta == tb
|
ca == cb && aa == ab
|
||||||
}
|
}
|
||||||
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => {
|
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => {
|
||||||
ea == eb
|
ea == eb
|
||||||
@@ -184,7 +183,7 @@ impl<T> BoundKind<T> {
|
|||||||
};
|
};
|
||||||
format!("LAMBDA({}, Captures:{})", p_str, upvalues.len())
|
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::Block { .. } => "BLOCK".to_string(),
|
||||||
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
|
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
|
||||||
BoundKind::Record { fields } => format!("RECORD({})", fields.len()),
|
BoundKind::Record { fields } => format!("RECORD({})", fields.len()),
|
||||||
|
|||||||
@@ -137,9 +137,8 @@ impl Dumper {
|
|||||||
self.log(&format!("Parameter (Name: '{}', Slot: {})", name.name, slot), node);
|
self.log(&format!("Parameter (Name: '{}', Slot: {})", name.name, slot), node);
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Call { callee, args, is_tail } => {
|
BoundKind::Call { callee, args } => {
|
||||||
let label = if *is_tail { "Call (TCO)" } else { "Call" };
|
self.log("Call", node);
|
||||||
self.log(label, node);
|
|
||||||
self.indent += 1;
|
self.indent += 1;
|
||||||
|
|
||||||
self.write_indent();
|
self.write_indent();
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ impl<'a> LambdaCollector<'a> {
|
|||||||
self.visit(value);
|
self.visit(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Call { callee, args, .. } => {
|
BoundKind::Call { callee, args } => {
|
||||||
self.visit(callee);
|
self.visit(callee);
|
||||||
self.visit(args);
|
self.visit(args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ impl Optimizer {
|
|||||||
|
|
||||||
fn visit_node(&self, node: TypedNode) -> TypedNode {
|
fn visit_node(&self, node: TypedNode) -> TypedNode {
|
||||||
let (new_kind, new_ty) = match node.kind {
|
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 callee = self.visit_node(*callee);
|
||||||
let args = self.visit_node(*args);
|
let args = self.visit_node(*args);
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ impl Optimizer {
|
|||||||
for (i, cell) in closure.upvalues.iter().enumerate() {
|
for (i, cell) in closure.upvalues.iter().enumerate() {
|
||||||
sub.add_upvalue(i as u32, cell.borrow().clone());
|
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());
|
let inlined_body = sub.inline((*closure.function_node).clone());
|
||||||
|
|
||||||
// Try to collapse the now-stateless lambda call
|
// Try to collapse the now-stateless lambda call
|
||||||
@@ -73,6 +74,7 @@ impl Optimizer {
|
|||||||
for (i, cell) in closure.upvalues.iter().enumerate() {
|
for (i, cell) in closure.upvalues.iter().enumerate() {
|
||||||
sub.add_upvalue(i as u32, cell.borrow().clone());
|
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 inlined_body = sub.inline((*closure.function_node).clone());
|
||||||
|
|
||||||
let cracked_lambda = Node {
|
let cracked_lambda = Node {
|
||||||
@@ -87,12 +89,12 @@ impl Optimizer {
|
|||||||
};
|
};
|
||||||
return Node {
|
return Node {
|
||||||
identity: node.identity,
|
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,
|
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 } => {
|
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))
|
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::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::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::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)),
|
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();
|
let exprs = exprs.into_iter().map(|e| self.inline_recursive(e, depth, upvalue_subs)).collect();
|
||||||
(BoundKind::Block { exprs }, node.ty)
|
(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 callee = Box::new(self.inline_recursive(*callee, depth, upvalue_subs));
|
||||||
let args = Box::new(self.inline_recursive(*args, 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 } => {
|
BoundKind::DefLocal { name, slot, value, captured_by } => {
|
||||||
let value = Box::new(self.inline_recursive(*value, depth, upvalue_subs));
|
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();
|
let exprs = exprs.into_iter().map(|e| self.reindex_upvalues(e, mapping)).collect();
|
||||||
(BoundKind::Block { exprs }, node.ty)
|
(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 callee = Box::new(self.reindex_upvalues(*callee, mapping));
|
||||||
let args = Box::new(self.reindex_upvalues(*args, 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 } => {
|
BoundKind::DefLocal { name, slot, value, captured_by } => {
|
||||||
let value = Box::new(self.reindex_upvalues(*value, mapping));
|
let value = Box::new(self.reindex_upvalues(*value, mapping));
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ impl Specializer {
|
|||||||
|
|
||||||
fn visit_node(&self, node: TypedNode) -> TypedNode {
|
fn visit_node(&self, node: TypedNode) -> TypedNode {
|
||||||
let (new_kind, new_ty) = match node.kind {
|
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());
|
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
|
// Recursive traversal for other nodes
|
||||||
|
|||||||
+78
-81
@@ -1,123 +1,120 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use crate::ast::nodes::Node;
|
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<TypedNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ExecNode is the AST used by the VM. It carries TCO flags and links to source.
|
||||||
|
pub type ExecNode = Node<BoundKind<RuntimeMetadata>, RuntimeMetadata>;
|
||||||
|
|
||||||
pub struct TCO;
|
pub struct TCO;
|
||||||
|
|
||||||
impl TCO {
|
impl TCO {
|
||||||
pub fn optimize<T: Clone>(node: Node<BoundKind<T>, T>) -> Node<BoundKind<T>, T> {
|
/// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions.
|
||||||
// Top-level starts NOT in tail position usually, unless the script result is returned immediately
|
pub fn optimize(node: TypedNode) -> ExecNode {
|
||||||
// which implies tail position for the script body if viewed as a function.
|
Self::transform(Rc::new(node), true)
|
||||||
// Let's assume standard execution context (not tail call) for the script root.
|
|
||||||
Self::transform(node, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform<T: Clone>(node: Node<BoundKind<T>, T>, is_tail_position: bool) -> Node<BoundKind<T>, T> {
|
fn transform(node_rc: Rc<TypedNode>, is_tail_position: bool) -> ExecNode {
|
||||||
match node.kind {
|
let node = &*node_rc;
|
||||||
BoundKind::Call { callee, args, .. } => {
|
let new_kind = match &node.kind {
|
||||||
let new_callee = Box::new(Self::transform(*callee, false));
|
BoundKind::Call { callee, args } => {
|
||||||
let new_args = Box::new(Self::transform(*args, false));
|
BoundKind::Call {
|
||||||
|
callee: Box::new(Self::transform(Rc::new((**callee).clone()), false)),
|
||||||
Node {
|
args: Box::new(Self::transform(Rc::new((**args).clone()), false)),
|
||||||
kind: BoundKind::Call {
|
|
||||||
callee: new_callee,
|
|
||||||
args: new_args,
|
|
||||||
is_tail: is_tail_position,
|
|
||||||
},
|
|
||||||
..node
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::If { cond, then_br, else_br } => {
|
BoundKind::If { cond, then_br, else_br } => {
|
||||||
let new_cond = Box::new(Self::transform(*cond, false));
|
BoundKind::If {
|
||||||
let new_then = Box::new(Self::transform(*then_br, is_tail_position));
|
cond: Box::new(Self::transform(Rc::new((**cond).clone()), false)),
|
||||||
let new_else = else_br.map(|e| Box::new(Self::transform(*e, is_tail_position)));
|
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))),
|
||||||
Node {
|
|
||||||
kind: BoundKind::If {
|
|
||||||
cond: new_cond,
|
|
||||||
then_br: new_then,
|
|
||||||
else_br: new_else,
|
|
||||||
},
|
|
||||||
..node
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Block { exprs } => {
|
BoundKind::Block { exprs } => {
|
||||||
if exprs.is_empty() {
|
if exprs.is_empty() {
|
||||||
return Node {
|
BoundKind::Block { exprs: vec![] }
|
||||||
kind: BoundKind::Block { exprs },
|
} else {
|
||||||
..node
|
let last_idx = exprs.len() - 1;
|
||||||
};
|
let mut new_exprs = Vec::with_capacity(exprs.len());
|
||||||
}
|
|
||||||
|
|
||||||
let last_idx = exprs.len() - 1;
|
for (i, expr) in exprs.iter().enumerate() {
|
||||||
let mut new_exprs = Vec::with_capacity(exprs.len());
|
let is_last = i == last_idx;
|
||||||
|
new_exprs.push(Self::transform(Rc::new(expr.clone()), is_tail_position && is_last));
|
||||||
for (i, expr) in exprs.into_iter().enumerate() {
|
}
|
||||||
let is_last = i == last_idx;
|
BoundKind::Block { exprs: new_exprs }
|
||||||
// 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::Lambda { params, upvalues, body, positional_count } => {
|
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
||||||
// The body of a lambda is implicitly in tail position when the lambda is called.
|
BoundKind::Lambda {
|
||||||
let new_body = Rc::new(Self::transform((*body).clone(), true));
|
params: Rc::new(Self::transform(params.clone(), false)),
|
||||||
|
upvalues: upvalues.clone(),
|
||||||
Node {
|
body: Rc::new(Self::transform(body.clone(), true)),
|
||||||
kind: BoundKind::Lambda {
|
positional_count: *positional_count,
|
||||||
params: params.clone(),
|
|
||||||
upvalues,
|
|
||||||
body: new_body,
|
|
||||||
positional_count,
|
|
||||||
},
|
|
||||||
..node
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Set { addr, value } => {
|
BoundKind::Set { addr, value } => {
|
||||||
Node {
|
BoundKind::Set { addr: *addr, value: Box::new(Self::transform(Rc::new((**value).clone()), false)) }
|
||||||
kind: BoundKind::Set { addr, value: Box::new(Self::transform(*value, false)) },
|
|
||||||
..node
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
BoundKind::DefLocal { name, slot, value, captured_by } => {
|
BoundKind::DefLocal { name, slot, value, captured_by } => {
|
||||||
Node {
|
BoundKind::DefLocal {
|
||||||
kind: BoundKind::DefLocal { name, slot, value: Box::new(Self::transform(*value, false)), captured_by },
|
name: name.clone(),
|
||||||
..node
|
slot: *slot,
|
||||||
|
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
|
||||||
|
captured_by: captured_by.clone()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
BoundKind::DefGlobal { name, global_index, value } => {
|
BoundKind::DefGlobal { name, global_index, value } => {
|
||||||
Node {
|
BoundKind::DefGlobal {
|
||||||
kind: BoundKind::DefGlobal { name, global_index, value: Box::new(Self::transform(*value, false)) },
|
name: name.clone(),
|
||||||
..node
|
global_index: *global_index,
|
||||||
|
value: Box::new(Self::transform(Rc::new((**value).clone()), false))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { fields } => {
|
||||||
let new_fields = fields.into_iter().map(|(k, v)| {
|
let new_fields = fields.iter().map(|(k, v)| {
|
||||||
(Self::transform(k, false), Self::transform(v, false))
|
(Self::transform(Rc::new(k.clone()), false), Self::transform(Rc::new(v.clone()), false))
|
||||||
}).collect();
|
}).collect();
|
||||||
Node {
|
BoundKind::Record { fields: new_fields }
|
||||||
kind: BoundKind::Record { fields: new_fields },
|
|
||||||
..node
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
BoundKind::Tuple { elements } => {
|
BoundKind::Tuple { elements } => {
|
||||||
let new_elements = elements.into_iter().map(|e| Self::transform(e, false)).collect();
|
let new_elements = elements.iter().map(|e| Self::transform(Rc::new(e.clone()), false)).collect();
|
||||||
Node {
|
BoundKind::Tuple { elements: new_elements }
|
||||||
kind: BoundKind::Tuple { elements: new_elements },
|
|
||||||
..node
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Atoms and variables don't change
|
Node {
|
||||||
_ => node,
|
identity: node.identity.clone(),
|
||||||
|
kind: new_kind,
|
||||||
|
ty: RuntimeMetadata {
|
||||||
|
ty: node.ty.clone(),
|
||||||
|
is_tail: is_tail_position,
|
||||||
|
original: node_rc,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ impl TypeChecker {
|
|||||||
}, fn_ty)
|
}, fn_ty)
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Call { callee, args, is_tail } => {
|
BoundKind::Call { callee, args } => {
|
||||||
let callee_typed = self.check_node(*callee, ctx)?;
|
let callee_typed = self.check_node(*callee, ctx)?;
|
||||||
let args_typed = self.check_node(*args, ctx)?;
|
let args_typed = self.check_node(*args, ctx)?;
|
||||||
|
|
||||||
@@ -271,8 +271,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
(BoundKind::Call {
|
(BoundKind::Call {
|
||||||
callee: Box::new(callee_typed),
|
callee: Box::new(callee_typed),
|
||||||
args: Box::new(args_typed),
|
args: Box::new(args_typed)
|
||||||
is_tail
|
|
||||||
}, ret_ty)
|
}, ret_ty)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+10
-9
@@ -14,7 +14,7 @@ use crate::ast::compiler::lambda_collector::LambdaCollector;
|
|||||||
use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry};
|
use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry};
|
||||||
use crate::ast::compiler::optimizer::Optimizer;
|
use crate::ast::compiler::optimizer::Optimizer;
|
||||||
use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer};
|
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;
|
||||||
use crate::ast::rtl::intrinsics;
|
use crate::ast::rtl::intrinsics;
|
||||||
|
|
||||||
@@ -67,9 +67,10 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
|
|||||||
|
|
||||||
let checker = TypeChecker::new(self.global_types.clone());
|
let checker = TypeChecker::new(self.global_types.clone());
|
||||||
let typed_ast = checker.check(bound_ast, &[])?;
|
let typed_ast = checker.check(bound_ast, &[])?;
|
||||||
|
let exec_ast = TCO::optimize(typed_ast);
|
||||||
|
|
||||||
let mut vm = VM::new(self.global_values.clone());
|
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.)
|
/// Backend: Optimization (TCO, etc.)
|
||||||
pub fn link(&self, node: TypedNode) -> TypedNode {
|
pub fn link(&self, node: TypedNode) -> ExecNode {
|
||||||
// 1. Specialize (Always performed for correctness)
|
// 1. Specialize (Always performed for correctness)
|
||||||
let specialized = self.specialize_node(node);
|
let specialized = self.specialize_node(node);
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@ impl Environment {
|
|||||||
let optimizer = Optimizer::new(self.optimization_level);
|
let optimizer = Optimizer::new(self.optimization_level);
|
||||||
let optimized = optimizer.optimize(specialized);
|
let optimized = optimizer.optimize(specialized);
|
||||||
|
|
||||||
// 3. TCO (Always performed)
|
// 3. TCO (Always performed, converts to ExecNode)
|
||||||
TCO::optimize(optimized)
|
TCO::optimize(optimized)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +230,7 @@ impl Environment {
|
|||||||
let optimizer = Optimizer::new(opt_level);
|
let optimizer = Optimizer::new(opt_level);
|
||||||
let optimized_ast = optimizer.optimize(specialized_ast);
|
let optimized_ast = optimizer.optimize(specialized_ast);
|
||||||
|
|
||||||
// 4. TCO
|
// 4. TCO (converts to ExecNode)
|
||||||
let tco_ast = TCO::optimize(optimized_ast);
|
let tco_ast = TCO::optimize(optimized_ast);
|
||||||
|
|
||||||
// 5. Compile to Value (VM)
|
// 5. Compile to Value (VM)
|
||||||
@@ -240,7 +241,7 @@ impl Environment {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 6. Determine correct return type from the newly inferred function signature
|
// 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()
|
sig.ret.clone()
|
||||||
} else {
|
} else {
|
||||||
StaticType::Any
|
StaticType::Any
|
||||||
@@ -261,7 +262,7 @@ impl Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Runtime: Execute the linked AST in the VM
|
/// Runtime: Execute the linked AST in the VM
|
||||||
pub fn run(&self, node: &TypedNode) -> Result<Value, String> {
|
pub fn run(&self, node: &ExecNode) -> Result<Value, String> {
|
||||||
let mut vm = VM::new(self.global_values.clone());
|
let mut vm = VM::new(self.global_values.clone());
|
||||||
let mut result = vm.run(node)?;
|
let mut result = vm.run(node)?;
|
||||||
|
|
||||||
@@ -269,7 +270,7 @@ impl Environment {
|
|||||||
if let Value::Object(obj) = &result
|
if let Value::Object(obj) = &result
|
||||||
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
|
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
|
||||||
{
|
{
|
||||||
result = vm.run(&closure.function_node)?;
|
result = vm.run(&closure.exec_node)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT: Resolve any pending tail call requests from the top-level execution
|
// 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
|
if let Ok(Value::Object(obj)) = &result
|
||||||
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
|
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
|
||||||
{
|
{
|
||||||
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
|
// Resolve top-level tail calls
|
||||||
|
|||||||
+125
-446
@@ -2,25 +2,29 @@ use std::rc::Rc;
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode};
|
use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode};
|
||||||
|
use crate::ast::compiler::tco::ExecNode;
|
||||||
use crate::ast::types::{Value, Object};
|
use crate::ast::types::{Value, Object};
|
||||||
|
use crate::ast::nodes::Node;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Closure {
|
pub struct Closure {
|
||||||
pub parameter_node: Rc<TypedNode>,
|
pub parameter_node: Rc<TypedNode>,
|
||||||
pub function_node: Rc<TypedNode>,
|
pub function_node: Rc<TypedNode>,
|
||||||
|
pub exec_node: Rc<ExecNode>,
|
||||||
pub upvalues: Vec<Rc<RefCell<Value>>>,
|
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>,
|
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 {
|
impl Object for Closure {
|
||||||
fn type_name(&self) -> &'static str {
|
fn type_name(&self) -> &'static str { "closure" }
|
||||||
"closure"
|
fn as_any(&self) -> &dyn Any { self }
|
||||||
}
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -29,53 +33,37 @@ struct CallFrame {
|
|||||||
closure: Option<Rc<Closure>>,
|
closure: Option<Rc<Closure>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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: &TypedNode) {}
|
fn before_eval(&mut self, _vm: &VM, _node: &ExecNode) {}
|
||||||
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>) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default observer that does nothing and should be optimized away
|
|
||||||
pub struct NoOpObserver;
|
pub struct NoOpObserver;
|
||||||
impl VMObserver for NoOpObserver {}
|
impl VMObserver for NoOpObserver {}
|
||||||
|
|
||||||
/// 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,
|
indent: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TracingObserver {
|
impl TracingObserver {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self { Self { logs: Vec::new(), indent: 0 } }
|
||||||
Self { logs: Vec::new(), indent: 0 }
|
fn pad(&self) -> String { "| ".repeat(self.indent) }
|
||||||
}
|
|
||||||
|
|
||||||
fn pad(&self) -> String {
|
|
||||||
"| ".repeat(self.indent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TracingObserver {
|
impl Default for TracingObserver { fn default() -> Self { Self::new() } }
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VMObserver for TracingObserver {
|
impl VMObserver for TracingObserver {
|
||||||
const ACTIVE: bool = true;
|
const ACTIVE: bool = true;
|
||||||
|
fn before_eval(&mut self, _vm: &VM, node: &ExecNode) {
|
||||||
fn before_eval(&mut self, _vm: &VM, node: &TypedNode) {
|
|
||||||
let pad = self.pad();
|
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;
|
self.indent += 1;
|
||||||
}
|
}
|
||||||
|
fn after_eval(&mut self, vm: &VM, node: &ExecNode, res: &Result<Value, String>) {
|
||||||
fn after_eval(&mut self, vm: &VM, node: &TypedNode, res: &Result<Value, String>) {
|
|
||||||
self.indent = self.indent.saturating_sub(1);
|
self.indent = self.indent.saturating_sub(1);
|
||||||
let pad = self.pad();
|
let pad = self.pad();
|
||||||
|
|
||||||
// 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 = format!("{}| ", pad);
|
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));
|
self.logs.push(format!("{}}} -> {}", pad, res_str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,124 +88,71 @@ macro_rules! dispatch_eval {
|
|||||||
match &$node.kind {
|
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::Parameter { .. } => Ok(Value::Void),
|
BoundKind::Parameter { .. } => Ok(Value::Void),
|
||||||
|
|
||||||
BoundKind::DefGlobal { global_index, value, .. } => {
|
BoundKind::DefGlobal { global_index, value, .. } => {
|
||||||
let val = $self.$eval_method($($observer,)? 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);
|
|
||||||
}
|
|
||||||
globals[idx] = val.clone();
|
globals[idx] = val.clone();
|
||||||
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_method($($observer,)? 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_method($($observer,)? 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))) } else { val };
|
||||||
Value::Cell(Rc::new(RefCell::new(val)))
|
|
||||||
} else {
|
|
||||||
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 abs_index == $self.stack.len() { $self.stack.push(final_val.clone()); }
|
||||||
if abs_index == $self.stack.len() {
|
else if abs_index < $self.stack.len() { $self.stack[abs_index] = final_val.clone(); }
|
||||||
$self.stack.push(final_val.clone());
|
else { return Err(format!("Stack gap at local {}", slot)); }
|
||||||
} 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)
|
Ok(final_val)
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::If { cond, then_br, else_br } => {
|
BoundKind::If { cond, then_br, else_br } => {
|
||||||
let c = $self.$eval_method($($observer,)? cond)?;
|
let c = $self.$eval_method($($observer,)? cond)?;
|
||||||
if c.is_truthy() {
|
if c.is_truthy() { $self.$eval_method($($observer,)? then_br) }
|
||||||
$self.$eval_method($($observer,)? then_br)
|
else if let Some(e) = else_br { $self.$eval_method($($observer,)? e) }
|
||||||
} else if let Some(e) = else_br {
|
else { Ok(Value::Void) }
|
||||||
$self.$eval_method($($observer,)? e)
|
|
||||||
} else {
|
|
||||||
Ok(Value::Void)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
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_method($($observer,)? e)?; }
|
||||||
last = $self.$eval_method($($observer,)? e)?;
|
|
||||||
}
|
|
||||||
Ok(last)
|
Ok(last)
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
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());
|
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)?);
|
// 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);
|
||||||
|
|
||||||
let closure = Closure {
|
|
||||||
parameter_node: params.clone(),
|
|
||||||
function_node: body.clone(),
|
|
||||||
upvalues: captured,
|
|
||||||
positional_count,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Value::Object(Rc::new(closure)))
|
Ok(Value::Object(Rc::new(closure)))
|
||||||
},
|
},
|
||||||
|
BoundKind::Call { callee, args } => {
|
||||||
BoundKind::Call { callee, args, is_tail } => {
|
|
||||||
let mut func_val = $self.$eval_method($($observer,)? callee)?;
|
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 {
|
let mut arg_vals = match &args.kind {
|
||||||
BoundKind::Tuple { elements } => {
|
BoundKind::Tuple { elements } => {
|
||||||
let mut vals = Vec::with_capacity(elements.len());
|
let mut vals = Vec::with_capacity(elements.len());
|
||||||
let mut is_complex = false;
|
let mut is_complex = false;
|
||||||
for e in elements {
|
for e in elements {
|
||||||
if matches!(e.kind, BoundKind::Tuple { .. }) {
|
if matches!(e.kind, BoundKind::Tuple { .. }) { is_complex = true; break; }
|
||||||
is_complex = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
vals.push($self.$eval_method($($observer,)? e)?);
|
vals.push($self.$eval_method($($observer,)? e)?);
|
||||||
}
|
}
|
||||||
if is_complex {
|
if is_complex { get_arg_vals!($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)
|
|
||||||
} 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)
|
|
||||||
}
|
}
|
||||||
|
_ => { get_arg_vals!($self, $($observer,)? args) }
|
||||||
};
|
};
|
||||||
|
|
||||||
if *is_tail {
|
if $node.ty.is_tail {
|
||||||
match func_val {
|
match func_val {
|
||||||
Value::Object(obj) => return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))),
|
Value::Object(obj) => return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))),
|
||||||
Value::Function(f) => return Ok(f(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>() {
|
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.frames.push(CallFrame { stack_base: old_stack_top, closure: Some(closure_rc.clone()) });
|
||||||
$self.frames.push(CallFrame {
|
if let Some(count) = closure.positional_count && arg_vals.len() == count as usize {
|
||||||
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.stack.extend(arg_vals);
|
$self.stack.extend(arg_vals);
|
||||||
} else {
|
} else {
|
||||||
// Unpack arguments into slots based on the closure's parameter pattern
|
|
||||||
$self.unpack(&closure.parameter_node, &arg_vals, &mut 0)?;
|
$self.unpack(&closure.parameter_node, &arg_vals, &mut 0)?;
|
||||||
}
|
}
|
||||||
|
let result = $self.$eval_method($($observer,)? &closure.exec_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(payload)) => {
|
Ok(Value::TailCallRequest(payload)) => {
|
||||||
let (next_obj, next_args) = *payload;
|
let (next_obj, next_args) = *payload;
|
||||||
@@ -266,103 +185,57 @@ macro_rules! dispatch_eval {
|
|||||||
},
|
},
|
||||||
res => break res,
|
res => break res,
|
||||||
}
|
}
|
||||||
} else {
|
} else { break Err(format!("Object is not a closure: {}", obj.type_name())); }
|
||||||
break Err(format!("Object is not a closure: {}", obj.type_name()));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
_ => break Err(format!("Attempt to call non-function: {}", func_val)),
|
_ => break Err(format!("Attempt to call non-function: {}", func_val)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
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_method($($observer,)? e)?); }
|
||||||
vals.push($self.$eval_method($($observer,)? e)?);
|
|
||||||
}
|
|
||||||
Ok(Value::make_tuple(vals))
|
Ok(Value::make_tuple(vals))
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { fields } => {
|
||||||
let mut keys = Vec::with_capacity(fields.len());
|
let mut keys = Vec::with_capacity(fields.len());
|
||||||
let mut values = Vec::with_capacity(fields.len());
|
let mut values = Vec::with_capacity(fields.len());
|
||||||
for (k, v) in fields {
|
for (k, v) in fields {
|
||||||
let key = $self.$eval_method($($observer,)? k)?;
|
let key = $self.$eval_method($($observer,)? k)?;
|
||||||
let val = $self.$eval_method($($observer,)? v)?;
|
let val = $self.$eval_method($($observer,)? v)?;
|
||||||
|
if let Value::Keyword(kw) = key { keys.push(kw); values.push(val); }
|
||||||
if let Value::Keyword(kw) = key {
|
else { return Err(format!("Record key must be keyword, got {}", key)); }
|
||||||
keys.push(kw);
|
|
||||||
values.push(val);
|
|
||||||
} else {
|
|
||||||
return Err(format!("Record key must be keyword, got {}", key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(Value::make_record(keys, values))
|
Ok(Value::make_record(keys, values))
|
||||||
},
|
},
|
||||||
|
BoundKind::Expansion { bound_expanded, .. } => { $self.$eval_method($($observer,)? bound_expanded) }
|
||||||
BoundKind::Expansion { original_call: _, bound_expanded } => {
|
BoundKind::Extension(ext) => { Err(format!("Execution of extension '{}' not implemented yet", ext.display_name())) }
|
||||||
$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()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VM {
|
impl VM {
|
||||||
pub fn new(globals: Rc<RefCell<Vec<Value>>>) -> Self {
|
pub fn new(globals: Rc<RefCell<Vec<Value>>>) -> Self { Self { stack: Vec::new(), globals, frames: Vec::new() } }
|
||||||
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);
|
let mut result = self.eval(root);
|
||||||
self.frames.pop();
|
self.frames.pop();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match result {
|
match result {
|
||||||
Ok(Value::TailCallRequest(payload)) => {
|
Ok(Value::TailCallRequest(payload)) => {
|
||||||
let (next_obj, next_args) = *payload;
|
let (next_obj, next_args) = *payload;
|
||||||
if let Some(closure) = next_obj.as_any().downcast_ref::<Closure>() {
|
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.stack.clear();
|
||||||
|
self.frames.push(CallFrame { stack_base: 0, closure: Some(Rc::new(closure.clone())) });
|
||||||
self.frames.push(CallFrame {
|
if let Some(count) = closure.positional_count && next_args.len() == count as usize {
|
||||||
stack_base: old_stack_top,
|
|
||||||
closure: Some(closure_rc),
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(count) = closure.positional_count
|
|
||||||
&& next_args.len() == count as usize
|
|
||||||
{
|
|
||||||
self.stack.extend(next_args);
|
self.stack.extend(next_args);
|
||||||
} else {
|
} else {
|
||||||
self.unpack(&closure.parameter_node, &next_args, &mut 0)?;
|
self.unpack(&closure.parameter_node, &next_args, &mut 0)?;
|
||||||
}
|
}
|
||||||
|
result = self.eval(&closure.exec_node);
|
||||||
result = self.eval(&closure.function_node);
|
|
||||||
|
|
||||||
self.frames.pop();
|
self.frames.pop();
|
||||||
} else {
|
} else { return Err(format!("Tail call target is not a closure: {}", next_obj.type_name())); }
|
||||||
return Err(format!("Tail call target is not a closure: {}", next_obj.type_name()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => return result,
|
_ => return result,
|
||||||
}
|
}
|
||||||
@@ -370,115 +243,61 @@ impl VM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_with_args(&mut self, closure: &Closure, args: Vec<Value>) -> Result<Value, String> {
|
pub fn run_with_args(&mut self, closure: &Closure, args: Vec<Value>) -> Result<Value, String> {
|
||||||
self.stack.clear();
|
self.stack.clear(); self.frames.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); }
|
||||||
let closure_rc = Rc::new(closure.clone());
|
else { self.unpack(&closure.parameter_node, &args, &mut 0)?; }
|
||||||
self.frames.push(CallFrame {
|
self.eval(&closure.exec_node)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_with_args_observed<O: VMObserver>(&mut self, observer: &mut O, closure: &Closure, args: Vec<Value>) -> Result<Value, String> {
|
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.stack.clear(); self.frames.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); }
|
||||||
let closure_rc = Rc::new(closure.clone());
|
else { self.unpack(&closure.parameter_node, &args, &mut 0)?; }
|
||||||
self.frames.push(CallFrame {
|
self.eval_observed(observer, &closure.exec_node)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_with_observer<O: VMObserver>(&mut self, observer: &mut O, root: &TypedNode) -> Result<Value, String> {
|
pub fn run_with_observer<O: VMObserver>(&mut self, observer: &mut O, root: &ExecNode) -> Result<Value, String> {
|
||||||
self.stack.clear();
|
self.stack.clear(); self.frames.clear();
|
||||||
self.frames.clear();
|
self.frames.push(CallFrame { stack_base: 0, closure: None });
|
||||||
|
|
||||||
self.frames.push(CallFrame {
|
|
||||||
stack_base: 0,
|
|
||||||
closure: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = self.eval_observed(observer, root);
|
let result = self.eval_observed(observer, root);
|
||||||
|
|
||||||
self.frames.pop();
|
self.frames.pop();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pure, original, zero-cost hot path.
|
#[inline(always)] fn eval(&mut self, node: &ExecNode) -> Result<Value, String> { dispatch_eval!(self, node, eval) }
|
||||||
#[inline(always)]
|
|
||||||
fn eval(&mut self, node: &TypedNode) -> Result<Value, String> {
|
|
||||||
dispatch_eval!(self, node, eval)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The observed path for debugging.
|
fn eval_observed<O: VMObserver>(&mut self, observer: &mut O, node: &ExecNode) -> Result<Value, String> {
|
||||||
fn eval_observed<O: VMObserver>(&mut self, observer: &mut O, node: &TypedNode) -> Result<Value, String> {
|
|
||||||
observer.before_eval(self, node);
|
observer.before_eval(self, node);
|
||||||
|
let wrapper = |vm: &mut Self, obs: &mut O| -> Result<Value, String> { dispatch_eval!(vm, node, eval_observed, obs) };
|
||||||
// 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 result = wrapper(self, observer);
|
let result = wrapper(self, observer);
|
||||||
observer.after_eval(self, node, &result);
|
observer.after_eval(self, node, &result);
|
||||||
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) => {
|
||||||
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 + (idx as usize);
|
let abs_index = frame.stack_base + (idx as usize);
|
||||||
if abs_index < self.stack.len() {
|
if abs_index < self.stack.len() {
|
||||||
// Check if already boxed
|
if let Value::Cell(cell) = &self.stack[abs_index] { Ok(cell.clone()) }
|
||||||
if let Value::Cell(cell) = &self.stack[abs_index] {
|
else {
|
||||||
Ok(cell.clone())
|
|
||||||
} else {
|
|
||||||
// Box it
|
|
||||||
let val = self.stack[abs_index].clone();
|
let val = self.stack[abs_index].clone();
|
||||||
let cell = Rc::new(RefCell::new(val));
|
let cell = Rc::new(RefCell::new(val));
|
||||||
self.stack[abs_index] = Value::Cell(cell.clone());
|
self.stack[abs_index] = Value::Cell(cell.clone());
|
||||||
Ok(cell)
|
Ok(cell)
|
||||||
}
|
}
|
||||||
} else {
|
} else { Err(format!("Stack underflow capture local {}", idx)) }
|
||||||
Err(format!("Stack underflow capture local {}", idx))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Address::Upvalue(idx) => {
|
Address::Upvalue(idx) => {
|
||||||
let frame = self.frames.last().ok_or("No call frame")?;
|
let frame = self.frames.last().ok_or("No call frame")?;
|
||||||
if let Some(closure) = &frame.closure {
|
if let Some(closure) = &frame.closure {
|
||||||
let idx = idx as usize;
|
let idx = idx as usize;
|
||||||
if idx < closure.upvalues.len() {
|
if idx < closure.upvalues.len() { Ok(closure.upvalues[idx].clone()) }
|
||||||
Ok(closure.upvalues[idx].clone())
|
else { Err(format!("Upvalue access out of bounds capture {}", idx)) }
|
||||||
} else {
|
} else { Err("Current frame has no closure".to_string()) }
|
||||||
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()),
|
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 frame = self.frames.last().ok_or("No call frame")?;
|
||||||
let abs_index = frame.stack_base + (idx as usize);
|
let abs_index = frame.stack_base + (idx as usize);
|
||||||
if abs_index < self.stack.len() {
|
if abs_index < self.stack.len() {
|
||||||
match &self.stack[abs_index] {
|
match &self.stack[abs_index] { Value::Cell(cell) => Ok(cell.borrow().clone()), val => Ok(val.clone()) }
|
||||||
Value::Cell(cell) => Ok(cell.borrow().clone()),
|
} else { Err(format!("Stack underflow access local {}", idx)) }
|
||||||
val => Ok(val.clone()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(format!("Stack underflow access local {}", idx))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Address::Global(idx) => {
|
Address::Global(idx) => {
|
||||||
let idx = idx as usize;
|
let idx = idx as usize;
|
||||||
let globals = self.globals.borrow();
|
let globals = self.globals.borrow();
|
||||||
if idx < globals.len() {
|
if idx < globals.len() { Ok(globals[idx].clone()) }
|
||||||
Ok(globals[idx].clone())
|
else { Err(format!("Global access out of bounds {}", idx)) }
|
||||||
} else {
|
|
||||||
Err(format!("Global access out of bounds {}", idx))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Address::Upvalue(idx) => {
|
Address::Upvalue(idx) => {
|
||||||
let frame = self.frames.last().ok_or("No call frame")?;
|
let frame = self.frames.last().ok_or("No call frame")?;
|
||||||
if let Some(closure) = &frame.closure {
|
if let Some(closure) = &frame.closure {
|
||||||
let idx = idx as usize;
|
let idx = idx as usize;
|
||||||
if idx < closure.upvalues.len() {
|
if idx < closure.upvalues.len() { Ok(closure.upvalues[idx].borrow().clone()) }
|
||||||
Ok(closure.upvalues[idx].borrow().clone())
|
else { Err(format!("Upvalue access out of bounds {}", idx)) }
|
||||||
} else {
|
} else { Err("Current frame has no closure (cannot access upvalues)".to_string()) }
|
||||||
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 frame = self.frames.last().ok_or("No call frame")?;
|
||||||
let abs_index = frame.stack_base + (idx as usize);
|
let abs_index = frame.stack_base + (idx as usize);
|
||||||
if abs_index < self.stack.len() {
|
if abs_index < self.stack.len() {
|
||||||
if let Value::Cell(cell) = &self.stack[abs_index] {
|
if let Value::Cell(cell) = &self.stack[abs_index] { *cell.borrow_mut() = value; }
|
||||||
*cell.borrow_mut() = value;
|
else { self.stack[abs_index] = value; }
|
||||||
} else {
|
} else if abs_index == self.stack.len() { self.stack.push(value); }
|
||||||
self.stack[abs_index] = value;
|
else { return Err(format!("Stack gap write local {}", idx)); }
|
||||||
}
|
|
||||||
} else if abs_index == self.stack.len() {
|
|
||||||
self.stack.push(value);
|
|
||||||
} else {
|
|
||||||
return Err(format!("Stack gap write local {}", idx));
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Address::Global(idx) => {
|
Address::Global(idx) => {
|
||||||
let idx = idx as usize;
|
let idx = idx 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);
|
|
||||||
}
|
|
||||||
globals[idx] = value;
|
globals[idx] = value;
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
@@ -554,58 +352,37 @@ impl VM {
|
|||||||
let frame = self.frames.last().ok_or("No call frame")?;
|
let frame = self.frames.last().ok_or("No call frame")?;
|
||||||
if let Some(closure) = &frame.closure {
|
if let Some(closure) = &frame.closure {
|
||||||
let idx = idx as usize;
|
let idx = idx as usize;
|
||||||
if idx < closure.upvalues.len() {
|
if idx < closure.upvalues.len() { *closure.upvalues[idx].borrow_mut() = value; Ok(()) }
|
||||||
*closure.upvalues[idx].borrow_mut() = value;
|
else { Err(format!("Upvalue assignment out of bounds {}", idx)) }
|
||||||
Ok(())
|
} else { Err("Current frame has no closure".to_string()) }
|
||||||
} 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>) {
|
fn flatten_value(val: Value, into: &mut Vec<Value>) {
|
||||||
if let Some(values) = val.as_slice() {
|
if let Some(values) = val.as_slice() { for item in values.iter() { Self::flatten_value(item.clone(), into); } }
|
||||||
for item in values.iter() {
|
else { into.push(val); }
|
||||||
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();
|
let mut arg_vals = Vec::new();
|
||||||
match &args.kind {
|
match &args.kind {
|
||||||
BoundKind::Tuple { elements } => {
|
BoundKind::Tuple { elements } => { self.eval_and_flatten(elements, &mut arg_vals)?; }
|
||||||
self.eval_and_flatten(elements, &mut arg_vals)?;
|
_ => { VM::flatten_value(self.eval(args)?, &mut arg_vals); }
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let v = self.eval(args)?;
|
|
||||||
VM::flatten_value(v, &mut arg_vals);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(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();
|
let mut arg_vals = Vec::new();
|
||||||
match &args.kind {
|
match &args.kind {
|
||||||
BoundKind::Tuple { elements } => {
|
BoundKind::Tuple { elements } => { self.eval_observed_and_flatten(observer, elements, &mut arg_vals)?; }
|
||||||
self.eval_observed_and_flatten(observer, elements, &mut arg_vals)?;
|
_ => { VM::flatten_value(self.eval_observed(observer, args)?, &mut arg_vals); }
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let v = self.eval_observed(observer, args)?;
|
|
||||||
VM::flatten_value(v, &mut arg_vals);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(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 {
|
for e in elements {
|
||||||
match &e.kind {
|
match &e.kind {
|
||||||
BoundKind::Tuple { elements: sub } => self.eval_and_flatten(sub, into)?,
|
BoundKind::Tuple { elements: sub } => self.eval_and_flatten(sub, into)?,
|
||||||
@@ -615,7 +392,7 @@ impl VM {
|
|||||||
Ok(())
|
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 {
|
for e in elements {
|
||||||
match &e.kind {
|
match &e.kind {
|
||||||
BoundKind::Tuple { elements: sub } => self.eval_observed_and_flatten(observer, sub, into)?,
|
BoundKind::Tuple { elements: sub } => self.eval_observed_and_flatten(observer, sub, into)?,
|
||||||
@@ -625,41 +402,26 @@ impl VM {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps values into stack slots based on the parameter pattern.
|
fn unpack<T>(&mut self, pattern: &Node<BoundKind<T>, T>, values: &[Value], offset: &mut usize) -> Result<(), String> {
|
||||||
/// Returns the number of slots filled.
|
|
||||||
fn unpack(&mut self, pattern: &TypedNode, values: &[Value], offset: &mut usize) -> Result<(), String> {
|
|
||||||
match &pattern.kind {
|
match &pattern.kind {
|
||||||
BoundKind::Parameter { slot, .. } => {
|
BoundKind::Parameter { slot, .. } => {
|
||||||
let val = values.get(*offset).cloned().unwrap_or(Value::Void);
|
let val = values.get(*offset).cloned().unwrap_or(Value::Void);
|
||||||
*offset += 1;
|
*offset += 1;
|
||||||
|
|
||||||
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 abs_index == self.stack.len() { self.stack.push(val); }
|
||||||
if abs_index == self.stack.len() {
|
else if abs_index < self.stack.len() { self.stack[abs_index] = val; }
|
||||||
self.stack.push(val);
|
else { return Err(format!("Stack gap during unpack at slot {}", slot)); }
|
||||||
} else if abs_index < self.stack.len() {
|
|
||||||
self.stack[abs_index] = val;
|
|
||||||
} else {
|
|
||||||
return Err(format!("Stack gap during unpack at slot {}", slot));
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
BoundKind::Tuple { elements } => {
|
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()) {
|
if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) {
|
||||||
*offset += 1;
|
*offset += 1;
|
||||||
let mut sub_offset = 0;
|
let mut sub_offset = 0;
|
||||||
for el in elements {
|
for el in elements { self.unpack(el, sub_values, &mut sub_offset)?; }
|
||||||
self.unpack(el, sub_values, &mut sub_offset)?;
|
|
||||||
}
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
for el in elements { self.unpack(el, values, offset)?; }
|
||||||
for el in elements {
|
|
||||||
self.unpack(el, values, offset)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Err("Invalid node in parameter pattern".to_string()),
|
_ => Err("Invalid node in parameter pattern".to_string()),
|
||||||
@@ -671,116 +433,33 @@ impl VM {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ast::nodes::{Node, Symbol};
|
use crate::ast::nodes::{Node, Symbol};
|
||||||
|
use crate::ast::compiler::tco::TCO;
|
||||||
use crate::ast::types::{SourceLocation, NodeIdentity, StaticType};
|
use crate::ast::types::{SourceLocation, NodeIdentity, StaticType};
|
||||||
|
|
||||||
fn make_dummy_identity() -> Rc<NodeIdentity> {
|
fn make_dummy_identity() -> Rc<NodeIdentity> { Rc::new(NodeIdentity { location: SourceLocation { line: 0, col: 0 } }) }
|
||||||
Rc::new(NodeIdentity {
|
|
||||||
location: SourceLocation { line: 0, col: 0 },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_capture_boxing_modification() {
|
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();
|
let id = make_dummy_identity();
|
||||||
|
|
||||||
// 1. Define Lambda Body: x = 20 (Upvalue 0)
|
|
||||||
let lambda_body = Node {
|
let lambda_body = Node {
|
||||||
identity: id.clone(),
|
identity: id.clone(), ty: StaticType::Void,
|
||||||
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)) }) },
|
||||||
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 {
|
let root = Node {
|
||||||
identity: id.clone(),
|
identity: id.clone(), ty: StaticType::Int,
|
||||||
ty: StaticType::Int,
|
|
||||||
kind: BoundKind::Block {
|
kind: BoundKind::Block {
|
||||||
exprs: vec![
|
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)) }) } },
|
||||||
Node {
|
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) } }) } },
|
||||||
identity: id.clone(),
|
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![] } }) } },
|
||||||
ty: StaticType::Int,
|
Node { identity: id.clone(), ty: StaticType::Int, kind: BoundKind::Get { addr: Address::Local(0), name: Symbol::from("x") } },
|
||||||
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") },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let globals = Rc::new(RefCell::new(Vec::new()));
|
let globals = Rc::new(RefCell::new(Vec::new()));
|
||||||
let mut vm = VM::new(globals);
|
let mut vm = VM::new(globals);
|
||||||
|
let exec_root = TCO::optimize(root);
|
||||||
let result = vm.run(&root);
|
let result = vm.run(&exec_root);
|
||||||
|
match result { Ok(Value::Int(val)) => assert_eq!(val, 20), _ => panic!("Expected Int(20)") }
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user