Refactor Call to include is_tail flag
The `TailCall` variant has been merged into `Call` by adding an `is_tail` boolean field. This simplifies the AST by reducing the number of node variants and makes it easier to handle tail call optimization. The `tco.rs` module is responsible for setting this flag when a call occurs in tail position.
This commit is contained in:
@@ -233,7 +233,8 @@ 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,11 +83,7 @@ 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,
|
||||||
|
|
||||||
TailCall {
|
|
||||||
callee: Box<BoundNode<T>>,
|
|
||||||
args: Box<BoundNode<T>>,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Block {
|
Block {
|
||||||
@@ -145,11 +141,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 }, BoundKind::Call { callee: cb, args: ab }) => {
|
(BoundKind::Call { callee: ca, args: aa, is_tail: ta }, BoundKind::Call { callee: cb, args: ab, is_tail: tb }) => {
|
||||||
ca == cb && aa == ab
|
ca == cb && aa == ab && ta == tb
|
||||||
}
|
|
||||||
(BoundKind::TailCall { callee: ca, args: aa }, BoundKind::TailCall { callee: cb, args: ab }) => {
|
|
||||||
ca == cb && aa == ab
|
|
||||||
}
|
}
|
||||||
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => {
|
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => {
|
||||||
ea == eb
|
ea == eb
|
||||||
@@ -191,8 +184,7 @@ impl<T> BoundKind<T> {
|
|||||||
};
|
};
|
||||||
format!("LAMBDA({}, Captures:{})", p_str, upvalues.len())
|
format!("LAMBDA({}, Captures:{})", p_str, upvalues.len())
|
||||||
},
|
},
|
||||||
BoundKind::Call { .. } => "CALL".to_string(),
|
BoundKind::Call { is_tail, .. } => if *is_tail { "T-CALL".to_string() } else { "CALL".to_string() },
|
||||||
BoundKind::TailCall { .. } => "T-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,23 +137,9 @@ 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 } => {
|
BoundKind::Call { callee, args, is_tail } => {
|
||||||
self.log("Call", node);
|
let label = if *is_tail { "Call (TCO)" } else { "Call" };
|
||||||
self.indent += 1;
|
self.log(label, node);
|
||||||
|
|
||||||
self.write_indent();
|
|
||||||
self.output.push_str("Callee:\n");
|
|
||||||
self.visit(callee);
|
|
||||||
|
|
||||||
self.write_indent();
|
|
||||||
self.output.push_str("Arguments:\n");
|
|
||||||
self.visit(args);
|
|
||||||
|
|
||||||
self.indent -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
BoundKind::TailCall { callee, args } => {
|
|
||||||
self.log("TailCall (TCO)", 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::TailCall { 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 } | BoundKind::TailCall { callee, args } => {
|
BoundKind::Call { callee, args, is_tail } => {
|
||||||
let callee = self.visit_node(*callee);
|
let callee = self.visit_node(*callee);
|
||||||
let args = self.visit_node(*args);
|
let args = self.visit_node(*args);
|
||||||
|
|
||||||
@@ -87,12 +87,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) },
|
kind: BoundKind::Call { callee: Box::new(cracked_lambda), args: Box::new(args), is_tail },
|
||||||
ty: node.ty,
|
ty: node.ty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
(BoundKind::Call { callee: Box::new(callee), args: Box::new(args) }, node.ty)
|
(BoundKind::Call { callee: Box::new(callee), args: Box::new(args), is_tail }, node.ty)
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::If { cond, then_br, else_br } => {
|
BoundKind::If { cond, then_br, else_br } => {
|
||||||
@@ -269,7 +269,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 } | BoundKind::TailCall { 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 +388,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 } | BoundKind::TailCall { callee, args } => {
|
BoundKind::Call { callee, args, is_tail } => {
|
||||||
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 }, node.ty)
|
(BoundKind::Call { callee, args, is_tail }, 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 +444,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 } | BoundKind::TailCall { callee, args } => {
|
BoundKind::Call { callee, args, is_tail } => {
|
||||||
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 }, node.ty)
|
(BoundKind::Call { callee, args, is_tail }, 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,14 +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 } => {
|
BoundKind::Call { callee, args, is_tail } => {
|
||||||
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) }, ret_ty)
|
(BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args), is_tail }, ret_ty)
|
||||||
},
|
|
||||||
|
|
||||||
BoundKind::TailCall { callee, args } => {
|
|
||||||
let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, *args, node.ty.clone());
|
|
||||||
(BoundKind::TailCall { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Recursive traversal for other nodes
|
// Recursive traversal for other nodes
|
||||||
|
|||||||
+8
-17
@@ -14,26 +14,17 @@ impl TCO {
|
|||||||
|
|
||||||
fn transform<T: Clone>(node: Node<BoundKind<T>, T>, is_tail_position: bool) -> Node<BoundKind<T>, T> {
|
fn transform<T: Clone>(node: Node<BoundKind<T>, T>, is_tail_position: bool) -> Node<BoundKind<T>, T> {
|
||||||
match node.kind {
|
match node.kind {
|
||||||
BoundKind::Call { callee, args } => {
|
BoundKind::Call { callee, args, .. } => {
|
||||||
let new_callee = Box::new(Self::transform(*callee, false));
|
let new_callee = Box::new(Self::transform(*callee, false));
|
||||||
let new_args = Box::new(Self::transform(*args, false));
|
let new_args = Box::new(Self::transform(*args, false));
|
||||||
|
|
||||||
if is_tail_position {
|
Node {
|
||||||
Node {
|
kind: BoundKind::Call {
|
||||||
kind: BoundKind::TailCall {
|
callee: new_callee,
|
||||||
callee: new_callee,
|
args: new_args,
|
||||||
args: new_args,
|
is_tail: is_tail_position,
|
||||||
},
|
},
|
||||||
..node
|
..node
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Node {
|
|
||||||
kind: BoundKind::Call {
|
|
||||||
callee: new_callee,
|
|
||||||
args: new_args,
|
|
||||||
},
|
|
||||||
..node
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ impl TypeChecker {
|
|||||||
}, fn_ty)
|
}, fn_ty)
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Call { callee, args } => {
|
BoundKind::Call { callee, args, is_tail } => {
|
||||||
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,7 +271,8 @@ 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)
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -335,8 +336,6 @@ impl TypeChecker {
|
|||||||
BoundKind::Extension(_ext) => {
|
BoundKind::Extension(_ext) => {
|
||||||
return Err(format!("TypeChecking for extension '{}' not implemented", _ext.display_name()));
|
return Err(format!("TypeChecking for extension '{}' not implemented", _ext.display_name()));
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::TailCall { .. } => unreachable!("TailCall generated before TypeChecker"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Node {
|
Ok(Node {
|
||||||
|
|||||||
+10
-42
@@ -188,48 +188,7 @@ macro_rules! dispatch_eval {
|
|||||||
Ok(Value::Object(Rc::new(closure)))
|
Ok(Value::Object(Rc::new(closure)))
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::TailCall { callee, args } => {
|
BoundKind::Call { callee, args, is_tail } => {
|
||||||
let func_val = $self.$eval_method($($observer,)? callee)?;
|
|
||||||
|
|
||||||
let arg_vals = match &args.kind {
|
|
||||||
BoundKind::Tuple { elements } => {
|
|
||||||
// FAST-PATH: If it's a flat tuple, evaluate directly into Vec
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match func_val {
|
|
||||||
Value::Object(obj) => Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))),
|
|
||||||
Value::Function(f) => Ok(f(arg_vals)),
|
|
||||||
_ => Err(format!("Tail call target is not a function: {}", func_val)),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
BoundKind::Call { callee, args } => {
|
|
||||||
let mut func_val = $self.$eval_method($($observer,)? callee)?;
|
let mut func_val = $self.$eval_method($($observer,)? callee)?;
|
||||||
|
|
||||||
let mut arg_vals = match &args.kind {
|
let mut arg_vals = match &args.kind {
|
||||||
@@ -262,6 +221,14 @@ macro_rules! dispatch_eval {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if *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)),
|
||||||
|
_ => return Err(format!("Tail call target is not a function: {}", func_val)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match func_val {
|
match func_val {
|
||||||
Value::Function(f) => break Ok(f(arg_vals)),
|
Value::Function(f) => break Ok(f(arg_vals)),
|
||||||
@@ -792,6 +759,7 @@ mod tests {
|
|||||||
ty: StaticType::Tuple(vec![]),
|
ty: StaticType::Tuple(vec![]),
|
||||||
kind: BoundKind::Tuple { elements: vec![] },
|
kind: BoundKind::Tuple { elements: vec![] },
|
||||||
}),
|
}),
|
||||||
|
is_tail: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// return x (Local 0)
|
// return x (Local 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user