Refactor: Rename TCO module to lowering

The TCO (Tail Call Optimization) module has been renamed to `lowering`.
This change better reflects the module's broader responsibility, which
includes not only TCO but also general AST transformations and
preparation for VM execution.

The `optimize` function has been renamed to `lower` to align with the
module's new name.
This commit is contained in:
Michael Schimmel
2026-03-11 10:49:24 +01:00
parent db26719cad
commit bb77caf1af
4 changed files with 298 additions and 299 deletions
@@ -1,288 +1,288 @@
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind}; use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind};
use crate::ast::nodes::Node; use crate::ast::nodes::Node;
use crate::ast::types::StaticType; use crate::ast::types::StaticType;
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc; use std::rc::Rc;
#[derive(Clone)] #[derive(Clone)]
pub struct RuntimeMetadata { pub struct RuntimeMetadata {
pub ty: StaticType, pub ty: StaticType,
pub is_tail: bool, pub is_tail: bool,
/// The analyzed node, containing metrics and a link to the original TypedNode. /// The analyzed node, containing metrics and a link to the original TypedNode.
pub original: Rc<AnalyzedNode>, pub original: Rc<AnalyzedNode>,
pub stack_size: u32, pub stack_size: u32,
} }
impl Debug for RuntimeMetadata { impl Debug for RuntimeMetadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Metadata") f.debug_struct("Metadata")
.field("ty", &self.ty) .field("ty", &self.ty)
.field("is_tail", &self.is_tail) .field("is_tail", &self.is_tail)
.field("stack_size", &self.stack_size) .field("stack_size", &self.stack_size)
.finish() .finish()
} }
} }
/// The ExecNode is the AST used by the VM. It carries TCO flags and links to metrics. /// The ExecNode is the AST used by the VM. It carries TCO flags and links to metrics.
pub type ExecNode = Node<BoundKind<RuntimeMetadata>, RuntimeMetadata>; pub type ExecNode = Node<BoundKind<RuntimeMetadata>, RuntimeMetadata>;
fn calc_stack_size<T>(root_node: &Node<BoundKind<T>, T>) -> u32 { fn calc_stack_size<T>(root_node: &Node<BoundKind<T>, T>) -> u32 {
let mut max_slot = -1i32; let mut max_slot = -1i32;
fn visit<T>(node: &Node<BoundKind<T>, T>, max_slot: &mut i32) { fn visit<T>(node: &Node<BoundKind<T>, T>, max_slot: &mut i32) {
match &node.kind { match &node.kind {
BoundKind::Get { BoundKind::Get {
addr: Address::Local(slot), addr: Address::Local(slot),
.. ..
} }
| BoundKind::Set { | BoundKind::Set {
addr: Address::Local(slot), addr: Address::Local(slot),
.. ..
} }
| BoundKind::Define { | BoundKind::Define {
addr: Address::Local(slot), addr: Address::Local(slot),
.. ..
} => { } => {
if slot.0 as i32 > *max_slot { if slot.0 as i32 > *max_slot {
*max_slot = slot.0 as i32; *max_slot = slot.0 as i32;
} }
} }
BoundKind::Lambda { .. } => { BoundKind::Lambda { .. } => {
// Do NOT recurse into nested lambdas to prevent over-allocating outer stacks // Do NOT recurse into nested lambdas to prevent over-allocating outer stacks
} }
_ => {} _ => {}
} }
// Generic traversal // Generic traversal
match &node.kind { match &node.kind {
BoundKind::If { BoundKind::If {
cond, cond,
then_br, then_br,
else_br, else_br,
} => { } => {
visit(cond, max_slot); visit(cond, max_slot);
visit(then_br, max_slot); visit(then_br, max_slot);
if let Some(e) = else_br { if let Some(e) = else_br {
visit(e, max_slot); visit(e, max_slot);
} }
} }
BoundKind::Set { value, .. } | BoundKind::Define { value, .. } => { BoundKind::Set { value, .. } | BoundKind::Define { value, .. } => {
visit(value, max_slot); visit(value, max_slot);
} }
BoundKind::Destructure { pattern, value } => { BoundKind::Destructure { pattern, value } => {
visit(pattern, max_slot); visit(pattern, max_slot);
visit(value, max_slot); visit(value, max_slot);
} }
BoundKind::Pipe { inputs, lambda, .. } => { BoundKind::Pipe { inputs, lambda, .. } => {
for i in inputs { for i in inputs {
visit(i, max_slot); visit(i, max_slot);
} }
visit(lambda, max_slot); visit(lambda, max_slot);
} }
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
visit(callee, max_slot); visit(callee, max_slot);
visit(args, max_slot); visit(args, max_slot);
} }
BoundKind::Again { args } => visit(args, max_slot), BoundKind::Again { args } => visit(args, max_slot),
BoundKind::Block { exprs } => { BoundKind::Block { exprs } => {
for e in exprs { for e in exprs {
visit(e, max_slot); visit(e, max_slot);
} }
} }
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
for e in elements { for e in elements {
visit(e, max_slot); visit(e, max_slot);
} }
} }
BoundKind::Record { values, .. } => { BoundKind::Record { values, .. } => {
for v in values { for v in values {
visit(v, max_slot); visit(v, max_slot);
} }
} }
BoundKind::Expansion { bound_expanded, .. } => visit(bound_expanded, max_slot), BoundKind::Expansion { bound_expanded, .. } => visit(bound_expanded, max_slot),
_ => {} _ => {}
} }
} }
// Special case for root: if the node itself is a lambda, we DO want to visit its params and body, // Special case for root: if the node itself is a lambda, we DO want to visit its params and body,
// but not any deeply nested lambdas. // but not any deeply nested lambdas.
if let BoundKind::Lambda { params, body, .. } = &root_node.kind { if let BoundKind::Lambda { params, body, .. } = &root_node.kind {
visit(params, &mut max_slot); visit(params, &mut max_slot);
visit(body, &mut max_slot); visit(body, &mut max_slot);
} else { } else {
visit(root_node, &mut max_slot); visit(root_node, &mut max_slot);
} }
(max_slot + 1) as u32 (max_slot + 1) as u32
} }
pub struct TCO; pub struct Lowering;
impl TCO { impl Lowering {
/// Lowers an AnalyzedNode to an ExecNode and marks tail positions. /// Lowers an AnalyzedNode to an ExecNode, marking tail positions and calculating stack sizes.
pub fn optimize(node: AnalyzedNode) -> ExecNode { pub fn lower(node: AnalyzedNode) -> ExecNode {
let root_stack_size = calc_stack_size(&node); let root_stack_size = calc_stack_size(&node);
let mut exec_node = Self::transform(Rc::new(node), true); let mut exec_node = Self::transform(Rc::new(node), true);
exec_node.ty.stack_size = root_stack_size; exec_node.ty.stack_size = root_stack_size;
exec_node exec_node
} }
fn transform(node_rc: Rc<AnalyzedNode>, is_tail_position: bool) -> ExecNode { fn transform(node_rc: Rc<AnalyzedNode>, is_tail_position: bool) -> ExecNode {
let node = &*node_rc; let node = &*node_rc;
let new_kind = match &node.kind { let new_kind = match &node.kind {
BoundKind::Call { callee, args } => BoundKind::Call { BoundKind::Call { callee, args } => BoundKind::Call {
callee: Rc::new(Self::transform(callee.clone(), false)), callee: Rc::new(Self::transform(callee.clone(), false)),
args: Rc::new(Self::transform(args.clone(), false)), args: Rc::new(Self::transform(args.clone(), false)),
}, },
BoundKind::Again { args } => { BoundKind::Again { args } => {
if !is_tail_position { if !is_tail_position {
panic!("'again' is only allowed in tail position to avoid dead code."); panic!("'again' is only allowed in tail position to avoid dead code.");
} }
BoundKind::Again { BoundKind::Again {
args: Rc::new(Self::transform(args.clone(), false)), args: Rc::new(Self::transform(args.clone(), false)),
} }
} }
BoundKind::Pipe { BoundKind::Pipe {
inputs, inputs,
lambda, lambda,
out_type, out_type,
} => { } => {
let mut t_inputs = Vec::with_capacity(inputs.len()); let mut t_inputs = Vec::with_capacity(inputs.len());
for input in inputs { for input in inputs {
t_inputs.push(Rc::new(Self::transform(input.clone(), false))); t_inputs.push(Rc::new(Self::transform(input.clone(), false)));
} }
BoundKind::Pipe { BoundKind::Pipe {
inputs: t_inputs, inputs: t_inputs,
lambda: Rc::new(Self::transform(lambda.clone(), false)), lambda: Rc::new(Self::transform(lambda.clone(), false)),
out_type: out_type.clone(), out_type: out_type.clone(),
} }
} }
BoundKind::If { BoundKind::If {
cond, cond,
then_br, then_br,
else_br, else_br,
} => BoundKind::If { } => BoundKind::If {
cond: Rc::new(Self::transform(cond.clone(), false)), cond: Rc::new(Self::transform(cond.clone(), false)),
then_br: Rc::new(Self::transform( then_br: Rc::new(Self::transform(
then_br.clone(), then_br.clone(),
is_tail_position, is_tail_position,
)), )),
else_br: else_br else_br: else_br
.as_ref() .as_ref()
.map(|e| Rc::new(Self::transform(e.clone(), is_tail_position))), .map(|e| Rc::new(Self::transform(e.clone(), is_tail_position))),
}, },
BoundKind::Block { exprs } => { BoundKind::Block { exprs } => {
if exprs.is_empty() { if exprs.is_empty() {
BoundKind::Block { exprs: vec![] } BoundKind::Block { exprs: vec![] }
} else { } else {
let last_idx = exprs.len() - 1; let last_idx = exprs.len() - 1;
let mut new_exprs = Vec::with_capacity(exprs.len()); let mut new_exprs = Vec::with_capacity(exprs.len());
for (i, expr) in exprs.iter().enumerate() { for (i, expr) in exprs.iter().enumerate() {
let is_last = i == last_idx; let is_last = i == last_idx;
new_exprs.push(Rc::new(Self::transform( new_exprs.push(Rc::new(Self::transform(
expr.clone(), expr.clone(),
is_tail_position && is_last, is_tail_position && is_last,
))); )));
} }
BoundKind::Block { exprs: new_exprs } BoundKind::Block { exprs: new_exprs }
} }
} }
BoundKind::Lambda { BoundKind::Lambda {
params, params,
upvalues, upvalues,
body, body,
positional_count, positional_count,
} => BoundKind::Lambda { } => BoundKind::Lambda {
params: Rc::new(Self::transform(params.clone(), false)), params: Rc::new(Self::transform(params.clone(), false)),
upvalues: upvalues.clone(), upvalues: upvalues.clone(),
body: Rc::new(Self::transform(body.clone(), true)), body: Rc::new(Self::transform(body.clone(), true)),
positional_count: *positional_count, positional_count: *positional_count,
}, },
BoundKind::Set { addr, value } => BoundKind::Set { BoundKind::Set { addr, value } => BoundKind::Set {
addr: *addr, addr: *addr,
value: Rc::new(Self::transform(value.clone(), false)), value: Rc::new(Self::transform(value.clone(), false)),
}, },
BoundKind::Define { BoundKind::Define {
name, name,
addr, addr,
kind, kind,
value, value,
captured_by, captured_by,
} => BoundKind::Define { } => BoundKind::Define {
name: name.clone(), name: name.clone(),
addr: *addr, addr: *addr,
kind: *kind, kind: *kind,
value: Rc::new(Self::transform(value.clone(), false)), value: Rc::new(Self::transform(value.clone(), false)),
captured_by: captured_by.clone(), captured_by: captured_by.clone(),
}, },
BoundKind::Destructure { pattern, value } => BoundKind::Destructure { BoundKind::Destructure { pattern, value } => BoundKind::Destructure {
pattern: Rc::new(Self::transform(pattern.clone(), false)), pattern: Rc::new(Self::transform(pattern.clone(), false)),
value: Rc::new(Self::transform(value.clone(), false)), value: Rc::new(Self::transform(value.clone(), false)),
}, },
BoundKind::Record { layout, values } => { BoundKind::Record { layout, values } => {
let new_values = values let new_values = values
.iter() .iter()
.map(|v| Rc::new(Self::transform(v.clone(), false))) .map(|v| Rc::new(Self::transform(v.clone(), false)))
.collect(); .collect();
BoundKind::Record { BoundKind::Record {
layout: layout.clone(), layout: layout.clone(),
values: new_values, values: new_values,
} }
} }
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
let new_elements = elements let new_elements = elements
.iter() .iter()
.map(|e| Rc::new(Self::transform(e.clone(), false))) .map(|e| Rc::new(Self::transform(e.clone(), false)))
.collect(); .collect();
BoundKind::Tuple { BoundKind::Tuple {
elements: new_elements, elements: new_elements,
} }
} }
BoundKind::Constant(v) => BoundKind::Constant(v.clone()), BoundKind::Constant(v) => BoundKind::Constant(v.clone()),
BoundKind::Get { addr, name } => BoundKind::Get { BoundKind::Get { addr, name } => BoundKind::Get {
addr: *addr, addr: *addr,
name: name.clone(), name: name.clone(),
}, },
BoundKind::FieldAccessor(k) => BoundKind::FieldAccessor(*k), BoundKind::FieldAccessor(k) => BoundKind::FieldAccessor(*k),
BoundKind::GetField { rec, field } => BoundKind::GetField { BoundKind::GetField { rec, field } => BoundKind::GetField {
rec: Rc::new(Self::transform(rec.clone(), false)), rec: Rc::new(Self::transform(rec.clone(), false)),
field: *field, field: *field,
}, },
BoundKind::Nop => BoundKind::Nop, BoundKind::Nop => BoundKind::Nop,
BoundKind::Expansion { BoundKind::Expansion {
original_call, original_call,
bound_expanded, bound_expanded,
} => BoundKind::Expansion { } => BoundKind::Expansion {
original_call: original_call.clone(), original_call: original_call.clone(),
bound_expanded: Rc::new(Self::transform( bound_expanded: Rc::new(Self::transform(
bound_expanded.clone(), bound_expanded.clone(),
is_tail_position, is_tail_position,
)), )),
}, },
BoundKind::Extension(_) => BoundKind::Nop, BoundKind::Extension(_) => BoundKind::Nop,
BoundKind::Error => BoundKind::Error, BoundKind::Error => BoundKind::Error,
}; };
let stack_size = match &new_kind { let stack_size = match &new_kind {
BoundKind::Lambda { .. } => calc_stack_size(node), BoundKind::Lambda { .. } => calc_stack_size(node),
_ => 0, _ => 0,
}; };
Node { Node {
identity: node.identity.clone(), identity: node.identity.clone(),
kind: new_kind, kind: new_kind,
ty: RuntimeMetadata { ty: RuntimeMetadata {
ty: node.ty.original.ty.clone(), ty: node.ty.original.ty.clone(),
is_tail: is_tail_position, is_tail: is_tail_position,
original: node_rc, original: node_rc,
stack_size, stack_size,
}, },
} }
} }
} }
+2 -2
View File
@@ -7,7 +7,7 @@ pub mod lambda_collector;
pub mod macros; pub mod macros;
pub mod optimizer; pub mod optimizer;
pub mod specializer; pub mod specializer;
pub mod tco; pub mod lowering;
pub mod type_checker; pub mod type_checker;
pub use binder::*; pub use binder::*;
@@ -17,5 +17,5 @@ pub use dumper::*;
pub use macros::*; pub use macros::*;
pub use optimizer::*; pub use optimizer::*;
pub use specializer::*; pub use specializer::*;
pub use tco::*; pub use lowering::*;
pub use type_checker::*; pub use type_checker::*;
+7 -8
View File
@@ -15,10 +15,10 @@ use crate::ast::compiler::bound_nodes::{
}; };
use crate::ast::compiler::dumper::Dumper; use crate::ast::compiler::dumper::Dumper;
use crate::ast::compiler::lambda_collector::LambdaCollector; use crate::ast::compiler::lambda_collector::LambdaCollector;
use crate::ast::compiler::lowering::{ExecNode, Lowering};
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::{ExecNode, TCO};
use crate::ast::rtl::intrinsics; use crate::ast::rtl::intrinsics;
use crate::ast::types::{ use crate::ast::types::{
Identity, NodeIdentity, Object, Purity, SourceLocation, StaticType, Value, Identity, NodeIdentity, Object, Purity, SourceLocation, StaticType, Value,
@@ -141,7 +141,7 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
.join("\n")); .join("\n"));
} }
let exec_ast = TCO::optimize(Analyzer::analyze(&typed_ast, &HashMap::new())); // Minimal analysis for macro eval let exec_ast = Lowering::lower(Analyzer::analyze(&typed_ast, &HashMap::new())); // Minimal analysis for macro eval
let mut vm = VM::new(self.global_values.clone()); let mut vm = VM::new(self.global_values.clone());
vm.run(&exec_ast) vm.run(&exec_ast)
@@ -586,8 +586,8 @@ impl Environment {
.with_registry(self.typed_function_registry.clone()); .with_registry(self.typed_function_registry.clone());
let optimized = optimizer.optimize(specialized); let optimized = optimizer.optimize(specialized);
// 5. TCO // 5. Lowering
TCO::optimize(optimized) Lowering::lower(optimized)
} }
pub fn instantiate(&self, node: ExecNode) -> Rc<crate::ast::types::NativeFunction> { pub fn instantiate(&self, node: ExecNode) -> Rc<crate::ast::types::NativeFunction> {
@@ -705,14 +705,14 @@ impl Environment {
.with_purity(global_purity.clone()); .with_purity(global_purity.clone());
let optimized_ast = optimizer.optimize(specialized_ast); let optimized_ast = optimizer.optimize(specialized_ast);
let tco_ast = TCO::optimize(optimized_ast); let exec_ast = Lowering::lower(optimized_ast);
let mut vm = VM::new(global_values.clone()); let mut vm = VM::new(global_values.clone());
let compiled_val = match vm.run(&tco_ast) { let compiled_val = match vm.run(&exec_ast) {
Ok(v) => v, Ok(v) => v,
Err(e) => return Err(format!("VM Error during specialization: {}", e)), Err(e) => return Err(format!("VM Error during specialization: {}", e)),
}; };
let ret_type = tco_ast.ty.ty.clone(); let ret_type = exec_ast.ty.ty.clone();
Ok((compiled_val, ret_type)) Ok((compiled_val, ret_type))
}, },
); );
@@ -774,4 +774,3 @@ impl Environment {
Ok((final_result, observer.logs)) Ok((final_result, observer.logs))
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind}; use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind};
use crate::ast::compiler::tco::ExecNode; use crate::ast::compiler::lowering::ExecNode;
use crate::ast::nodes::Node; use crate::ast::nodes::Node;
use crate::ast::types::{Object, Value}; use crate::ast::types::{Object, Value};
use std::any::Any; use std::any::Any;