diff --git a/src/ast/compiler/tco.rs b/src/ast/compiler/lowering.rs similarity index 95% rename from src/ast/compiler/tco.rs rename to src/ast/compiler/lowering.rs index fa72a5f..fb9d0cc 100644 --- a/src/ast/compiler/tco.rs +++ b/src/ast/compiler/lowering.rs @@ -1,288 +1,288 @@ -use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind}; -use crate::ast::nodes::Node; -use crate::ast::types::StaticType; -use std::fmt::Debug; -use std::rc::Rc; - -#[derive(Clone)] -pub struct RuntimeMetadata { - pub ty: StaticType, - pub is_tail: bool, - /// The analyzed node, containing metrics and a link to the original TypedNode. - pub original: Rc, - pub stack_size: u32, -} - -impl Debug for RuntimeMetadata { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Metadata") - .field("ty", &self.ty) - .field("is_tail", &self.is_tail) - .field("stack_size", &self.stack_size) - .finish() - } -} - -/// The ExecNode is the AST used by the VM. It carries TCO flags and links to metrics. -pub type ExecNode = Node, RuntimeMetadata>; - -fn calc_stack_size(root_node: &Node, T>) -> u32 { - let mut max_slot = -1i32; - - fn visit(node: &Node, T>, max_slot: &mut i32) { - match &node.kind { - BoundKind::Get { - addr: Address::Local(slot), - .. - } - | BoundKind::Set { - addr: Address::Local(slot), - .. - } - | BoundKind::Define { - addr: Address::Local(slot), - .. - } => { - if slot.0 as i32 > *max_slot { - *max_slot = slot.0 as i32; - } - } - BoundKind::Lambda { .. } => { - // Do NOT recurse into nested lambdas to prevent over-allocating outer stacks - } - _ => {} - } - - // Generic traversal - match &node.kind { - BoundKind::If { - cond, - then_br, - else_br, - } => { - visit(cond, max_slot); - visit(then_br, max_slot); - if let Some(e) = else_br { - visit(e, max_slot); - } - } - BoundKind::Set { value, .. } | BoundKind::Define { value, .. } => { - visit(value, max_slot); - } - BoundKind::Destructure { pattern, value } => { - visit(pattern, max_slot); - visit(value, max_slot); - } - BoundKind::Pipe { inputs, lambda, .. } => { - for i in inputs { - visit(i, max_slot); - } - visit(lambda, max_slot); - } - BoundKind::Call { callee, args } => { - visit(callee, max_slot); - visit(args, max_slot); - } - BoundKind::Again { args } => visit(args, max_slot), - BoundKind::Block { exprs } => { - for e in exprs { - visit(e, max_slot); - } - } - BoundKind::Tuple { elements } => { - for e in elements { - visit(e, max_slot); - } - } - BoundKind::Record { values, .. } => { - for v in values { - visit(v, 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, - // but not any deeply nested lambdas. - if let BoundKind::Lambda { params, body, .. } = &root_node.kind { - visit(params, &mut max_slot); - visit(body, &mut max_slot); - } else { - visit(root_node, &mut max_slot); - } - - (max_slot + 1) as u32 -} - -pub struct TCO; - -impl TCO { - /// Lowers an AnalyzedNode to an ExecNode and marks tail positions. - pub fn optimize(node: AnalyzedNode) -> ExecNode { - let root_stack_size = calc_stack_size(&node); - let mut exec_node = Self::transform(Rc::new(node), true); - exec_node.ty.stack_size = root_stack_size; - exec_node - } - - fn transform(node_rc: Rc, is_tail_position: bool) -> ExecNode { - let node = &*node_rc; - let new_kind = match &node.kind { - BoundKind::Call { callee, args } => BoundKind::Call { - callee: Rc::new(Self::transform(callee.clone(), false)), - args: Rc::new(Self::transform(args.clone(), false)), - }, - - BoundKind::Again { args } => { - if !is_tail_position { - panic!("'again' is only allowed in tail position to avoid dead code."); - } - BoundKind::Again { - args: Rc::new(Self::transform(args.clone(), false)), - } - } - BoundKind::Pipe { - inputs, - lambda, - out_type, - } => { - let mut t_inputs = Vec::with_capacity(inputs.len()); - for input in inputs { - t_inputs.push(Rc::new(Self::transform(input.clone(), false))); - } - BoundKind::Pipe { - inputs: t_inputs, - lambda: Rc::new(Self::transform(lambda.clone(), false)), - out_type: out_type.clone(), - } - } - - BoundKind::If { - cond, - then_br, - else_br, - } => BoundKind::If { - cond: Rc::new(Self::transform(cond.clone(), false)), - then_br: Rc::new(Self::transform( - then_br.clone(), - is_tail_position, - )), - else_br: else_br - .as_ref() - .map(|e| Rc::new(Self::transform(e.clone(), is_tail_position))), - }, - - BoundKind::Block { exprs } => { - if exprs.is_empty() { - 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(Rc::new(Self::transform( - expr.clone(), - is_tail_position && is_last, - ))); - } - BoundKind::Block { exprs: new_exprs } - } - } - - BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - } => 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 } => BoundKind::Set { - addr: *addr, - value: Rc::new(Self::transform(value.clone(), false)), - }, - BoundKind::Define { - name, - addr, - kind, - value, - captured_by, - } => BoundKind::Define { - name: name.clone(), - addr: *addr, - kind: *kind, - value: Rc::new(Self::transform(value.clone(), false)), - captured_by: captured_by.clone(), - }, - BoundKind::Destructure { pattern, value } => BoundKind::Destructure { - pattern: Rc::new(Self::transform(pattern.clone(), false)), - value: Rc::new(Self::transform(value.clone(), false)), - }, - BoundKind::Record { layout, values } => { - let new_values = values - .iter() - .map(|v| Rc::new(Self::transform(v.clone(), false))) - .collect(); - BoundKind::Record { - layout: layout.clone(), - values: new_values, - } - } - BoundKind::Tuple { elements } => { - let new_elements = elements - .iter() - .map(|e| Rc::new(Self::transform(e.clone(), false))) - .collect(); - BoundKind::Tuple { - elements: new_elements, - } - } - BoundKind::Constant(v) => BoundKind::Constant(v.clone()), - BoundKind::Get { addr, name } => BoundKind::Get { - addr: *addr, - name: name.clone(), - }, - BoundKind::FieldAccessor(k) => BoundKind::FieldAccessor(*k), - BoundKind::GetField { rec, field } => BoundKind::GetField { - rec: Rc::new(Self::transform(rec.clone(), false)), - field: *field, - }, - BoundKind::Nop => BoundKind::Nop, - BoundKind::Expansion { - original_call, - bound_expanded, - } => BoundKind::Expansion { - original_call: original_call.clone(), - bound_expanded: Rc::new(Self::transform( - bound_expanded.clone(), - is_tail_position, - )), - }, - BoundKind::Extension(_) => BoundKind::Nop, - BoundKind::Error => BoundKind::Error, - }; - - let stack_size = match &new_kind { - BoundKind::Lambda { .. } => calc_stack_size(node), - _ => 0, - }; - - Node { - identity: node.identity.clone(), - kind: new_kind, - ty: RuntimeMetadata { - ty: node.ty.original.ty.clone(), - is_tail: is_tail_position, - original: node_rc, - stack_size, - }, - } - } -} +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind}; +use crate::ast::nodes::Node; +use crate::ast::types::StaticType; +use std::fmt::Debug; +use std::rc::Rc; + +#[derive(Clone)] +pub struct RuntimeMetadata { + pub ty: StaticType, + pub is_tail: bool, + /// The analyzed node, containing metrics and a link to the original TypedNode. + pub original: Rc, + pub stack_size: u32, +} + +impl Debug for RuntimeMetadata { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Metadata") + .field("ty", &self.ty) + .field("is_tail", &self.is_tail) + .field("stack_size", &self.stack_size) + .finish() + } +} + +/// The ExecNode is the AST used by the VM. It carries TCO flags and links to metrics. +pub type ExecNode = Node, RuntimeMetadata>; + +fn calc_stack_size(root_node: &Node, T>) -> u32 { + let mut max_slot = -1i32; + + fn visit(node: &Node, T>, max_slot: &mut i32) { + match &node.kind { + BoundKind::Get { + addr: Address::Local(slot), + .. + } + | BoundKind::Set { + addr: Address::Local(slot), + .. + } + | BoundKind::Define { + addr: Address::Local(slot), + .. + } => { + if slot.0 as i32 > *max_slot { + *max_slot = slot.0 as i32; + } + } + BoundKind::Lambda { .. } => { + // Do NOT recurse into nested lambdas to prevent over-allocating outer stacks + } + _ => {} + } + + // Generic traversal + match &node.kind { + BoundKind::If { + cond, + then_br, + else_br, + } => { + visit(cond, max_slot); + visit(then_br, max_slot); + if let Some(e) = else_br { + visit(e, max_slot); + } + } + BoundKind::Set { value, .. } | BoundKind::Define { value, .. } => { + visit(value, max_slot); + } + BoundKind::Destructure { pattern, value } => { + visit(pattern, max_slot); + visit(value, max_slot); + } + BoundKind::Pipe { inputs, lambda, .. } => { + for i in inputs { + visit(i, max_slot); + } + visit(lambda, max_slot); + } + BoundKind::Call { callee, args } => { + visit(callee, max_slot); + visit(args, max_slot); + } + BoundKind::Again { args } => visit(args, max_slot), + BoundKind::Block { exprs } => { + for e in exprs { + visit(e, max_slot); + } + } + BoundKind::Tuple { elements } => { + for e in elements { + visit(e, max_slot); + } + } + BoundKind::Record { values, .. } => { + for v in values { + visit(v, 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, + // but not any deeply nested lambdas. + if let BoundKind::Lambda { params, body, .. } = &root_node.kind { + visit(params, &mut max_slot); + visit(body, &mut max_slot); + } else { + visit(root_node, &mut max_slot); + } + + (max_slot + 1) as u32 +} + +pub struct Lowering; + +impl Lowering { + /// Lowers an AnalyzedNode to an ExecNode, marking tail positions and calculating stack sizes. + pub fn lower(node: AnalyzedNode) -> ExecNode { + let root_stack_size = calc_stack_size(&node); + let mut exec_node = Self::transform(Rc::new(node), true); + exec_node.ty.stack_size = root_stack_size; + exec_node + } + + fn transform(node_rc: Rc, is_tail_position: bool) -> ExecNode { + let node = &*node_rc; + let new_kind = match &node.kind { + BoundKind::Call { callee, args } => BoundKind::Call { + callee: Rc::new(Self::transform(callee.clone(), false)), + args: Rc::new(Self::transform(args.clone(), false)), + }, + + BoundKind::Again { args } => { + if !is_tail_position { + panic!("'again' is only allowed in tail position to avoid dead code."); + } + BoundKind::Again { + args: Rc::new(Self::transform(args.clone(), false)), + } + } + BoundKind::Pipe { + inputs, + lambda, + out_type, + } => { + let mut t_inputs = Vec::with_capacity(inputs.len()); + for input in inputs { + t_inputs.push(Rc::new(Self::transform(input.clone(), false))); + } + BoundKind::Pipe { + inputs: t_inputs, + lambda: Rc::new(Self::transform(lambda.clone(), false)), + out_type: out_type.clone(), + } + } + + BoundKind::If { + cond, + then_br, + else_br, + } => BoundKind::If { + cond: Rc::new(Self::transform(cond.clone(), false)), + then_br: Rc::new(Self::transform( + then_br.clone(), + is_tail_position, + )), + else_br: else_br + .as_ref() + .map(|e| Rc::new(Self::transform(e.clone(), is_tail_position))), + }, + + BoundKind::Block { exprs } => { + if exprs.is_empty() { + 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(Rc::new(Self::transform( + expr.clone(), + is_tail_position && is_last, + ))); + } + BoundKind::Block { exprs: new_exprs } + } + } + + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => 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 } => BoundKind::Set { + addr: *addr, + value: Rc::new(Self::transform(value.clone(), false)), + }, + BoundKind::Define { + name, + addr, + kind, + value, + captured_by, + } => BoundKind::Define { + name: name.clone(), + addr: *addr, + kind: *kind, + value: Rc::new(Self::transform(value.clone(), false)), + captured_by: captured_by.clone(), + }, + BoundKind::Destructure { pattern, value } => BoundKind::Destructure { + pattern: Rc::new(Self::transform(pattern.clone(), false)), + value: Rc::new(Self::transform(value.clone(), false)), + }, + BoundKind::Record { layout, values } => { + let new_values = values + .iter() + .map(|v| Rc::new(Self::transform(v.clone(), false))) + .collect(); + BoundKind::Record { + layout: layout.clone(), + values: new_values, + } + } + BoundKind::Tuple { elements } => { + let new_elements = elements + .iter() + .map(|e| Rc::new(Self::transform(e.clone(), false))) + .collect(); + BoundKind::Tuple { + elements: new_elements, + } + } + BoundKind::Constant(v) => BoundKind::Constant(v.clone()), + BoundKind::Get { addr, name } => BoundKind::Get { + addr: *addr, + name: name.clone(), + }, + BoundKind::FieldAccessor(k) => BoundKind::FieldAccessor(*k), + BoundKind::GetField { rec, field } => BoundKind::GetField { + rec: Rc::new(Self::transform(rec.clone(), false)), + field: *field, + }, + BoundKind::Nop => BoundKind::Nop, + BoundKind::Expansion { + original_call, + bound_expanded, + } => BoundKind::Expansion { + original_call: original_call.clone(), + bound_expanded: Rc::new(Self::transform( + bound_expanded.clone(), + is_tail_position, + )), + }, + BoundKind::Extension(_) => BoundKind::Nop, + BoundKind::Error => BoundKind::Error, + }; + + let stack_size = match &new_kind { + BoundKind::Lambda { .. } => calc_stack_size(node), + _ => 0, + }; + + Node { + identity: node.identity.clone(), + kind: new_kind, + ty: RuntimeMetadata { + ty: node.ty.original.ty.clone(), + is_tail: is_tail_position, + original: node_rc, + stack_size, + }, + } + } +} diff --git a/src/ast/compiler/mod.rs b/src/ast/compiler/mod.rs index 2715725..5c604c7 100644 --- a/src/ast/compiler/mod.rs +++ b/src/ast/compiler/mod.rs @@ -7,7 +7,7 @@ pub mod lambda_collector; pub mod macros; pub mod optimizer; pub mod specializer; -pub mod tco; +pub mod lowering; pub mod type_checker; pub use binder::*; @@ -17,5 +17,5 @@ pub use dumper::*; pub use macros::*; pub use optimizer::*; pub use specializer::*; -pub use tco::*; +pub use lowering::*; pub use type_checker::*; diff --git a/src/ast/environment.rs b/src/ast/environment.rs index bf519c3..5ad6623 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -15,10 +15,10 @@ use crate::ast::compiler::bound_nodes::{ }; use crate::ast::compiler::dumper::Dumper; 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::optimizer::Optimizer; use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer}; -use crate::ast::compiler::tco::{ExecNode, TCO}; use crate::ast::rtl::intrinsics; use crate::ast::types::{ Identity, NodeIdentity, Object, Purity, SourceLocation, StaticType, Value, @@ -141,7 +141,7 @@ impl MacroEvaluator for RuntimeMacroEvaluator { .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()); vm.run(&exec_ast) @@ -586,8 +586,8 @@ impl Environment { .with_registry(self.typed_function_registry.clone()); let optimized = optimizer.optimize(specialized); - // 5. TCO - TCO::optimize(optimized) + // 5. Lowering + Lowering::lower(optimized) } pub fn instantiate(&self, node: ExecNode) -> Rc { @@ -705,14 +705,14 @@ impl Environment { .with_purity(global_purity.clone()); 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 compiled_val = match vm.run(&tco_ast) { + let compiled_val = match vm.run(&exec_ast) { Ok(v) => v, 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)) }, ); @@ -774,4 +774,3 @@ impl Environment { Ok((final_result, observer.logs)) } } - diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 520b23e..4c9e352 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -1,5 +1,5 @@ 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::types::{Object, Value}; use std::any::Any;