diff --git a/src/ast/compiler/analyzer.rs b/src/ast/compiler/analyzer.rs index 1abab4c..1e57a14 100644 --- a/src/ast/compiler/analyzer.rs +++ b/src/ast/compiler/analyzer.rs @@ -1,37 +1,32 @@ -use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; -use crate::ast::types::{Identity, Purity, StaticType}; +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, NodeMetrics, TypedNode}; +use crate::ast::types::Purity; use std::collections::{HashMap, HashSet}; - -#[derive(Debug, Clone, Default)] -pub struct Analysis { - pub purity: HashMap, - pub is_recursive: HashSet, -} +use std::rc::Rc; pub struct Analyzer<'a> { global_purity: &'a HashMap, - results: Analysis, /// Stack of currently visiting lambdas to detect direct recursion. - lambda_stack: Vec, + lambda_stack: Vec, /// Map of global index to its Lambda identity if known. - globals_to_lambdas: HashMap, + globals_to_lambdas: HashMap, + /// Set of identities that were found to be recursive. + recursive_identities: HashSet, } impl<'a> Analyzer<'a> { - pub fn analyze(node: &TypedNode, global_purity: &'a HashMap) -> Analysis { + pub fn analyze(node: &TypedNode, global_purity: &'a HashMap) -> AnalyzedNode { let mut analyzer = Self { global_purity, - results: Analysis::default(), lambda_stack: Vec::new(), globals_to_lambdas: HashMap::new(), + recursive_identities: HashSet::new(), }; - + // First pass: map globals to their lambda identities analyzer.collect_globals(node); - - // Second pass: full analysis - analyzer.visit(node); - analyzer.results + + // Second pass: full analysis (decorating TypedNode into AnalyzedNode) + analyzer.visit(Rc::new(node.clone())) } fn collect_globals(&mut self, node: &TypedNode) { @@ -46,104 +41,147 @@ impl<'a> Analyzer<'a> { for e in exprs { self.collect_globals(e); } } _ => { - // Simplified traversal for global collection node.kind.for_each_child(|child| self.collect_globals(child)); } } } - fn visit(&mut self, node: &TypedNode) -> Purity { - let purity = match &node.kind { - BoundKind::Constant(_) | BoundKind::Nop | BoundKind::Parameter { .. } => Purity::Pure, - - BoundKind::Get { addr, .. } => match addr { - Address::Global(idx) => self.global_purity.get(idx).cloned().unwrap_or(Purity::Pure), - _ => Purity::Pure, // Locals are considered pure access in this model - }, + fn visit(&mut self, node_rc: Rc) -> AnalyzedNode { + let node = &*node_rc; + let mut is_recursive = false; - BoundKind::Set { .. } => Purity::Impure, + let (new_kind, purity) = match &node.kind { + BoundKind::Constant(v) => (BoundKind::Constant(v.clone()), Purity::Pure), + BoundKind::Nop => (BoundKind::Nop, Purity::Pure), + BoundKind::Parameter { name, slot } => { + (BoundKind::Parameter { name: name.clone(), slot: *slot }, Purity::Pure) + } - BoundKind::DefLocal { value, .. } | BoundKind::DefGlobal { value, .. } => { - self.visit(value) + BoundKind::Get { addr, name } => { + let p = match addr { + Address::Global(idx) => self.global_purity.get(idx).cloned().unwrap_or(Purity::Pure), + _ => Purity::Pure, + }; + (BoundKind::Get { addr: *addr, name: name.clone() }, p) + } + + BoundKind::Set { addr, value } => { + let val_m = self.visit(Rc::new((**value).clone())); + (BoundKind::Set { addr: *addr, value: Box::new(val_m) }, Purity::Impure) + } + + BoundKind::DefLocal { name, slot, value, captured_by } => { + let val_m = self.visit(Rc::new((**value).clone())); + let p = val_m.ty.purity; + (BoundKind::DefLocal { name: name.clone(), slot: *slot, value: Box::new(val_m), captured_by: captured_by.clone() }, p) + } + + BoundKind::DefGlobal { name, global_index, value } => { + let val_m = self.visit(Rc::new((**value).clone())); + let p = val_m.ty.purity; + (BoundKind::DefGlobal { name: name.clone(), global_index: *global_index, value: Box::new(val_m) }, p) } BoundKind::If { cond, then_br, else_br } => { - let p_cond = self.visit(cond); - let p_then = self.visit(then_br); - let p_else = else_br.as_ref().map(|e| self.visit(e)).unwrap_or(Purity::Pure); - p_cond.min(p_then).min(p_else) + let cond_m = self.visit(Rc::new((**cond).clone())); + let then_m = self.visit(Rc::new((**then_br).clone())); + let else_m = else_br.as_ref().map(|e| self.visit(Rc::new((**e).clone()))); + + let mut p = cond_m.ty.purity.min(then_m.ty.purity); + if let Some(ref em) = else_m { p = p.min(em.ty.purity); } + (BoundKind::If { cond: Box::new(cond_m), then_br: Box::new(then_m), else_br: else_m.map(Box::new) }, p) } - BoundKind::Lambda { body, .. } => { + BoundKind::Lambda { params, upvalues, body, positional_count } => { self.lambda_stack.push(node.identity.clone()); - self.visit(body); + let params_m = self.visit(params.clone()); + let body_m = self.visit(body.clone()); self.lambda_stack.pop(); - Purity::Pure // Creating a lambda is pure + + is_recursive = self.recursive_identities.contains(&node.identity); + (BoundKind::Lambda { params: Rc::new(params_m), upvalues: upvalues.clone(), body: Rc::new(body_m), positional_count: *positional_count }, Purity::Pure) } BoundKind::Call { callee, args } => { - let p_callee = self.visit(callee); - let p_args = self.visit(args); - - // Detect recursion + let callee_m = self.visit(Rc::new((**callee).clone())); + let args_m = self.visit(Rc::new((**args).clone())); + if let BoundKind::Get { addr: Address::Global(idx), .. } = &callee.kind && let Some(lambda_id) = self.globals_to_lambdas.get(idx) && self.lambda_stack.contains(lambda_id) { - self.results.is_recursive.insert(lambda_id.clone()); - // Also mark the call itself if needed - self.results.is_recursive.insert(node.identity.clone()); + self.recursive_identities.insert(lambda_id.clone()); + is_recursive = true; } - // For purity, we'd need to know the function's purity. - // For now, if it's a call, we conservatively check if it's a known pure global. let p_func = if let BoundKind::Get { addr: Address::Global(idx), .. } = &callee.kind { self.global_purity.get(idx).cloned().unwrap_or(Purity::Impure) } else { Purity::Impure }; - - p_callee.min(p_args).min(p_func) + let p = callee_m.ty.purity.min(args_m.ty.purity).min(p_func); + (BoundKind::Call { callee: Box::new(callee_m), args: Box::new(args_m) }, p) } BoundKind::Block { exprs } => { + let mut new_exprs = Vec::with_capacity(exprs.len()); let mut p = Purity::Pure; for e in exprs { - p = p.min(self.visit(e)); + let em = self.visit(Rc::new(e.clone())); + p = p.min(em.ty.purity); + new_exprs.push(em); } - p + (BoundKind::Block { exprs: new_exprs }, p) } BoundKind::Tuple { elements } => { + let mut new_elements = Vec::with_capacity(elements.len()); let mut p = Purity::Pure; for e in elements { - p = p.min(self.visit(e)); + let em = self.visit(Rc::new(e.clone())); + p = p.min(em.ty.purity); + new_elements.push(em); } - p + (BoundKind::Tuple { elements: new_elements }, p) } BoundKind::Record { fields } => { + let mut new_fields = Vec::with_capacity(fields.len()); let mut p = Purity::Pure; for (k, v) in fields { - p = p.min(self.visit(k)).min(self.visit(v)); + let km = self.visit(Rc::new(k.clone())); + let vm = self.visit(Rc::new(v.clone())); + p = p.min(km.ty.purity).min(vm.ty.purity); + new_fields.push((km, vm)); } - p + (BoundKind::Record { fields: new_fields }, p) } - - _ => Purity::Impure, + + BoundKind::Expansion { original_call, bound_expanded } => { + let expanded_m = self.visit(Rc::new((**bound_expanded).clone())); + (BoundKind::Expansion { original_call: original_call.clone(), bound_expanded: Box::new(expanded_m.clone()) }, expanded_m.ty.purity) + } + + BoundKind::Extension(_) => (BoundKind::Nop, Purity::Impure), }; - self.results.purity.insert(node.identity.clone(), purity); - purity + crate::ast::nodes::Node { + identity: node.identity.clone(), + kind: new_kind, + ty: NodeMetrics { + original: node_rc, + purity, + is_recursive, + }, + } } } -/// Extension trait to make traversal easier trait NodeExt { fn for_each_child(&self, f: F); } -impl NodeExt for BoundKind { +impl NodeExt for BoundKind { fn for_each_child(&self, mut f: F) { match self { BoundKind::If { cond, then_br, else_br } => { @@ -159,14 +197,17 @@ impl NodeExt for BoundKind { f(callee); f(args); } BoundKind::Block { exprs } => { - for e in exprs { f(e); } // Block + for e in exprs { f(e); } } BoundKind::Tuple { elements } => { - for e in elements { f(e); } // Tuple + for e in elements { f(e); } } BoundKind::Record { fields } => { for (k, v) in fields { f(k); f(v); } } + BoundKind::Expansion { bound_expanded, .. } => { + f(bound_expanded); + } _ => {} } } diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index 4c8226f..6edfa5e 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -27,6 +27,17 @@ pub type BoundNode = Node, T>; /// Type alias for a node that has been fully type-checked. pub type TypedNode = BoundNode; +/// Metrics collected during the analysis phase. +#[derive(Debug, Clone, PartialEq)] +pub struct NodeMetrics { + pub original: Rc, + pub purity: crate::ast::types::Purity, + pub is_recursive: bool, +} + +/// Type alias for a node that has been analyzed. +pub type AnalyzedNode = BoundNode; + #[derive(Debug, Clone)] pub enum BoundKind { Nop, diff --git a/src/ast/compiler/optimizer.rs b/src/ast/compiler/optimizer.rs index 0667eef..094aaff 100644 --- a/src/ast/compiler/optimizer.rs +++ b/src/ast/compiler/optimizer.rs @@ -1,5 +1,4 @@ -use crate::ast::compiler::analyzer::Analysis; -use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, NodeMetrics}; use crate::ast::nodes::Node; use crate::ast::types::{Purity, StaticType, Value}; use crate::ast::vm::Closure; @@ -7,23 +6,17 @@ use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::rc::Rc; -/// The Optimizer performs Phase 2 (Cracking), Phase 2.5/2.6 (Aggressive Collapsing & DCE) -/// and Phase 4 (Global Inlining). pub struct Optimizer { pub enabled: bool, max_passes: usize, pub globals: Option>>>, pub global_purity: Option>>>, - pub lambda_registry: Option>>>, - pub analysis: Analysis, + pub lambda_registry: Option>>>, } -/// Global state for tracking the optimization path (recursion, depth). struct PathTracker { inlining_depth: usize, - /// Stack of global indices to detect recursion in named functions. inlining_stack: HashSet, - /// Stack of node identities to detect recursion in anonymous lambdas. identity_stack: HashSet, } @@ -66,15 +59,9 @@ impl Optimizer { globals: None, global_purity: None, lambda_registry: None, - analysis: Analysis::default(), } } - pub fn with_analysis(mut self, analysis: Analysis) -> Self { - self.analysis = analysis; - self - } - pub fn with_globals(mut self, globals: Rc>>) -> Self { self.globals = Some(globals); self @@ -85,12 +72,12 @@ impl Optimizer { self } - pub fn with_registry(mut self, registry: Rc>>) -> Self { + pub fn with_registry(mut self, registry: Rc>>) -> Self { self.lambda_registry = Some(registry); self } - pub fn optimize(&self, node: TypedNode) -> TypedNode { + pub fn optimize(&self, node: AnalyzedNode) -> AnalyzedNode { if !self.enabled { return node; } @@ -110,12 +97,12 @@ impl Optimizer { fn visit_node( &self, - node: TypedNode, + node: AnalyzedNode, sub: &mut SubstitutionMap, path: &mut PathTracker, - ) -> TypedNode { - let (new_kind, new_ty) = match node.kind { - BoundKind::Get { addr, name } => { + ) -> AnalyzedNode { + let (new_kind, metrics) = match node.kind { + BoundKind::Get { addr, ref name } => { let new_addr = match addr { Address::Local(slot) => { if !sub.assigned_locals.contains(&slot) { @@ -123,11 +110,7 @@ impl Optimizer { return inlined_node.clone(); } if let Some(val) = sub.locals.get(&slot) { - return Node { - identity: node.identity, - ty: val.static_type(), - kind: BoundKind::Constant(val.clone()), - }; + return self.make_constant_node(val.clone(), &node); } } sub.used_slots.insert(slot); @@ -135,11 +118,7 @@ impl Optimizer { } Address::Upvalue(idx) => { if let Some(val) = sub.upvalues.get(&idx) { - return Node { - identity: node.identity, - ty: val.static_type(), - kind: BoundKind::Constant(val.clone()), - }; + return self.make_constant_node(val.clone(), &node); } Address::Upvalue(idx) } @@ -148,22 +127,14 @@ impl Optimizer { if let Some(val) = sub.globals.get(&idx) && self.is_inlinable_value(val, Some(idx)) { - return Node { - identity: node.identity, - ty: val.static_type(), - kind: BoundKind::Constant(val.clone()), - }; + return self.make_constant_node(val.clone(), &node); } if let Some(globals_rc) = &self.globals { let globals = globals_rc.borrow(); if let Some(val) = globals.get(idx as usize) && self.is_inlinable_value(val, Some(idx)) { - return Node { - identity: node.identity, - ty: val.static_type(), - kind: BoundKind::Constant(val.clone()), - }; + return self.make_constant_node(val.clone(), &node); } } } @@ -174,20 +145,20 @@ impl Optimizer { ( BoundKind::Get { addr: new_addr, - name, + name: name.clone(), }, - node.ty, + node.ty.clone(), ) } - BoundKind::Parameter { name, slot } => { + BoundKind::Parameter { ref name, slot } => { let new_slot = sub.map_slot(slot); ( BoundKind::Parameter { - name, + name: name.clone(), slot: new_slot, }, - node.ty, + node.ty.clone(), ) } @@ -217,7 +188,7 @@ impl Optimizer { addr: new_addr, value, }, - node.ty, + node.ty.clone(), ) } @@ -226,7 +197,6 @@ impl Optimizer { let args = self.visit_node(*args, sub, path); if self.enabled { - // Case 1: Beta-Reduction for Lambda Literals if let BoundKind::Lambda { params, body, @@ -236,29 +206,25 @@ impl Optimizer { && upvalues.is_empty() && positional_count.is_some() && path.inlining_depth < 5 + && !callee.ty.is_recursive + && path.enter_lambda(&callee.identity) { - // USE STATIC ANALYSIS: Don't inline if the function is known to be recursive - if !self.analysis.is_recursive.contains(&callee.identity) - && path.enter_lambda(&callee.identity) - { - path.inlining_depth += 1; - let collapsed = self.try_beta_reduce_with_sub( - params, - &args, - (**body).clone(), - &mut SubstitutionMap::new(), - path, - ); - path.inlining_depth -= 1; - path.exit_lambda(&callee.identity); + path.inlining_depth += 1; + let collapsed = self.try_beta_reduce_with_sub( + params, + &args, + (**body).clone(), + &mut SubstitutionMap::new(), + path, + ); + path.inlining_depth -= 1; + path.exit_lambda(&callee.identity); - if let Some(res) = collapsed { - return res; - } + if let Some(res) = collapsed { + return res; } } - // Case 1.5: Beta-Reduction for Global Registry functions (from Registry) if let BoundKind::Get { addr: Address::Global(idx), .. @@ -277,61 +243,20 @@ impl Optimizer { } = &lambda_node.kind && upvalues.is_empty() && positional_count.is_some() + && !lambda_node.ty.is_recursive { - // USE STATIC ANALYSIS: Don't inline if the function is known to be recursive - if !self.analysis.is_recursive.contains(&lambda_node.identity) { - let mut inner_sub = SubstitutionMap::new(); - path.inlining_stack.insert(*idx); - path.inlining_depth += 1; - let collapsed = self.try_beta_reduce_with_sub( - params.as_ref(), - &args, - body.as_ref().clone(), - &mut inner_sub, - path, - ); - path.inlining_depth -= 1; - path.inlining_stack.remove(idx); - - if let Some(res) = collapsed { - return res; - } - } - } - } - - // Case 2: Cracking and Inlining for Constant Closures - if let BoundKind::Constant(Value::Object(ref obj)) = callee.kind - && path.inlining_depth < 5 - && let Some(closure) = obj.as_any().downcast_ref::() - && (closure.upvalues.is_empty() - || self.purity_of(&closure.function_node) >= Purity::SideEffectFree) - { - // USE STATIC ANALYSIS: Don't inline if the function is known to be recursive - if !self.analysis.is_recursive.contains(&closure.function_node.identity) - && path.enter_lambda(&closure.function_node.identity) - { - let mut closure_sub = SubstitutionMap::new(); - for (i, cell) in closure.upvalues.iter().enumerate() { - closure_sub.add_upvalue(i as u32, cell.borrow().clone()); - } - + let mut inner_sub = SubstitutionMap::new(); + path.inlining_stack.insert(*idx); path.inlining_depth += 1; - let inlined_body = self.visit_node( - (*closure.function_node).clone(), - &mut closure_sub, - path, - ); - let collapsed = self.try_beta_reduce_with_sub( - &closure.parameter_node, + params.as_ref(), &args, - inlined_body, - &mut closure_sub, + body.as_ref().clone(), + &mut inner_sub, path, ); path.inlining_depth -= 1; - path.exit_lambda(&closure.function_node.identity); + path.inlining_stack.remove(idx); if let Some(res) = collapsed { return res; @@ -339,138 +264,107 @@ impl Optimizer { } } + if let BoundKind::Constant(Value::Object(ref obj)) = callee.kind + && path.inlining_depth < 5 + && let Some(closure) = obj.as_any().downcast_ref::() + && (closure.upvalues.is_empty() + || closure.function_node.ty.purity >= Purity::SideEffectFree) + && !closure.function_node.ty.is_recursive + && path.enter_lambda(&closure.function_node.identity) + { + let mut closure_sub = SubstitutionMap::new(); + for (i, cell) in closure.upvalues.iter().enumerate() { + closure_sub.add_upvalue(i as u32, cell.borrow().clone()); + } + + path.inlining_depth += 1; + let inlined_body = self.visit_node( + (*closure.function_node).clone(), + &mut closure_sub, + path, + ); + + let collapsed = self.try_beta_reduce_with_sub( + &closure.parameter_node, + &args, + inlined_body, + &mut closure_sub, + path, + ); + path.inlining_depth -= 1; + path.exit_lambda(&closure.function_node.identity); + + if let Some(res) = collapsed { + return res; + } + } + if let Some(folded) = self.try_fold_pure(&callee, &args) { return folded; } } - if self.enabled - && let BoundKind::Constant(Value::Object(ref obj)) = callee.kind - && path.inlining_depth < 5 - && let Some(closure) = obj.as_any().downcast_ref::() - && (closure.upvalues.is_empty() - || self.purity_of(&closure.function_node) >= Purity::SideEffectFree) - { - // CRACKING fallback (only if not recursive already handled above) - let mut closure_sub = SubstitutionMap::new(); - for (i, cell) in closure.upvalues.iter().enumerate() { - closure_sub.add_upvalue(i as u32, cell.borrow().clone()); - } - path.inlining_depth += 1; - let inlined_body = - self.visit_node((*closure.function_node).clone(), &mut closure_sub, path); - path.inlining_depth -= 1; - - let cracked_lambda = Node { - identity: callee.identity.clone(), - ty: callee.ty.clone(), - kind: BoundKind::Lambda { - params: closure.parameter_node.clone(), - upvalues: vec![], - body: Rc::new(inlined_body), - positional_count: closure.positional_count, - }, - }; - return Node { - identity: node.identity, - 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), }, - node.ty, + node.ty.clone(), ) } BoundKind::If { - cond, - then_br, - else_br, + ref cond, + ref then_br, + ref else_br, } => { - let cond = self.visit_node(*cond, sub, path); + let cond_opt = self.visit_node((**cond).clone(), sub, path); if self.enabled - && let BoundKind::Constant(ref val) = cond.kind + && let BoundKind::Constant(ref val) = cond_opt.kind { if val.is_truthy() { - return self.visit_node(*then_br, sub, path); + return self.visit_node((**then_br).clone(), sub, path); } else if let Some(else_node) = else_br { - return self.visit_node(*else_node, sub, path); + return self.visit_node((**else_node).clone(), sub, path); } else { - return Node { - identity: node.identity, - kind: BoundKind::Nop, - ty: StaticType::Void, - }; + return self.make_nop_node(&node); } } - let then_br = Box::new(self.visit_node(*then_br, sub, path)); - let else_br = else_br.map(|e| Box::new(self.visit_node(*e, sub, path))); + let then_br = Box::new(self.visit_node((**then_br).clone(), sub, path)); + let else_br = else_br.as_ref().map(|e| Box::new(self.visit_node((**e).clone(), sub, path))); ( BoundKind::If { - cond: Box::new(cond), + cond: Box::new(cond_opt), then_br, else_br, }, - node.ty, + node.ty.clone(), ) } - BoundKind::Block { exprs } => { + BoundKind::Block { ref exprs } => { let mut info = UsageInfo::default(); - if !exprs.is_empty() { - for e in &exprs { + for e in exprs { self.collect_usage(e, &mut info); } - if let Some(last) = exprs.last() { - match &last.kind { - BoundKind::Get { - addr: Address::Local(slot), - .. - } => { - info.used_locals.insert(*slot); - } - BoundKind::Get { - addr: Address::Global(idx), - .. - } => { - info.used_globals.insert(*idx); - } - _ => {} - } - } } - // IMPORTANT: Propagate block assignments to the outer map so we don't - // incorrectly inline these variables in the rest of the script. - for slot in &info.assigned_locals { - sub.assigned_locals.insert(*slot); - } - for idx in &info.assigned_globals { - sub.assigned_globals.insert(*idx); - } + for slot in &info.assigned_locals { sub.assigned_locals.insert(*slot); } + for idx in &info.assigned_globals { sub.assigned_globals.insert(*idx); } let mut new_exprs = Vec::with_capacity(exprs.len()); let last_idx = exprs.len().saturating_sub(1); - for (i, e) in exprs.into_iter().enumerate() { + for (i, e) in exprs.iter().enumerate() { let is_last = i == last_idx; - if self.enabled && !is_last { let removable = match &e.kind { BoundKind::DefLocal { slot, value, .. } => { !info.used_locals.contains(slot) && !sub.captured_slots.contains(slot) - && self.purity_of(value) >= Purity::SideEffectFree + && value.ty.purity >= Purity::SideEffectFree } BoundKind::DefGlobal { global_index, @@ -478,7 +372,7 @@ impl Optimizer { .. } => { !info.used_globals.contains(global_index) - && (self.purity_of(value) >= Purity::SideEffectFree + && (value.ty.purity >= Purity::SideEffectFree || matches!(value.kind, BoundKind::Lambda { .. })) } BoundKind::Set { @@ -488,17 +382,15 @@ impl Optimizer { } => { !info.used_locals.contains(slot) && !sub.captured_slots.contains(slot) - && self.purity_of(value) >= Purity::SideEffectFree + && value.ty.purity >= Purity::SideEffectFree } - _ => self.purity_of(&e) >= Purity::SideEffectFree, + _ => e.ty.purity >= Purity::SideEffectFree, }; - if removable { - continue; - } + if removable { continue; } } - let opt = self.visit_node(e, sub, path); + let opt = self.visit_node(e.clone(), sub, path); if self.enabled && matches!(opt.kind, BoundKind::Nop) && !is_last { continue; } @@ -507,22 +399,13 @@ impl Optimizer { if self.enabled { if new_exprs.is_empty() { - return Node { - identity: node.identity, - kind: BoundKind::Nop, - ty: StaticType::Void, - }; + return self.make_nop_node(&node); } else if new_exprs.len() == 1 { return new_exprs.pop().unwrap(); } } - let ty = if new_exprs.is_empty() { - StaticType::Void - } else { - new_exprs.last().unwrap().ty.clone() - }; - (BoundKind::Block { exprs: new_exprs }, ty) + (BoundKind::Block { exprs: new_exprs }, node.ty.clone()) } BoundKind::Lambda { .. } => { @@ -542,10 +425,7 @@ impl Optimizer { let mut info = UsageInfo::default(); self.collect_usage(&node, &mut info); - // Track captures from outer scope that are assigned in this lambda - for idx in &info.assigned_globals { - sub.assigned_globals.insert(*idx); - } + for idx in &info.assigned_globals { sub.assigned_globals.insert(*idx); } let mut new_upvalues = Vec::new(); let mut mapping = Vec::new(); @@ -557,15 +437,11 @@ impl Optimizer { let mut inlined_val = None; match capture_addr { Address::Local(slot) => { - if let Some(val) = sub.locals.get(slot) { - inlined_val = Some(val.clone()); - } + if let Some(val) = sub.locals.get(slot) { inlined_val = Some(val.clone()); } sub.captured_slots.insert(*slot); } Address::Upvalue(idx) => { - if let Some(val) = sub.upvalues.get(idx) { - inlined_val = Some(val.clone()); - } + if let Some(val) = sub.upvalues.get(idx) { inlined_val = Some(val.clone()); } } _ => {} } @@ -584,12 +460,7 @@ impl Optimizer { } } - // 1. Visit parameters to determine their new slots - let params_node = - self.visit_node(params.as_ref().clone(), &mut next_inner_subs, path); - - // 2. IMPORTANT: Pre-register parameter slots in the inner substitution map - // so they are mapped 1:1 and don't get reassigned to higher slots in the body. + let params_node = self.visit_node(params.as_ref().clone(), &mut next_inner_subs, path); self.collect_parameter_slots(¶ms_node, &mut next_inner_subs); let body_node = self.visit_node(body.as_ref().clone(), &mut next_inner_subs, path); @@ -606,56 +477,48 @@ impl Optimizer { body: Rc::new(reindexed_body), positional_count: *positional_count, }, - node.ty, + node.ty.clone(), ) } BoundKind::DefLocal { - name, + ref name, slot, - value, - captured_by, + ref value, + ref captured_by, } => { - if !captured_by.is_empty() { - sub.captured_slots.insert(slot); - } - let value = Box::new(self.visit_node(*value, sub, path)); - if let BoundKind::Constant(val) = &value.kind { + if !captured_by.is_empty() { sub.captured_slots.insert(slot); } + let value_opt = Box::new(self.visit_node((**value).clone(), sub, path)); + if let BoundKind::Constant(val) = &value_opt.kind { sub.add_local(slot, val.clone()); } else { sub.locals.remove(&slot); } - if self.purity_of(&value) == Purity::Pure { - sub.pure_slots.insert(slot); - } else { - sub.pure_slots.remove(&slot); - } - let new_slot = sub.map_slot(slot); ( BoundKind::DefLocal { - name, + name: name.clone(), slot: new_slot, - value, - captured_by, + value: value_opt, + captured_by: captured_by.clone(), }, - node.ty, + node.ty.clone(), ) } BoundKind::DefGlobal { - name, + ref name, global_index, - value, + ref value, } => { - let value = Box::new(self.visit_node(*value, sub, path)); - if let BoundKind::Constant(val) = &value.kind + let value_opt = Box::new(self.visit_node((**value).clone(), sub, path)); + if let BoundKind::Constant(val) = &value_opt.kind && self.is_inlinable_value(val, Some(global_index)) { sub.add_global(global_index, val.clone()); } - let p = self.purity_of(&value); + let p = value_opt.ty.purity; if p > Purity::Impure && let Some(purity_rc) = &self.global_purity { @@ -663,11 +526,11 @@ impl Optimizer { } ( BoundKind::DefGlobal { - name, + name: name.clone(), global_index, - value, + value: value_opt, }, - node.ty, + node.ty.clone(), ) } @@ -676,44 +539,43 @@ impl Optimizer { .into_iter() .map(|e| self.visit_node(e, sub, path)) .collect(); - (BoundKind::Tuple { elements }, node.ty) + (BoundKind::Tuple { elements }, node.ty.clone()) } BoundKind::Record { fields } => { let fields = fields .into_iter() .map(|(k, v)| (self.visit_node(k, sub, path), self.visit_node(v, sub, path))) .collect(); - (BoundKind::Record { fields }, node.ty) + (BoundKind::Record { fields }, node.ty.clone()) } BoundKind::Expansion { - original_call, - bound_expanded, + ref original_call, + ref bound_expanded, } => { path.inlining_depth += 1; - let bound_expanded = Box::new(self.visit_node(*bound_expanded, sub, path)); + let bound_expanded = Box::new(self.visit_node((**bound_expanded).clone(), sub, path)); path.inlining_depth -= 1; ( BoundKind::Expansion { - original_call, + original_call: original_call.clone(), bound_expanded, }, - node.ty, + node.ty.clone(), ) } - k => (k, node.ty), + k => (k, node.ty.clone()), }; Node { identity: node.identity, kind: new_kind, - ty: new_ty, + ty: metrics, } } - fn collect_parameter_slots(&self, node: &TypedNode, sub: &mut SubstitutionMap) { + fn collect_parameter_slots(&self, node: &AnalyzedNode, sub: &mut SubstitutionMap) { match &node.kind { BoundKind::Parameter { slot, .. } => { - // Identity mapping for parameters to keep them in their reserved frame slots sub.slot_mapping.insert(*slot, *slot); sub.next_slot = sub.next_slot.max(*slot + 1); } @@ -736,9 +598,7 @@ impl Optimizer { | Value::DateTime(_) => true, Value::Object(obj) => { if let Some(closure) = obj.as_any().downcast_ref::() { - // A closure is only inlinable if it's not recursive - closure.upvalues.is_empty() - && !self.analysis.is_recursive.contains(&closure.function_node.identity) + closure.upvalues.is_empty() && !closure.function_node.ty.is_recursive } else { false } @@ -747,99 +607,48 @@ impl Optimizer { } } - fn purity_of(&self, node: &TypedNode) -> Purity { - if let Some(p) = self.analysis.purity.get(&node.identity) { - return *p; - } - - match &node.kind { - BoundKind::Constant(_) | BoundKind::Parameter { .. } | BoundKind::Nop => Purity::Pure, - // Defining a lambda is pure; only calling it may have side effects. - BoundKind::Lambda { .. } => Purity::Pure, - BoundKind::Get { addr, .. } => match addr { - Address::Global(idx) => { - if let Some(purity_rc) = &self.global_purity { - *purity_rc.borrow().get(idx).unwrap_or(&Purity::Impure) - } else { - Purity::Impure - } - } - _ => Purity::Pure, + fn make_constant_node(&self, val: Value, template: &AnalyzedNode) -> AnalyzedNode { + let ty = val.static_type(); + let typed_original = Rc::new(Node { + identity: template.identity.clone(), + kind: BoundKind::Constant(val.clone()), + ty: ty.clone(), + }); + Node { + identity: template.identity.clone(), + kind: BoundKind::Constant(val), + ty: NodeMetrics { + original: typed_original, + purity: Purity::Pure, + is_recursive: false, }, - BoundKind::Tuple { elements } => elements - .iter() - .map(|e| self.purity_of(e)) - .min() - .unwrap_or(Purity::Pure), - BoundKind::Record { fields } => fields - .iter() - .map(|(k, v)| self.purity_of(k).min(self.purity_of(v))) - .min() - .unwrap_or(Purity::Pure), - BoundKind::If { - cond, - then_br, - else_br, - } => { - let p = self.purity_of(cond).min(self.purity_of(then_br)); - if let Some(e) = else_br { - p.min(self.purity_of(e)) - } else { - p - } - } - BoundKind::Block { exprs } => exprs - .iter() - .map(|e| self.purity_of(e)) - .min() - .unwrap_or(Purity::Pure), - BoundKind::Expansion { bound_expanded, .. } => self.purity_of(bound_expanded), - BoundKind::DefLocal { value, .. } => self.purity_of(value), - BoundKind::Set { .. } => Purity::Impure, - BoundKind::Call { callee, args } => { - let callee_purity = match &callee.kind { - BoundKind::Lambda { body, .. } => self.purity_of(body), - BoundKind::Get { - addr: Address::Global(idx), - .. - } => { - if let Some(purity_rc) = &self.global_purity { - *purity_rc.borrow().get(idx).unwrap_or(&Purity::Impure) - } else { - Purity::Impure - } - } - BoundKind::Constant(Value::Function(f)) => f.purity, - BoundKind::Constant(Value::Object(obj)) => { - if let Some(closure) = obj.as_any().downcast_ref::() { - self.purity_of(&closure.function_node) - } else { - Purity::Impure - } - } - _ => Purity::Impure, - }; - callee_purity.min(self.purity_of(args)) - } - _ => Purity::Impure, } } - fn try_fold_pure(&self, callee: &TypedNode, args: &TypedNode) -> Option { - let temp_call = Node { - identity: callee.identity.clone(), - ty: StaticType::Any, - kind: BoundKind::Call { - callee: Box::new(callee.clone()), - args: Box::new(args.clone()), + fn make_nop_node(&self, template: &AnalyzedNode) -> AnalyzedNode { + let typed_original = Rc::new(Node { + identity: template.identity.clone(), + kind: BoundKind::Nop, + ty: StaticType::Void, + }); + Node { + identity: template.identity.clone(), + kind: BoundKind::Nop, + ty: NodeMetrics { + original: typed_original, + purity: Purity::Pure, + is_recursive: false, }, - }; - // Folding requires the function AND arguments to be truly Pure (deterministic). - if self.purity_of(&temp_call) < Purity::Pure { + } + } + + fn try_fold_pure(&self, callee: &AnalyzedNode, args: &AnalyzedNode) -> Option { + if callee.ty.purity < Purity::Pure || args.ty.purity < Purity::Pure { return None; } + let mut arg_nodes = Vec::new(); - self.flatten_typed_tuple(args, &mut arg_nodes); + self.flatten_tuple(args.clone(), &mut arg_nodes); let mut arg_values = Vec::with_capacity(arg_nodes.len()); for node in arg_nodes { if let BoundKind::Constant(val) = node.kind { @@ -860,57 +669,43 @@ impl Optimizer { Value::Function(f) => (f.func)(arg_values), _ => return None, }; - Some(Node { - identity: callee.identity.clone(), - ty: result.static_type(), - kind: BoundKind::Constant(result), - }) + Some(self.make_constant_node(result, callee)) } fn try_beta_reduce_with_sub( &self, - params: &TypedNode, - args: &TypedNode, - body: TypedNode, + params: &AnalyzedNode, + args: &AnalyzedNode, + body: AnalyzedNode, sub: &mut SubstitutionMap, path: &mut PathTracker, - ) -> Option { + ) -> Option { let mut arg_vals = Vec::new(); - self.flatten_typed_tuple(args, &mut arg_vals); + self.flatten_tuple(args.clone(), &mut arg_vals); let mut slot_index = 0; self.map_params_to_args(params, &arg_vals, &mut slot_index, sub); - // Safety: Only proceed if we mapped EXACTLY the number of arguments provided. - // And if we actually have parameters to bind (or if it's a 0-arg call). - if slot_index != arg_vals.len() { - return None; - } - if sub.locals.is_empty() - && sub.ast_locals.is_empty() - && sub.upvalues.is_empty() - && !arg_vals.is_empty() - { + if slot_index != arg_vals.len() { return None; } + if sub.locals.is_empty() && sub.ast_locals.is_empty() && sub.upvalues.is_empty() && !arg_vals.is_empty() { return None; } Some(self.visit_node(body, sub, path)) } - fn flatten_typed_tuple(&self, node: &TypedNode, into: &mut Vec) { + fn flatten_tuple(&self, node: AnalyzedNode, into: &mut Vec) { if let BoundKind::Tuple { elements } = &node.kind { - for el in elements { - self.flatten_typed_tuple(el, into); - } + for el in elements { self.flatten_tuple(el.clone(), into); } } else if !matches!(node.kind, BoundKind::Nop) { - into.push(node.clone()); + into.push(node); } } fn map_params_to_args( &self, - pattern: &TypedNode, - args: &[TypedNode], + pattern: &AnalyzedNode, + args: &[AnalyzedNode], offset: &mut usize, sub: &mut SubstitutionMap, ) { @@ -920,59 +715,41 @@ impl Optimizer { if let BoundKind::Constant(val) = &arg.kind { sub.add_local(*slot, val.clone()); } else if let BoundKind::Lambda { upvalues, .. } = &arg.kind { - if upvalues.is_empty() { - sub.add_ast_local(*slot, arg.clone()); - } - } else if let BoundKind::Get { - addr: Address::Global(_), - .. - } = &arg.kind - { + if upvalues.is_empty() { sub.add_ast_local(*slot, arg.clone()); } + } else if let BoundKind::Get { addr: Address::Global(_), .. } = &arg.kind { sub.add_ast_local(*slot, arg.clone()); } } - // Parameters in beta-reduction are also identity mapped if not inlined sub.slot_mapping.insert(*slot, *slot); sub.next_slot = sub.next_slot.max(*slot + 1); *offset += 1; } BoundKind::Tuple { elements } => { - for el in elements { - self.map_params_to_args(el, args, offset, sub); - } + for el in elements { self.map_params_to_args(el, args, offset, sub); } } _ => {} } } - fn collect_usage(&self, node: &TypedNode, info: &mut UsageInfo) { + fn collect_usage(&self, node: &AnalyzedNode, info: &mut UsageInfo) { match &node.kind { BoundKind::Constant(v) => { if let Value::Object(obj) = v && let Some(closure) = obj.as_any().downcast_ref::() { - info.used_identities - .insert(closure.function_node.identity.clone()); + info.used_identities.insert(closure.function_node.identity.clone()); self.collect_usage(&closure.function_node, info); } } BoundKind::Get { addr, .. } => match addr { - Address::Local(slot) => { - info.used_locals.insert(*slot); - } - Address::Global(idx) => { - info.used_globals.insert(*idx); - } + Address::Local(slot) => { info.used_locals.insert(*slot); } + Address::Global(idx) => { info.used_globals.insert(*idx); } _ => {} }, BoundKind::Set { addr, value } => { match addr { - Address::Local(slot) => { - info.assigned_locals.insert(*slot); - } - Address::Global(idx) => { - info.assigned_globals.insert(*idx); - } + Address::Local(slot) => { info.assigned_locals.insert(*slot); } + Address::Global(idx) => { info.assigned_globals.insert(*idx); } _ => {} } self.collect_usage(value, info); @@ -982,31 +759,17 @@ impl Optimizer { self.collect_usage(params, info); self.collect_usage(body, info); } - BoundKind::Block { exprs } => { - for e in exprs { - self.collect_usage(e, info); - } - } - BoundKind::If { - cond, - then_br, - else_br, - } => { + BoundKind::Block { exprs } => { for e in exprs { self.collect_usage(e, info); } } + BoundKind::If { cond, then_br, else_br } => { self.collect_usage(cond, info); self.collect_usage(then_br, info); - if let Some(e) = else_br { - self.collect_usage(e, info); - } + if let Some(e) = else_br { self.collect_usage(e, info); } } BoundKind::Call { callee, args } => { self.collect_usage(callee, info); self.collect_usage(args, info); } - BoundKind::Tuple { elements } => { - for e in elements { - self.collect_usage(e, info); - } - } + BoundKind::Tuple { elements } => { for e in elements { self.collect_usage(e, info); } } BoundKind::Record { fields } => { for (k, v) in fields { self.collect_usage(k, info); @@ -1024,18 +787,16 @@ impl Optimizer { } } -/// Helper for transitively inlining values and cleaning up capture lists. struct SubstitutionMap { locals: HashMap, upvalues: HashMap, globals: HashMap, - ast_locals: HashMap, + ast_locals: HashMap, slot_mapping: HashMap, assigned_locals: HashSet, assigned_globals: HashSet, next_slot: u32, used_slots: HashSet, - pure_slots: HashSet, used_globals: HashSet, captured_slots: HashSet, } @@ -1052,280 +813,80 @@ impl SubstitutionMap { assigned_globals: HashSet::new(), next_slot: 0, used_slots: HashSet::new(), - pure_slots: HashSet::new(), used_globals: HashSet::new(), captured_slots: HashSet::new(), } } - fn add_ast_local(&mut self, slot: u32, node: TypedNode) { - self.ast_locals.insert(slot, node); - } - + fn add_ast_local(&mut self, slot: u32, node: AnalyzedNode) { self.ast_locals.insert(slot, node); } fn map_slot(&mut self, old_slot: u32) -> u32 { - if let Some(&new_slot) = self.slot_mapping.get(&old_slot) { - return new_slot; - } + if let Some(&new_slot) = self.slot_mapping.get(&old_slot) { return new_slot; } let new_slot = self.next_slot; self.slot_mapping.insert(old_slot, new_slot); self.next_slot += 1; new_slot } + fn add_local(&mut self, slot: u32, val: Value) { self.locals.insert(slot, val); } + fn add_upvalue(&mut self, idx: u32, val: Value) { self.upvalues.insert(idx, val); } + fn add_global(&mut self, idx: u32, val: Value) { self.globals.insert(idx, val); } - fn add_local(&mut self, slot: u32, val: Value) { - self.locals.insert(slot, val); - } - - fn add_upvalue(&mut self, idx: u32, val: Value) { - self.upvalues.insert(idx, val); - } - - fn add_global(&mut self, idx: u32, val: Value) { - self.globals.insert(idx, val); - } - - fn reindex_upvalues(&self, node: TypedNode, mapping: &[Option]) -> TypedNode { - let (new_kind, new_ty) = match node.kind { - BoundKind::Get { - addr: Address::Upvalue(idx), - name, - } => { - if let Some(res) = mapping.get(idx as usize) { - match res { - Some(new_idx) => ( - BoundKind::Get { - addr: Address::Upvalue(*new_idx), - name, - }, - node.ty, - ), - None => ( - BoundKind::Get { - addr: Address::Upvalue(idx), - name, - }, - node.ty, - ), - } + fn reindex_upvalues(&self, node: AnalyzedNode, mapping: &[Option]) -> AnalyzedNode { + let (new_kind, metrics) = match node.kind { + BoundKind::Get { addr: Address::Upvalue(idx), name } => { + if let Some(res) = mapping.get(idx as usize) && let Some(new_idx) = res { + (BoundKind::Get { addr: Address::Upvalue(*new_idx), name }, node.ty.clone()) } else { - ( - BoundKind::Get { - addr: Address::Upvalue(idx), - name, - }, - node.ty, - ) + (BoundKind::Get { addr: Address::Upvalue(idx), name }, node.ty.clone()) } } - BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - } => { + BoundKind::Lambda { params, upvalues, body, positional_count } => { let mut next_upvalues = Vec::new(); for addr in upvalues { - if let Address::Upvalue(idx) = addr - && let Some(res) = mapping.get(idx as usize) - { - if let Some(new_idx) = res { - next_upvalues.push(Address::Upvalue(*new_idx)); - } + if let Address::Upvalue(idx) = addr && let Some(res) = mapping.get(idx as usize) && let Some(new_idx) = res { + next_upvalues.push(Address::Upvalue(*new_idx)); continue; } next_upvalues.push(addr); } - ( - BoundKind::Lambda { - params, - upvalues: next_upvalues, - body, - positional_count, - }, - node.ty, - ) + (BoundKind::Lambda { params, upvalues: next_upvalues, body, positional_count }, node.ty.clone()) } - BoundKind::If { - cond, - then_br, - else_br, - } => { + BoundKind::If { cond, then_br, else_br } => { let cond = Box::new(self.reindex_upvalues(*cond, mapping)); let then_br = Box::new(self.reindex_upvalues(*then_br, mapping)); let else_br = else_br.map(|e| Box::new(self.reindex_upvalues(*e, mapping))); - ( - BoundKind::If { - cond, - then_br, - else_br, - }, - node.ty, - ) + (BoundKind::If { cond, then_br, else_br }, node.ty.clone()) } BoundKind::Block { exprs } => { - let exprs = exprs - .into_iter() - .map(|e| self.reindex_upvalues(e, mapping)) - .collect(); - (BoundKind::Block { exprs }, node.ty) + let exprs = exprs.into_iter().map(|e| self.reindex_upvalues(e, mapping)).collect(); + (BoundKind::Block { exprs }, node.ty.clone()) } 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 }, node.ty) + (BoundKind::Call { callee, args }, node.ty.clone()) } - BoundKind::DefLocal { - name, - slot, - value, - captured_by, - } => { + BoundKind::DefLocal { name, slot, value, captured_by } => { let value = Box::new(self.reindex_upvalues(*value, mapping)); - ( - BoundKind::DefLocal { - name, - slot, - value, - captured_by, - }, - node.ty, - ) + (BoundKind::DefLocal { name, slot, value, captured_by }, node.ty.clone()) } - BoundKind::DefGlobal { - name, - global_index, - value, - } => { + BoundKind::DefGlobal { name, global_index, value } => { let value = Box::new(self.reindex_upvalues(*value, mapping)); - ( - BoundKind::DefGlobal { - name, - global_index, - value, - }, - node.ty, - ) + (BoundKind::DefGlobal { name, global_index, value }, node.ty.clone()) } BoundKind::Set { addr, value } => { let value = Box::new(self.reindex_upvalues(*value, mapping)); - (BoundKind::Set { addr, value }, node.ty) + (BoundKind::Set { addr, value }, node.ty.clone()) } BoundKind::Tuple { elements } => { - let elements = elements - .into_iter() - .map(|e| self.reindex_upvalues(e, mapping)) - .collect(); - (BoundKind::Tuple { elements }, node.ty) + let elements = elements.into_iter().map(|e| self.reindex_upvalues(e, mapping)).collect(); + (BoundKind::Tuple { elements }, node.ty.clone()) } BoundKind::Record { fields } => { - let fields = fields - .into_iter() - .map(|(k, v)| { - ( - self.reindex_upvalues(k, mapping), - self.reindex_upvalues(v, mapping), - ) - }) - .collect(); - (BoundKind::Record { fields }, node.ty) + let fields = fields.into_iter().map(|(k, v)| (self.reindex_upvalues(k, mapping), self.reindex_upvalues(v, mapping))).collect(); + (BoundKind::Record { fields }, node.ty.clone()) } - k => (k, node.ty), + k => (k, node.ty.clone()), }; - Node { - identity: node.identity, - kind: new_kind, - ty: new_ty, - } - } -} - -#[cfg(test)] -mod tests { - use crate::ast::environment::Environment; - - fn get_optimized_dump(source: &str, enabled: bool) -> String { - let mut env = Environment::new(); - env.optimization = enabled; - env.dump_ast(source) - .expect("Compilation failed during test") - } - - #[test] - fn test_opt_folding() { - let dump = get_optimized_dump("(+ 10 20)", true); - insta::assert_snapshot!(dump); - } - - #[test] - fn test_opt_folding_complex() { - let dump_float = get_optimized_dump("(+ 1.5 2.5)", true); - assert!(dump_float.contains("Constant: 4")); - let dump_text = get_optimized_dump("(+ \"hello \" \"world\")", true); - assert!(dump_text.contains("Constant: \"hello world\"")); - let dump_date = get_optimized_dump("(date \"2023-01-01\")", true); - assert!(dump_date.contains("Constant: #2023-01-01 00:00:00#")); - let dump_cmp = get_optimized_dump("(> 10 5)", true); - assert!(dump_cmp.contains("Constant: true")); - } - - #[test] - fn test_opt_local_inlining() { - let source = "(fn [] (do (def x 10) (+ x 5)))"; - let dump = get_optimized_dump(source, true); - insta::assert_snapshot!(dump); - } - - #[test] - fn test_opt_dce_unused() { - let source = "(fn [] (do (def x 10) 42))"; - let dump = get_optimized_dump(source, true); - insta::assert_snapshot!(dump); - } - - #[test] - fn test_opt_assignment_safety() { - let source = "(fn [] (do (def x 10) (assign x 20) x))"; - let dump = get_optimized_dump(source, true); - insta::assert_snapshot!(dump); - } - - #[test] - fn test_opt_lambda_cracking() { - let source = "(fn [] (do (def x 10) (fn [] x)))"; - let dump = get_optimized_dump(source, true); - insta::assert_snapshot!(dump); - } - - #[test] - fn test_opt_global_unused_dce() { - let source = "(do (def f (fn [x] x)) 42)"; - let dump = get_optimized_dump(source, true); - assert!( - !dump.contains("DefGlobal"), - "Unused global helper 'f' should be optimized away. Dump: \n{}", - dump - ); - assert!(dump.contains("Constant: 42")); - } - - #[test] - fn test_opt_local_recursion_safety() { - // (fn [] (do (def f (fn [x] (f x))) (f 1))) - // Hier sollte der Optimizer 'f' nicht unendlich oft in sich selbst inlinen. - let source = "(fn [] (do (def f (fn [x] (f x))) (f 1)))"; - let dump = get_optimized_dump(source, true); - - // Der Dump sollte 'Call' auf 'f' (oder Slot-Get) noch enthalten, - // statt 5 Ebenen tief entfaltet zu sein. - assert!( - dump.contains("Call"), - "Recursive call should remain as a call. Dump: \n{}", - dump - ); - assert!( - !dump.contains("- Capturer:"), - "Should not be over-optimized. Dump: \n{}", - dump - ); + Node { identity: node.identity.clone(), kind: new_kind, ty: metrics } } } diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs index b54f9a0..1ab53fb 100644 --- a/src/ast/compiler/specializer.rs +++ b/src/ast/compiler/specializer.rs @@ -1,7 +1,6 @@ -use crate::ast::compiler::analyzer::Analysis; -use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, TypedNode}; +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, BoundNode, NodeMetrics}; use crate::ast::nodes::Node; -use crate::ast::types::{Signature, StaticType, Value}; +use crate::ast::types::{Purity, Signature, StaticType, Value}; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -17,13 +16,13 @@ pub type RtlLookupFunc = Rc Option<(Value, Static pub trait FunctionRegistry { fn resolve(&self, addr: Address) -> Option; + fn resolve_analyzed(&self, _addr: Address) -> Option { None } } pub type MonoCache = HashMap; pub struct Specializer { pub cache: Rc>, - pub analysis: Analysis, registry: Option>, compiler: Option, rtl_lookup: Option, @@ -35,279 +34,193 @@ impl Specializer { compiler: Option, rtl_lookup: Option, cache: Option>>, - analysis: Analysis, ) -> Self { Self { cache: cache.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))), - analysis, registry, compiler, rtl_lookup, } } - pub fn specialize(&self, node: TypedNode) -> TypedNode { + pub fn specialize(&self, node: AnalyzedNode) -> AnalyzedNode { self.visit_node(node) } - fn visit_node(&self, node: TypedNode) -> TypedNode { - let (new_kind, new_ty) = match node.kind { + fn visit_node(&self, node: AnalyzedNode) -> AnalyzedNode { + let (new_kind, metrics) = match node.kind { BoundKind::Call { callee, args } => { - 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.original.ty.clone()); + + let new_metrics = node.ty.clone(); ( BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args), }, - ret_ty, + new_metrics, ) } - // Recursive traversal for other nodes - BoundKind::If { - cond, - then_br, - else_br, - } => { + BoundKind::If { cond, then_br, else_br } => { let cond = Box::new(self.visit_node(*cond)); let then_br = Box::new(self.visit_node(*then_br)); let else_br = else_br.map(|e| Box::new(self.visit_node(*e))); - ( - BoundKind::If { - cond, - then_br, - else_br, - }, - node.ty, - ) + (BoundKind::If { cond, then_br, else_br }, node.ty.clone()) } BoundKind::Block { exprs } => { let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect(); - (BoundKind::Block { exprs }, node.ty) + (BoundKind::Block { exprs }, node.ty.clone()) } - BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - } => { + BoundKind::Lambda { params, upvalues, body, positional_count } => { let params = Rc::new(self.visit_node(params.as_ref().clone())); let body = Rc::new(self.visit_node((*body).clone())); - ( - BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - }, - node.ty, - ) + (BoundKind::Lambda { params, upvalues, body, positional_count }, node.ty.clone()) } - BoundKind::DefLocal { - name, - slot, - value, - captured_by, - } => { + BoundKind::DefLocal { name, slot, value, captured_by } => { let value = Box::new(self.visit_node(*value)); - ( - BoundKind::DefLocal { - name, - slot, - value, - captured_by, - }, - node.ty, - ) + (BoundKind::DefLocal { name, slot, value, captured_by }, node.ty.clone()) } - BoundKind::DefGlobal { - name, - global_index, - value, - } => { + BoundKind::DefGlobal { name, global_index, value } => { let value = Box::new(self.visit_node(*value)); - ( - BoundKind::DefGlobal { - name, - global_index, - value, - }, - node.ty, - ) + (BoundKind::DefGlobal { name, global_index, value }, node.ty.clone()) } BoundKind::Set { addr, value } => { let value = Box::new(self.visit_node(*value)); - (BoundKind::Set { addr, value }, node.ty) + (BoundKind::Set { addr, value }, node.ty.clone()) } BoundKind::Tuple { elements } => { let elements = elements.into_iter().map(|e| self.visit_node(e)).collect(); - (BoundKind::Tuple { elements }, node.ty) + (BoundKind::Tuple { elements }, node.ty.clone()) } BoundKind::Record { fields } => { let fields = fields .into_iter() .map(|(k, v)| (self.visit_node(k), self.visit_node(v))) .collect(); - (BoundKind::Record { fields }, node.ty) + (BoundKind::Record { fields }, node.ty.clone()) } - BoundKind::Expansion { - original_call, - bound_expanded, - } => { + BoundKind::Expansion { original_call, bound_expanded } => { let bound_expanded = Box::new(self.visit_node(*bound_expanded)); - ( - BoundKind::Expansion { - original_call, - bound_expanded, - }, - node.ty, - ) + (BoundKind::Expansion { original_call, bound_expanded }, node.ty.clone()) } - - // Leaf nodes or uninteresting nodes - k => (k, node.ty), + k => (k, node.ty.clone()), }; Node { identity: node.identity, kind: new_kind, - ty: new_ty, + ty: metrics, } } fn specialize_call_logic( &self, - callee: TypedNode, - args: TypedNode, + callee: AnalyzedNode, + args: AnalyzedNode, original_ty: StaticType, - ) -> (TypedNode, TypedNode, StaticType) { - // 1. Specialize children first + ) -> (AnalyzedNode, AnalyzedNode, StaticType) { let new_callee = self.visit_node(callee); let new_args = self.visit_node(args); - // 2. Check if this call is a candidate (Callee is Get(Address)) let address = if let BoundKind::Get { addr, .. } = &new_callee.kind { *addr } else { - // Not a direct call to a named function/variable return (new_callee, new_args, original_ty); }; - // 3. Check if all argument types are statically known - let arg_types: Vec = if let StaticType::Tuple(elements) = &new_args.ty { + let arg_types: Vec = if let StaticType::Tuple(elements) = &new_args.ty.original.ty { elements.clone() } else { - vec![new_args.ty.clone()] + vec![new_args.ty.original.ty.clone()] }; if arg_types.iter().any(|t| matches!(t, StaticType::Any)) { - // Cannot specialize with unknown types return (new_callee, new_args, original_ty); } - // --- Optimization Candidate --- let key = MonoCacheKey { address, arg_types: arg_types.clone(), }; - // 4. Check Cache if let Some((val, ret_ty)) = self.cache.borrow().get(&key) { - // Cache Hit! Replace Callee with Constant(Function) - let specialized_callee = Node { - identity: new_callee.identity.clone(), - kind: BoundKind::Constant(val.clone()), - ty: StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(arg_types), - ret: ret_ty.clone(), - })), - }; + let specialized_callee = self.make_constant_node(val.clone(), StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(arg_types), + ret: ret_ty.clone(), + })), &new_callee); return (specialized_callee, new_args, ret_ty.clone()); } - // 5. Check RTL (Host Functions) if let Some(rtl_lookup) = &self.rtl_lookup && let BoundKind::Get { name, .. } = &new_callee.kind && let Some((val, ret_ty)) = rtl_lookup(&name.name, &arg_types) { - // Cache Hit (RTL) - self.cache - .borrow_mut() - .insert(key.clone(), (val.clone(), ret_ty.clone())); - - let specialized_callee = Node { - identity: new_callee.identity.clone(), - kind: BoundKind::Constant(val.clone()), - ty: StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(arg_types), - ret: ret_ty.clone(), - })), - }; + self.cache.borrow_mut().insert(key.clone(), (val.clone(), ret_ty.clone())); + let specialized_callee = self.make_constant_node(val.clone(), StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(arg_types), + ret: ret_ty.clone(), + })), &new_callee); return (specialized_callee, new_args, ret_ty); } - // 6. Resolve Function Definition - if let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) { - // Check for recursion from pre-pass. - if self.analysis.is_recursive.contains(&func_node.identity) { - return (new_callee, new_args, original_ty); - } - - // Check constraints (no closures with state) - if let BoundKind::Lambda { upvalues, .. } = &func_node.kind { - if !upvalues.is_empty() { - return (new_callee, new_args, original_ty); - } - } else { - return (new_callee, new_args, original_ty); - } - - // 7. Compile Specialization (User Code) - if let Some(compiler) = &self.compiler { - match compiler(func_node, &arg_types) { - Ok((compiled_val, ret_ty)) => { - let res_val: Value = compiled_val; - let res_ty: StaticType = ret_ty; - - // Store in cache - self.cache - .borrow_mut() - .insert(key, (res_val.clone(), res_ty.clone())); - - // PERFORMANCE: Flatten the argument tuple to match the specialized signature. - let flat_elements = self.flatten_tuple(new_args.clone()); - let flat_types = flat_elements.iter().map(|e| e.ty.clone()).collect(); - let flattened_args = Node { - identity: new_args.identity.clone(), - kind: BoundKind::Tuple { - elements: flat_elements, - }, - ty: StaticType::Tuple(flat_types), - }; - - let specialized_callee = Node { - identity: new_callee.identity.clone(), - kind: BoundKind::Constant(res_val), - ty: StaticType::Function(Box::new(Signature { - params: flattened_args.ty.clone(), - ret: res_ty.clone(), - })), - }; - return (specialized_callee, flattened_args, res_ty); - } - Err(_) => { - // Fallback on error - } - } - } + if let Some(registry) = &self.registry + && let Some(func_node) = registry.resolve_analyzed(address) + && func_node.ty.is_recursive + { + return (new_callee, new_args, original_ty); + } + + if let Some(compiler) = &self.compiler + && let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) + && let Ok((compiled_val, ret_ty)) = compiler(func_node, &arg_types) + { + self.cache.borrow_mut().insert(key, (compiled_val.clone(), ret_ty.clone())); + let flat_elements = self.flatten_tuple(new_args.clone()); + let flat_types = flat_elements.iter().map(|e| e.ty.original.ty.clone()).collect(); + let flattened_args = Node { + identity: new_args.identity.clone(), + kind: BoundKind::Tuple { elements: flat_elements }, + ty: NodeMetrics { + original: Rc::new(Node { + identity: new_args.identity.clone(), + kind: BoundKind::Tuple { elements: vec![] }, + ty: StaticType::Tuple(flat_types), + }), + purity: new_args.ty.purity, + is_recursive: new_args.ty.is_recursive, + }, + }; + + let specialized_callee = self.make_constant_node(compiled_val, StaticType::Function(Box::new(Signature { + params: flattened_args.ty.original.ty.clone(), + ret: ret_ty.clone(), + })), &new_callee); + return (specialized_callee, flattened_args, ret_ty); } - // Fallback: Dynamic Call (new_callee, new_args, original_ty) } - fn flatten_tuple(&self, node: TypedNode) -> Vec { + fn make_constant_node(&self, val: Value, ty: StaticType, template: &AnalyzedNode) -> AnalyzedNode { + let typed_original = Rc::new(Node { + identity: template.identity.clone(), + kind: BoundKind::Constant(val.clone()), + ty: ty.clone(), + }); + Node { + identity: template.identity.clone(), + kind: BoundKind::Constant(val), + ty: NodeMetrics { + original: typed_original, + purity: Purity::Pure, + is_recursive: false, + }, + } + } + + fn flatten_tuple(&self, node: AnalyzedNode) -> Vec { match node.kind { BoundKind::Tuple { elements } => { let mut flat = Vec::new(); diff --git a/src/ast/compiler/tco.rs b/src/ast/compiler/tco.rs index a3a213b..43752b5 100644 --- a/src/ast/compiler/tco.rs +++ b/src/ast/compiler/tco.rs @@ -1,16 +1,15 @@ -use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode}; +use crate::ast::compiler::bound_nodes::{AnalyzedNode, BoundKind}; use crate::ast::nodes::Node; use crate::ast::types::StaticType; use std::rc::Rc; - use std::fmt::Debug; #[derive(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, + /// The analyzed node, containing metrics and a link to the original TypedNode. + pub original: Rc, } impl Debug for RuntimeMetadata { @@ -22,18 +21,18 @@ impl Debug for RuntimeMetadata { } } -/// The ExecNode is the AST used by the VM. It carries TCO flags and links to source. +/// The ExecNode is the AST used by the VM. It carries TCO flags and links to metrics. pub type ExecNode = Node, RuntimeMetadata>; pub struct TCO; impl TCO { - /// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions. - pub fn optimize(node: TypedNode) -> ExecNode { + /// Lowers an AnalyzedNode to an ExecNode and marks tail positions. + pub fn optimize(node: AnalyzedNode) -> ExecNode { Self::transform(Rc::new(node), true) } - fn transform(node_rc: Rc, is_tail_position: bool) -> ExecNode { + 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 { @@ -158,7 +157,7 @@ impl TCO { identity: node.identity.clone(), kind: new_kind, ty: RuntimeMetadata { - ty: node.ty.clone(), + ty: node.ty.original.ty.clone(), is_tail: is_tail_position, original: node_rc, }, diff --git a/src/ast/environment.rs b/src/ast/environment.rs index da91fc8..946306e 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -1,4 +1,4 @@ -use crate::ast::compiler::analyzer::{Analysis, Analyzer}; +use crate::ast::compiler::analyzer::Analyzer; use crate::ast::compiler::binder::Binder; use crate::ast::compiler::{TypeChecker, TypedNode}; use crate::ast::nodes::{Node, Symbol, UntypedKind}; @@ -8,7 +8,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode}; +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, BoundNode}; use crate::ast::compiler::dumper::Dumper; use crate::ast::compiler::lambda_collector::LambdaCollector; use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; @@ -26,15 +26,15 @@ pub struct Environment { pub global_values: Rc>>, pub prng: Rc>, pub function_registry: Rc>>, - pub typed_function_registry: Rc>>, + pub typed_function_registry: Rc>>, pub monomorph_cache: Rc>, pub debug_mode: bool, pub optimization: bool, - pub last_analysis: RefCell, } struct EnvFunctionRegistry { registry: Rc>>, + analyzed_registry: Rc>>, } impl FunctionRegistry for EnvFunctionRegistry { @@ -45,9 +45,15 @@ impl FunctionRegistry for EnvFunctionRegistry { None } } + fn resolve_analyzed(&self, addr: Address) -> Option { + if let Address::Global(idx) = addr { + self.analyzed_registry.borrow().get(&idx).cloned() + } else { + None + } + } } -/// Evaluator used during macro expansion to allow compile-time logic. struct RuntimeMacroEvaluator { global_names: Rc>>, global_types: Rc>>, @@ -60,19 +66,16 @@ impl MacroEvaluator for RuntimeMacroEvaluator { node: &Node, bindings: &HashMap, Node>, ) -> Result { - // 1. Check if it's a simple parameter substitution if let UntypedKind::Identifier(sym) = &node.kind && let Some(arg_node) = bindings.get(&sym.name) { return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc)); } - // 2. Full evaluation for complex compile-time expressions let bound_ast = Binder::bind_root(self.global_names.clone(), node)?; - let checker = TypeChecker::new(self.global_types.clone()); let typed_ast = checker.check(bound_ast, &[])?; - let exec_ast = TCO::optimize(typed_ast); + let exec_ast = TCO::optimize(Analyzer::analyze(&typed_ast, &HashMap::new())); // Minimal analysis for macro eval let mut vm = VM::new(self.global_values.clone()); vm.run(&exec_ast) @@ -98,7 +101,6 @@ impl Environment { monomorph_cache: Rc::new(RefCell::new(HashMap::new())), debug_mode: false, optimization: true, - last_analysis: RefCell::new(Analysis::default()), }; env.register_stdlib(); env @@ -135,7 +137,6 @@ impl Environment { values.push(Value::Function(func)); } - /// Utility to register a native function from a closure. pub fn register_native_fn( &self, name: &str, @@ -162,12 +163,11 @@ impl Environment { let idx = values.len() as u32; names.insert(Symbol::from(name), idx); types.insert(idx, ty); - purity.insert(idx, Purity::Pure); // Constants are always pure + purity.insert(idx, Purity::Pure); values.push(val); } fn register_stdlib(&self) { - // Register all standard library functions via RTL module rtl::register(self); } @@ -177,79 +177,54 @@ impl Environment { Ok(Dumper::dump(&linked)) } - /// Frontend: Parse -> Expand Macros -> Bind -> Type Check pub fn compile(&self, source: &str) -> Result { - // 1. Parse let mut parser = Parser::new(source)?; let untyped_ast = parser.parse_expression()?; - // 2. Check for trailing tokens if !parser.at_eof() { - return Err( - "Unexpected trailing expressions in script. Use (do ...) for sequences." - .to_string(), - ); + return Err("Unexpected trailing expressions in script.".to_string()); } - // 3. Expand Macros let expanded_ast = self.get_expander().expand(untyped_ast)?; - - // 4. Bind let bound_ast = Binder::bind_root(self.global_names.clone(), &expanded_ast)?; - - // 5. Collect Lambdas (Populate the registry with untyped templates) LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut()); - // 6. Type Check let checker = TypeChecker::new(self.global_types.clone()); let typed_ast = checker.check(bound_ast, &[])?; - // 7. Collect Typed Lambdas - LambdaCollector::collect(&typed_ast, &mut self.typed_function_registry.borrow_mut()); - - // 8. Analyze (Purity, Recursion) - let analysis = Analyzer::analyze(&typed_ast, &self.global_purity.borrow()); - *self.last_analysis.borrow_mut() = analysis; - Ok(typed_ast) } - /// Backend Phase 1: Optimization (TCO, etc.) and Lowering pub fn link(&self, node: TypedNode) -> ExecNode { - // 1. Specialize (Always performed for correctness) - let specialized = self.specialize_node(node); + // 1. Analyze + let analyzed = Analyzer::analyze(&node, &self.global_purity.borrow()); + + // 2. Collect Analyzed Lambdas + LambdaCollector::collect(&analyzed, &mut self.typed_function_registry.borrow_mut()); - // 2. Optimize (Level 1: Cracking, Level 2: Collapsing) + // 3. Specialize + let specialized = self.specialize_node(analyzed); + + // 4. Optimize let optimizer = Optimizer::new(self.optimization) .with_globals(self.global_values.clone()) .with_purity(self.global_purity.clone()) - .with_registry(self.typed_function_registry.clone()) - .with_analysis(self.last_analysis.borrow().clone()); + .with_registry(self.typed_function_registry.clone()); let optimized = optimizer.optimize(specialized); - // 3. TCO (Always performed, converts to ExecNode) + // 5. TCO TCO::optimize(optimized) } - /// Backend Phase 2: Packaging into an invokable NativeFunction pub fn instantiate(&self, node: ExecNode) -> Rc { let global_values = self.global_values.clone(); - - // OPTIMIZATION: Fast path for top-level Lambdas. - // If the script root is a Lambda with no captures (root functions usually have none), - // we can pre-create the closure and call it directly. - if let BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - } = &node.kind + if let BoundKind::Lambda { params, upvalues, body, positional_count } = &node.kind && upvalues.is_empty() { let closure = Rc::new(crate::ast::vm::Closure::new( - params.ty.original.clone(), - body.ty.original.clone(), - body.clone(), + params.ty.original.clone(), + body.ty.original.clone(), + body.clone(), vec![], *positional_count, )); @@ -267,20 +242,16 @@ impl Environment { }); } - // FALLBACK: Generic script body (Block, If, etc.) let exec_node = Rc::new(node); Rc::new(crate::ast::types::NativeFunction { purity: Purity::Impure, func: Rc::new(move |args| { let mut vm = VM::new(global_values.clone()); - - // 1. Execute the main body (Block, etc.) let res = match vm.run(&exec_node) { Ok(v) => v, Err(e) => panic!("Myc Runtime Error: {}", e), }; - // 2. Auto-apply: If the script returned a closure, we apply arguments to it. let mut final_res = res; if let Value::Object(obj) = &final_res && let Some(closure) = obj.as_any().downcast_ref::() @@ -290,78 +261,63 @@ impl Environment { Err(e) => panic!("Myc Runtime Error (Closure): {}", e), }; } - - // 3. Resolve Tail Calls vm.resolve_tail_calls(final_res) }), }) } - fn specialize_node(&self, node: TypedNode) -> TypedNode { + fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode { let registry = Rc::new(EnvFunctionRegistry { registry: self.function_registry.clone(), + analyzed_registry: self.typed_function_registry.clone(), }); let rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); - - let func_reg = self.function_registry.clone(); + let typed_reg = self.typed_function_registry.clone(); + let untyped_reg = self.function_registry.clone(); let mono_cache = self.monomorph_cache.clone(); let global_values = self.global_values.clone(); let global_types = self.global_types.clone(); let global_purity = self.global_purity.clone(); let optimization = self.optimization; - let analysis = self.last_analysis.borrow().clone(); - let compiler_analysis = analysis.clone(); let compiler = Rc::new( move |func_template: BoundNode, arg_types: &[StaticType]| -> Result<(Value, StaticType), String> { - // 1. Re-TypeCheck the template with concrete argument types let checker = TypeChecker::new(global_types.clone()); let retyped_ast = checker.check(func_template, arg_types)?; - // 2. Specialize (Recursive) + let analyzed = Analyzer::analyze(&retyped_ast, &global_purity.borrow()); + let sub_registry = Rc::new(EnvFunctionRegistry { - registry: func_reg.clone(), + registry: untyped_reg.clone(), + analyzed_registry: typed_reg.clone(), }); - let sub_rtl_lookup = - Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); + let sub_rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); let sub_specializer = Specializer::new( Some(sub_registry), None, Some(sub_rtl_lookup), Some(mono_cache.clone()), - compiler_analysis.clone(), ); - let specialized_ast = sub_specializer.specialize(retyped_ast); + let specialized_ast = sub_specializer.specialize(analyzed); - // 3. Optimize (Phase 2: Cracking & Folding) let optimizer = Optimizer::new(optimization) .with_globals(global_values.clone()) - .with_purity(global_purity.clone()) - .with_analysis(compiler_analysis.clone()); + .with_purity(global_purity.clone()); let optimized_ast = optimizer.optimize(specialized_ast); - // 4. TCO (converts to ExecNode) let tco_ast = TCO::optimize(optimized_ast); - - // 5. Compile to Value (VM) let mut vm = VM::new(global_values.clone()); let compiled_val = match vm.run(&tco_ast) { Ok(v) => v, Err(e) => return Err(format!("VM Error during specialization: {}", e)), }; - // 6. Determine correct return type from the newly inferred function signature - let ret_type = if let StaticType::Function(sig) = &tco_ast.ty.ty { - sig.ret.clone() - } else { - StaticType::Any - }; - + let ret_type = tco_ast.ty.ty.clone(); Ok((compiled_val, ret_type)) }, ); @@ -371,7 +327,6 @@ impl Environment { Some(compiler), Some(rtl_lookup), Some(self.monomorph_cache.clone()), - analysis, ); specializer.specialize(node) @@ -380,9 +335,7 @@ impl Environment { pub fn run_script(&self, source: &str) -> Result { if self.debug_mode { let (res, logs) = self.run_debug(source)?; - for line in logs { - println!("{}", line); - } + for line in logs { println!("{}", line); } res } else { let compiled = self.compile(source)?; @@ -395,33 +348,25 @@ impl Environment { pub fn run_debug(&self, source: &str) -> Result<(Result, Vec), String> { let compiled = self.compile(source)?; let linked = self.link(compiled); - - // Execute with TracingObserver let mut vm = VM::new(self.global_values.clone()); let mut observer = TracingObserver::new(); let mut result = vm.run_with_observer(&mut observer, &linked); - // If result is a closure (script entry), execute the body too if let Ok(Value::Object(obj)) = &result && let Some(closure) = obj.as_any().downcast_ref::() { result = vm.run_with_observer(&mut observer, &closure.exec_node); } - // Resolve top-level tail calls while let Ok(Value::TailCallRequest(payload)) = result { let (next_obj, next_args) = *payload; if let Some(closure) = next_obj.as_any().downcast_ref::() { result = vm.run_with_args_observed(&mut observer, closure, next_args); } else { - result = Err(format!( - "Tail call target is not a closure: {}", - next_obj.type_name() - )); + result = Err(format!("Tail call target is not a closure: {}", next_obj.type_name())); break; } } - Ok((result, observer.logs)) } } diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 9300d7a..343c867 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -1,4 +1,4 @@ -use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind}; use crate::ast::compiler::tco::ExecNode; use crate::ast::nodes::Node; use crate::ast::types::{Object, Value}; @@ -8,8 +8,11 @@ use std::rc::Rc; #[derive(Debug, Clone)] pub struct Closure { - pub parameter_node: Rc, - pub function_node: Rc, + /// The analyzed parameter pattern. + pub parameter_node: Rc, + /// The analyzed body (before TCO). + pub function_node: Rc, + /// The executable node (after TCO). pub exec_node: Rc, pub upvalues: Vec>>, pub positional_count: Option, @@ -18,8 +21,8 @@ pub struct Closure { impl Closure { #[inline] pub fn new( - params: Rc, - body: Rc, + params: Rc, + body: Rc, exec: Rc, upvalues: Vec>>, positional_count: Option, @@ -85,11 +88,14 @@ impl VMObserver for TracingObserver { const ACTIVE: bool = true; fn before_eval(&mut self, _vm: &VM, node: &ExecNode) { let pad = self.pad(); + let metrics = &node.ty.original.ty; self.logs.push(format!( - "{}{} [{}]: {{", + "{}{} [{} | P:{:?}{}]: {{", pad, node.kind.display_name(), - node.ty.ty + node.ty.ty, + metrics.purity, + if metrics.is_recursive { " | REC" } else { "" } )); self.indent += 1; } @@ -166,8 +172,13 @@ macro_rules! dispatch_eval { BoundKind::Lambda { params, upvalues, body, positional_count } => { let mut captured = Vec::with_capacity(upvalues.len()); for addr in upvalues { captured.push($self.capture_upvalue(*addr)?); } - // CRITICAL FIX: body.clone() is O(1), body.as_ref().clone() was O(N)! - let closure = Closure::new(params.ty.original.clone(), body.ty.original.clone(), body.clone(), captured, *positional_count); + let closure = Closure::new( + params.ty.original.clone(), + body.ty.original.clone(), + body.clone(), + captured, + *positional_count + ); Ok(Value::Object(Rc::new(closure))) }, BoundKind::Call { callee, args } => { @@ -361,7 +372,6 @@ impl VM { dispatch_eval!(self, node, eval) } - /// Resolves potential tail call requests iteratively until a final value is reached. pub fn resolve_tail_calls(&mut self, mut result: Value) -> Value { while let Value::TailCallRequest(payload) = result { let (next_obj, next_args) = *payload; @@ -622,110 +632,3 @@ impl VM { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::compiler::tco::TCO; - use crate::ast::nodes::{Node, Symbol}; - use crate::ast::types::{NodeIdentity, SourceLocation, StaticType}; - - fn make_dummy_identity() -> Rc { - Rc::new(NodeIdentity { - location: SourceLocation { line: 0, col: 0 }, - }) - } - - #[test] - fn test_capture_boxing_modification() { - let id = make_dummy_identity(); - let lambda_body = Node { - identity: id.clone(), - ty: StaticType::Void, - kind: BoundKind::Set { - addr: Address::Upvalue(0), - value: Box::new(Node { - identity: id.clone(), - ty: StaticType::Int, - kind: BoundKind::Constant(Value::Int(20)), - }), - }, - }; - let root = Node { - identity: id.clone(), - ty: StaticType::Int, - kind: BoundKind::Block { - exprs: vec![ - Node { - identity: id.clone(), - ty: StaticType::Int, - kind: BoundKind::Set { - addr: Address::Local(0), - value: Box::new(Node { - identity: id.clone(), - ty: StaticType::Int, - kind: BoundKind::Constant(Value::Int(10)), - }), - }, - }, - Node { - identity: id.clone(), - ty: StaticType::Any, - kind: BoundKind::Set { - addr: Address::Local(1), - value: Box::new(Node { - identity: id.clone(), - ty: StaticType::Any, - kind: BoundKind::Lambda { - params: Rc::new(Node { - identity: id.clone(), - ty: StaticType::Tuple(vec![]), - kind: BoundKind::Tuple { elements: vec![] }, - }), - upvalues: vec![Address::Local(0)], - body: Rc::new(lambda_body), - positional_count: Some(0), - }, - }), - }, - }, - Node { - identity: id.clone(), - ty: StaticType::Void, - kind: BoundKind::Call { - callee: Box::new(Node { - identity: id.clone(), - ty: StaticType::Any, - kind: BoundKind::Get { - addr: Address::Local(1), - name: Symbol::from("f"), - }, - }), - args: Box::new(Node { - identity: id.clone(), - ty: StaticType::Tuple(vec![]), - kind: BoundKind::Tuple { elements: vec![] }, - }), - }, - }, - Node { - identity: id.clone(), - ty: StaticType::Int, - kind: BoundKind::Get { - addr: Address::Local(0), - name: Symbol::from("x"), - }, - }, - ], - }, - }; - let globals = Rc::new(RefCell::new(Vec::new())); - let mut vm = VM::new(globals); - let exec_root = TCO::optimize(root); - let result = vm.run(&exec_root); - match result { - Ok(Value::Int(val)) => assert_eq!(val, 20), - _ => panic!("Expected Int(20)"), - } - } -}