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:
+80
-83
@@ -1,123 +1,120 @@
|
||||
use std::rc::Rc;
|
||||
use crate::ast::nodes::Node;
|
||||
use crate::ast::compiler::bound_nodes::BoundKind;
|
||||
use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode};
|
||||
use crate::ast::types::StaticType;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RuntimeMetadata {
|
||||
pub ty: StaticType,
|
||||
pub is_tail: bool,
|
||||
/// The original, high-level typed node. Perfect for Debuggers and Optimizers.
|
||||
pub original: Rc<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;
|
||||
|
||||
impl TCO {
|
||||
pub fn optimize<T: Clone>(node: Node<BoundKind<T>, T>) -> Node<BoundKind<T>, T> {
|
||||
// Top-level starts NOT in tail position usually, unless the script result is returned immediately
|
||||
// which implies tail position for the script body if viewed as a function.
|
||||
// Let's assume standard execution context (not tail call) for the script root.
|
||||
Self::transform(node, false)
|
||||
/// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions.
|
||||
pub fn optimize(node: TypedNode) -> ExecNode {
|
||||
Self::transform(Rc::new(node), true)
|
||||
}
|
||||
|
||||
fn transform<T: Clone>(node: Node<BoundKind<T>, T>, is_tail_position: bool) -> Node<BoundKind<T>, T> {
|
||||
match node.kind {
|
||||
BoundKind::Call { callee, args, .. } => {
|
||||
let new_callee = Box::new(Self::transform(*callee, false));
|
||||
let new_args = Box::new(Self::transform(*args, false));
|
||||
|
||||
Node {
|
||||
kind: BoundKind::Call {
|
||||
callee: new_callee,
|
||||
args: new_args,
|
||||
is_tail: is_tail_position,
|
||||
},
|
||||
..node
|
||||
fn transform(node_rc: Rc<TypedNode>, is_tail_position: bool) -> ExecNode {
|
||||
let node = &*node_rc;
|
||||
let new_kind = match &node.kind {
|
||||
BoundKind::Call { callee, args } => {
|
||||
BoundKind::Call {
|
||||
callee: Box::new(Self::transform(Rc::new((**callee).clone()), false)),
|
||||
args: Box::new(Self::transform(Rc::new((**args).clone()), false)),
|
||||
}
|
||||
},
|
||||
|
||||
BoundKind::If { cond, then_br, else_br } => {
|
||||
let new_cond = Box::new(Self::transform(*cond, false));
|
||||
let new_then = Box::new(Self::transform(*then_br, is_tail_position));
|
||||
let new_else = else_br.map(|e| Box::new(Self::transform(*e, is_tail_position)));
|
||||
|
||||
Node {
|
||||
kind: BoundKind::If {
|
||||
cond: new_cond,
|
||||
then_br: new_then,
|
||||
else_br: new_else,
|
||||
},
|
||||
..node
|
||||
BoundKind::If {
|
||||
cond: Box::new(Self::transform(Rc::new((**cond).clone()), false)),
|
||||
then_br: Box::new(Self::transform(Rc::new((**then_br).clone()), is_tail_position)),
|
||||
else_br: else_br.as_ref().map(|e| Box::new(Self::transform(Rc::new((**e).clone()), is_tail_position))),
|
||||
}
|
||||
},
|
||||
|
||||
BoundKind::Block { exprs } => {
|
||||
if exprs.is_empty() {
|
||||
return Node {
|
||||
kind: BoundKind::Block { exprs },
|
||||
..node
|
||||
};
|
||||
}
|
||||
|
||||
let last_idx = exprs.len() - 1;
|
||||
let mut new_exprs = Vec::with_capacity(exprs.len());
|
||||
|
||||
for (i, expr) in exprs.into_iter().enumerate() {
|
||||
let is_last = i == last_idx;
|
||||
// Only the last expression inherits the tail context
|
||||
new_exprs.push(Self::transform(expr, is_tail_position && is_last));
|
||||
}
|
||||
|
||||
Node {
|
||||
kind: BoundKind::Block { exprs: new_exprs },
|
||||
..node
|
||||
BoundKind::Block { exprs: vec![] }
|
||||
} else {
|
||||
let last_idx = exprs.len() - 1;
|
||||
let mut new_exprs = Vec::with_capacity(exprs.len());
|
||||
|
||||
for (i, expr) in exprs.iter().enumerate() {
|
||||
let is_last = i == last_idx;
|
||||
new_exprs.push(Self::transform(Rc::new(expr.clone()), is_tail_position && is_last));
|
||||
}
|
||||
BoundKind::Block { exprs: new_exprs }
|
||||
}
|
||||
},
|
||||
|
||||
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
||||
// The body of a lambda is implicitly in tail position when the lambda is called.
|
||||
let new_body = Rc::new(Self::transform((*body).clone(), true));
|
||||
|
||||
Node {
|
||||
kind: BoundKind::Lambda {
|
||||
params: params.clone(),
|
||||
upvalues,
|
||||
body: new_body,
|
||||
positional_count,
|
||||
},
|
||||
..node
|
||||
BoundKind::Lambda {
|
||||
params: Rc::new(Self::transform(params.clone(), false)),
|
||||
upvalues: upvalues.clone(),
|
||||
body: Rc::new(Self::transform(body.clone(), true)),
|
||||
positional_count: *positional_count,
|
||||
}
|
||||
},
|
||||
|
||||
BoundKind::Set { addr, value } => {
|
||||
Node {
|
||||
kind: BoundKind::Set { addr, value: Box::new(Self::transform(*value, false)) },
|
||||
..node
|
||||
}
|
||||
BoundKind::Set { addr: *addr, value: Box::new(Self::transform(Rc::new((**value).clone()), false)) }
|
||||
},
|
||||
BoundKind::DefLocal { name, slot, value, captured_by } => {
|
||||
Node {
|
||||
kind: BoundKind::DefLocal { name, slot, value: Box::new(Self::transform(*value, false)), captured_by },
|
||||
..node
|
||||
BoundKind::DefLocal {
|
||||
name: name.clone(),
|
||||
slot: *slot,
|
||||
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
|
||||
captured_by: captured_by.clone()
|
||||
}
|
||||
},
|
||||
BoundKind::DefGlobal { name, global_index, value } => {
|
||||
Node {
|
||||
kind: BoundKind::DefGlobal { name, global_index, value: Box::new(Self::transform(*value, false)) },
|
||||
..node
|
||||
BoundKind::DefGlobal {
|
||||
name: name.clone(),
|
||||
global_index: *global_index,
|
||||
value: Box::new(Self::transform(Rc::new((**value).clone()), false))
|
||||
}
|
||||
},
|
||||
BoundKind::Record { fields } => {
|
||||
let new_fields = fields.into_iter().map(|(k, v)| {
|
||||
(Self::transform(k, false), Self::transform(v, false))
|
||||
let new_fields = fields.iter().map(|(k, v)| {
|
||||
(Self::transform(Rc::new(k.clone()), false), Self::transform(Rc::new(v.clone()), false))
|
||||
}).collect();
|
||||
Node {
|
||||
kind: BoundKind::Record { fields: new_fields },
|
||||
..node
|
||||
}
|
||||
BoundKind::Record { fields: new_fields }
|
||||
},
|
||||
BoundKind::Tuple { elements } => {
|
||||
let new_elements = elements.into_iter().map(|e| Self::transform(e, false)).collect();
|
||||
Node {
|
||||
kind: BoundKind::Tuple { elements: new_elements },
|
||||
..node
|
||||
}
|
||||
let new_elements = elements.iter().map(|e| Self::transform(Rc::new(e.clone()), false)).collect();
|
||||
BoundKind::Tuple { elements: new_elements }
|
||||
},
|
||||
BoundKind::Parameter { name, slot } => BoundKind::Parameter { name: name.clone(), slot: *slot },
|
||||
BoundKind::Constant(v) => BoundKind::Constant(v.clone()),
|
||||
BoundKind::Get { addr, name } => BoundKind::Get { addr: *addr, name: name.clone() },
|
||||
BoundKind::Nop => BoundKind::Nop,
|
||||
BoundKind::Expansion { original_call, bound_expanded } => {
|
||||
BoundKind::Expansion {
|
||||
original_call: original_call.clone(),
|
||||
bound_expanded: Box::new(Self::transform(Rc::new((**bound_expanded).clone()), is_tail_position))
|
||||
}
|
||||
}
|
||||
BoundKind::Extension(_) => {
|
||||
BoundKind::Nop
|
||||
}
|
||||
};
|
||||
|
||||
Node {
|
||||
identity: node.identity.clone(),
|
||||
kind: new_kind,
|
||||
ty: RuntimeMetadata {
|
||||
ty: node.ty.clone(),
|
||||
is_tail: is_tail_position,
|
||||
original: node_rc,
|
||||
},
|
||||
|
||||
// Atoms and variables don't change
|
||||
_ => node,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user