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
+10 -8
View File
@@ -34,7 +34,7 @@ impl Optimizer {
fn visit_node(&self, node: TypedNode) -> TypedNode {
let (new_kind, new_ty) = match node.kind {
BoundKind::Call { callee, args, is_tail } => {
BoundKind::Call { callee, args } => {
let callee = self.visit_node(*callee);
let args = self.visit_node(*args);
@@ -52,6 +52,7 @@ impl Optimizer {
for (i, cell) in closure.upvalues.iter().enumerate() {
sub.add_upvalue(i as u32, cell.borrow().clone());
}
// DIRECT ACCESS: Use the original TypedNode from the closure
let inlined_body = sub.inline((*closure.function_node).clone());
// Try to collapse the now-stateless lambda call
@@ -73,6 +74,7 @@ impl Optimizer {
for (i, cell) in closure.upvalues.iter().enumerate() {
sub.add_upvalue(i as u32, cell.borrow().clone());
}
// DIRECT ACCESS: Use the original TypedNode
let inlined_body = sub.inline((*closure.function_node).clone());
let cracked_lambda = Node {
@@ -87,12 +89,12 @@ impl Optimizer {
};
return Node {
identity: node.identity,
kind: BoundKind::Call { callee: Box::new(cracked_lambda), args: Box::new(args), is_tail },
kind: BoundKind::Call { callee: Box::new(cracked_lambda), args: Box::new(args) },
ty: node.ty,
};
}
(BoundKind::Call { callee: Box::new(callee), args: Box::new(args), is_tail }, node.ty)
(BoundKind::Call { callee: Box::new(callee), args: Box::new(args) }, node.ty)
},
BoundKind::If { cond, then_br, else_br } => {
@@ -269,7 +271,7 @@ impl Optimizer {
self.contains_def_local(cond) || self.contains_def_local(then_br) || else_br.as_ref().is_some_and(|e| self.contains_def_local(e))
}
BoundKind::Block { exprs } => exprs.iter().any(|e| self.contains_def_local(e)),
BoundKind::Call { callee, args, .. } => self.contains_def_local(callee) || self.contains_def_local(args),
BoundKind::Call { callee, args } => self.contains_def_local(callee) || self.contains_def_local(args),
BoundKind::Lambda { .. } => false, // Nested lambda definitions are safe (they have their own frame)
BoundKind::Tuple { elements } => elements.iter().any(|e| self.contains_def_local(e)),
BoundKind::Record { fields } => fields.iter().any(|(k, v)| self.contains_def_local(k) || self.contains_def_local(v)),
@@ -388,10 +390,10 @@ impl SubstitutionMap {
let exprs = exprs.into_iter().map(|e| self.inline_recursive(e, depth, upvalue_subs)).collect();
(BoundKind::Block { exprs }, node.ty)
},
BoundKind::Call { callee, args, is_tail } => {
BoundKind::Call { callee, args } => {
let callee = Box::new(self.inline_recursive(*callee, depth, upvalue_subs));
let args = Box::new(self.inline_recursive(*args, depth, upvalue_subs));
(BoundKind::Call { callee, args, is_tail }, node.ty)
(BoundKind::Call { callee, args }, node.ty)
},
BoundKind::DefLocal { name, slot, value, captured_by } => {
let value = Box::new(self.inline_recursive(*value, depth, upvalue_subs));
@@ -444,10 +446,10 @@ impl SubstitutionMap {
let exprs = exprs.into_iter().map(|e| self.reindex_upvalues(e, mapping)).collect();
(BoundKind::Block { exprs }, node.ty)
},
BoundKind::Call { callee, args, is_tail } => {
BoundKind::Call { callee, args } => {
let callee = Box::new(self.reindex_upvalues(*callee, mapping));
let args = Box::new(self.reindex_upvalues(*args, mapping));
(BoundKind::Call { callee, args, is_tail }, node.ty)
(BoundKind::Call { callee, args }, node.ty)
},
BoundKind::DefLocal { name, slot, value, captured_by } => {
let value = Box::new(self.reindex_upvalues(*value, mapping));