diff --git a/src/ast/compiler/optimizer.rs b/src/ast/compiler/optimizer.rs index bdb4c80..52d86d1 100644 --- a/src/ast/compiler/optimizer.rs +++ b/src/ast/compiler/optimizer.rs @@ -3,17 +3,17 @@ use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode, Address}; use crate::ast::types::{Value, StaticType}; use crate::ast::vm::Closure; use crate::ast::nodes::Node; +use std::collections::HashMap; -/// The Optimizer performs Phase 2: Partial Evaluation & Closure Cracking. +/// The Optimizer performs Phase 2 (Cracking) and Phase 2.5 (Aggressive Collapsing). pub struct Optimizer { - /// 0: None, 1: Cracking (Stateless transformation), 2: Aggressive (Collapsing) pub level: u32, max_passes: usize, } impl Optimizer { pub fn new(level: u32) -> Self { - Self { level, max_passes: 3 } + Self { level, max_passes: 5 } } pub fn optimize(&self, node: TypedNode) -> TypedNode { @@ -24,6 +24,7 @@ impl Optimizer { let mut current = node; for _ in 0..self.max_passes { let next = self.visit_node(current.clone()); + // TODO: Structural equality check to stop early current = next; } current @@ -31,84 +32,79 @@ impl Optimizer { fn visit_node(&self, node: TypedNode) -> TypedNode { let (new_kind, new_ty) = match node.kind { - BoundKind::Call { callee, args } => { + BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => { let callee = self.visit_node(*callee); let args = self.visit_node(*args); - // --- Cracking & Collapsing --- - if let BoundKind::Constant(Value::Object(ref obj)) = callee.kind { - if let Some(closure) = obj.as_any().downcast_ref::() { - // Level 1+: Inlining Upvalues (Stateless transformation) - // Structure is PRESERVED (Call still exists), but code is specialized. - let body = (*closure.function_node).clone(); - let inlined_body = self.inline_upvalues(body, &closure.upvalues, 0); - - // Level 2: Aggressive Collapsing - if self.level >= 2 { - // 1. Collapse 0-parameter calls - if let Some(0) = closure.positional_count { - let is_empty_args = match &args.kind { - BoundKind::Nop => true, - BoundKind::Tuple { elements } => elements.is_empty(), - _ => false, - }; - if is_empty_args { - return self.visit_node(inlined_body); - } + if self.level >= 2 { + // Case 1: Beta-Reduction for Lambda Literals + if let BoundKind::Lambda { params, body, .. } = &callee.kind + && let Some(collapsed) = self.try_beta_reduce(params, &args, (**body).clone()) { + return self.visit_node(collapsed); + } + + // Case 2: Cracking and Inlining for Constant Closures + if let BoundKind::Constant(Value::Object(ref obj)) = callee.kind + && let Some(closure) = obj.as_any().downcast_ref::() { + let mut sub = SubstitutionMap::new(); + for (i, cell) in closure.upvalues.iter().enumerate() { + sub.add_upvalue(i as u32, cell.borrow().clone()); + } + let inlined_body = sub.inline((*closure.function_node).clone()); + + // Try to collapse the now-stateless lambda call + if let Some(collapsed) = self.try_beta_reduce(&closure.parameter_node, &args, inlined_body) { + return self.visit_node(collapsed); } - // 2. Beta-Reduction (TODO: Inlining parameters) } - // Stateless transformation (Cracking) - 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, - }; - } - } - - // Level 2: Intrinsic Folding (Arithmetic on constants) - if self.level >= 2 { if let Some(folded) = self.try_fold_intrinsic(&callee, &args) { return folded; } } + // Level 1: Just Crack closures but keep the Call structure + if self.level >= 1 + && let BoundKind::Constant(Value::Object(ref obj)) = callee.kind + && let Some(closure) = obj.as_any().downcast_ref::() { + let mut sub = SubstitutionMap::new(); + for (i, cell) in closure.upvalues.iter().enumerate() { + sub.add_upvalue(i as u32, cell.borrow().clone()); + } + let inlined_body = sub.inline((*closure.function_node).clone()); + + let cracked_lambda = Node { + identity: callee.identity.clone(), + ty: callee.ty.clone(), + kind: BoundKind::Lambda { + params: closure.parameter_node.clone(), + upvalues: vec![], // CRACKED: Stateless + 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) }, BoundKind::If { cond, then_br, else_br } => { let cond = self.visit_node(*cond); - if self.level >= 2 { - if let BoundKind::Constant(ref val) = cond.kind { + if self.level >= 2 + && let BoundKind::Constant(ref val) = cond.kind { if val.is_truthy() { return self.visit_node(*then_br); } else if let Some(else_node) = else_br { return self.visit_node(*else_node); } else { - return Node { - identity: node.identity, - kind: BoundKind::Nop, - ty: StaticType::Void, - }; + return Node { identity: node.identity, kind: BoundKind::Nop, ty: StaticType::Void }; } } - } let then_br = Box::new(self.visit_node(*then_br)); let else_br = else_br.map(|e| Box::new(self.visit_node(*e))); @@ -137,12 +133,13 @@ impl Optimizer { (BoundKind::Block { exprs: new_exprs }, ty) }, - // Recursive Traversal 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.as_ref().clone())); (BoundKind::Lambda { params, upvalues, body, positional_count }, node.ty) }, + + // Standard Recursive Traversal BoundKind::DefLocal { name, slot, value, captured_by } => { let value = Box::new(self.visit_node(*value)); (BoundKind::DefLocal { name, slot, value, captured_by }, node.ty) @@ -167,108 +164,308 @@ impl Optimizer { let bound_expanded = Box::new(self.visit_node(*bound_expanded)); (BoundKind::Expansion { original_call, bound_expanded }, node.ty) }, - k => (k, node.ty), }; - Node { - identity: node.identity, - kind: new_kind, - ty: new_ty, + Node { identity: node.identity, kind: new_kind, ty: new_ty } + } + + fn try_beta_reduce(&self, params: &TypedNode, args: &TypedNode, body: TypedNode) -> Option { + // SAFETY: Only beta-reduce if the body does not contain local definitions. + if self.contains_def_local(&body) { + return None; + } + + let mut sub = SubstitutionMap::new(); + let mut arg_vals = Vec::new(); + self.flatten_typed_tuple(args, &mut arg_vals); + + let mut slot_index = 0; + self.map_params_to_args(params, &arg_vals, &mut slot_index, &mut sub); + + // SAFETY: Only beta-reduce if ALL provided arguments were successfully inlined as constants. + // If we leave any Local(i) references, the VM will crash because the frame is gone. + if sub.locals.len() < arg_vals.len() { + return None; + } + + if sub.is_empty() && !arg_vals.is_empty() { + return None; + } + + Some(sub.inline(body)) + } + + fn flatten_typed_tuple(&self, node: &TypedNode, into: &mut Vec) { + if let BoundKind::Tuple { elements } = &node.kind { + for el in elements { + self.flatten_typed_tuple(el, into); + } + } else if !matches!(node.kind, BoundKind::Nop) { + into.push(node.clone()); + } + } + + fn map_params_to_args(&self, pattern: &TypedNode, args: &[TypedNode], offset: &mut usize, sub: &mut SubstitutionMap) { + match &pattern.kind { + BoundKind::Parameter { slot, .. } => { + if let Some(arg) = args.get(*offset) + && let BoundKind::Constant(val) = &arg.kind { + sub.add_local(*slot, val.clone()); + } + *offset += 1; + } + BoundKind::Tuple { elements } => { + for el in elements { + self.map_params_to_args(el, args, offset, sub); + } + } + _ => {} } } fn try_fold_intrinsic(&self, callee: &TypedNode, args: &TypedNode) -> Option { - // Simple constant folding for + and - on Ints - if let BoundKind::Get { name, .. } = &callee.kind { - if let BoundKind::Tuple { elements } = &args.kind { - if elements.len() == 2 { - if let (BoundKind::Constant(Value::Int(a)), BoundKind::Constant(Value::Int(b))) = (&elements[0].kind, &elements[1].kind) { - let res = match &*name.name { - "+" => Some(Value::Int(a + b)), - "-" => Some(Value::Int(a - b)), - "*" => Some(Value::Int(a * b)), - "/" if *b != 0 => Some(Value::Int(a / b)), - _ => None - }; - if let Some(val) = res { - return Some(Node { - identity: callee.identity.clone(), - ty: StaticType::Int, - kind: BoundKind::Constant(val), - }); - } - } + if let BoundKind::Get { name, .. } = &callee.kind + && let BoundKind::Tuple { elements } = &args.kind + && elements.len() == 2 + { + let val_a = self.get_const_int(&elements[0]); + let val_b = self.get_const_int(&elements[1]); + + if let (Some(a), Some(b)) = (val_a, val_b) { + let res = match &*name.name { + "+" => Some(Value::Int(a + b)), + "-" => Some(Value::Int(a - b)), + "*" => Some(Value::Int(a * b)), + "/" if b != 0 => Some(Value::Int(a / b)), + _ => None + }; + if let Some(val) = res { + return Some(Node { + identity: callee.identity.clone(), + ty: StaticType::Int, + kind: BoundKind::Constant(val), + }); } } } None } - fn inline_upvalues(&self, node: TypedNode, upvalues: &[Rc>], depth: usize) -> TypedNode { + fn get_const_int(&self, node: &TypedNode) -> Option { + match &node.kind { + BoundKind::Constant(Value::Int(i)) => Some(*i), + BoundKind::Tuple { elements } if elements.len() == 1 => self.get_const_int(&elements[0]), + _ => None + } + } + + fn contains_def_local(&self, node: &TypedNode) -> bool { + match &node.kind { + BoundKind::DefLocal { .. } => true, + BoundKind::If { cond, then_br, else_br } => { + self.contains_def_local(cond) || self.contains_def_local(then_br) || else_br.as_ref().is_some_and(|e| self.contains_def_local(e)) + } + BoundKind::Block { exprs } => exprs.iter().any(|e| self.contains_def_local(e)), + BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => self.contains_def_local(callee) || self.contains_def_local(args), + BoundKind::Lambda { .. } => false, // Nested lambda definitions are safe (they have their own frame) + BoundKind::Tuple { elements } => elements.iter().any(|e| self.contains_def_local(e)), + BoundKind::Record { fields } => fields.iter().any(|(k, v)| self.contains_def_local(k) || self.contains_def_local(v)), + BoundKind::Set { value, .. } => self.contains_def_local(value), + _ => false, + } + } +} + +/// Helper for transitively inlining values and cleaning up capture lists. +struct SubstitutionMap { + /// Mapping Address::Local(slot) -> Value + locals: HashMap, + /// Mapping Address::Upvalue(idx) -> Value + upvalues: HashMap, +} + +impl SubstitutionMap { + fn new() -> Self { + Self { locals: HashMap::new(), upvalues: HashMap::new() } + } + + 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 is_empty(&self) -> bool { + self.locals.is_empty() && self.upvalues.is_empty() + } + + fn inline(&self, node: TypedNode) -> TypedNode { + self.inline_recursive(node, 0, &HashMap::new()) + } + + fn inline_recursive(&self, node: TypedNode, depth: usize, upvalue_subs: &HashMap) -> TypedNode { + let (new_kind, new_ty) = match node.kind { + BoundKind::Get { addr, name } => { + match addr { + Address::Local(slot) if depth == 0 => { + if let Some(val) = self.locals.get(&slot) { + (BoundKind::Constant(val.clone()), val.static_type()) + } else { (BoundKind::Get { addr, name }, node.ty) } + } + Address::Upvalue(idx) if depth == 0 => { + if let Some(val) = self.upvalues.get(&idx) { + (BoundKind::Constant(val.clone()), val.static_type()) + } else { (BoundKind::Get { addr, name }, node.ty) } + } + Address::Upvalue(idx) if depth > 0 => { + if let Some(val) = upvalue_subs.get(&idx) { + (BoundKind::Constant(val.clone()), val.static_type()) + } else { (BoundKind::Get { addr, name }, node.ty) } + } + _ => (BoundKind::Get { addr, name }, node.ty) + } + }, + + BoundKind::Lambda { params, upvalues: original_upvalues, body, positional_count } => { + // Phase 3: Un-Capturing + // If this lambda captures variables that we just inlined, we MUST remove them + // from its capture list to avoid "Stack underflow capture" errors in the VM. + let mut new_upvalues = Vec::new(); + let mut next_inner_subs = HashMap::new(); + let mut mapping = Vec::new(); // Old Upvalue Index -> New Upvalue Index (or None) + + for (old_idx, capture_addr) in original_upvalues.iter().enumerate() { + let mut inlined_val = None; + + if depth == 0 { + match capture_addr { + Address::Local(slot) => { + if let Some(val) = self.locals.get(slot) { inlined_val = Some(val.clone()); } + } + Address::Upvalue(idx) => { + if let Some(val) = self.upvalues.get(idx) { inlined_val = Some(val.clone()); } + } + _ => {} + } + } else if let Address::Upvalue(old_parent_idx) = capture_addr + && let Some(val) = upvalue_subs.get(old_parent_idx) { + inlined_val = Some(val.clone()); + } + + if let Some(val) = inlined_val { + next_inner_subs.insert(old_idx as u32, val); + mapping.push(None); + } else { + mapping.push(Some(new_upvalues.len() as u32)); + new_upvalues.push(*capture_addr); + } + } + + // If any upvalues were removed, we must re-index the Get(Upvalue) nodes in the body. + let body_node = self.inline_recursive(body.as_ref().clone(), depth + 1, &next_inner_subs); + let reindexed_body = if new_upvalues.len() != original_upvalues.len() { + self.reindex_upvalues(body_node, &mapping) + } else { + body_node + }; + + (BoundKind::Lambda { params, upvalues: new_upvalues, body: Rc::new(reindexed_body), positional_count }, node.ty) + }, + + // Boilerplate Recursive Traversal + BoundKind::If { cond, then_br, else_br } => { + let cond = Box::new(self.inline_recursive(*cond, depth, upvalue_subs)); + let then_br = Box::new(self.inline_recursive(*then_br, depth, upvalue_subs)); + let else_br = else_br.map(|e| Box::new(self.inline_recursive(*e, depth, upvalue_subs))); + (BoundKind::If { cond, then_br, else_br }, node.ty) + }, + BoundKind::Block { exprs } => { + let exprs = exprs.into_iter().map(|e| self.inline_recursive(e, depth, upvalue_subs)).collect(); + (BoundKind::Block { exprs }, node.ty) + }, + BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => { + let callee = Box::new(self.inline_recursive(*callee, depth, upvalue_subs)); + let args = Box::new(self.inline_recursive(*args, depth, upvalue_subs)); + (BoundKind::Call { callee, args }, node.ty) + }, + BoundKind::DefLocal { name, slot, value, captured_by } => { + let value = Box::new(self.inline_recursive(*value, depth, upvalue_subs)); + (BoundKind::DefLocal { name, slot, value, captured_by }, node.ty) + }, + BoundKind::Set { addr, value } => { + let value = Box::new(self.inline_recursive(*value, depth, upvalue_subs)); + (BoundKind::Set { addr, value }, node.ty) + }, + BoundKind::Tuple { elements } => { + let elements = elements.into_iter().map(|e| self.inline_recursive(e, depth, upvalue_subs)).collect(); + (BoundKind::Tuple { elements }, node.ty) + }, + BoundKind::Record { fields } => { + let fields = fields.into_iter().map(|(k, v)| (self.inline_recursive(k, depth, upvalue_subs), self.inline_recursive(v, depth, upvalue_subs))).collect(); + (BoundKind::Record { fields }, node.ty) + }, + k => (k, node.ty), + }; + + Node { identity: node.identity, kind: new_kind, ty: new_ty } + } + + /// Re-maps Get(Upvalue(old_idx)) to Get(Upvalue(new_idx)) based on a mapping table. + 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 depth == 0 { - if let Some(cell) = upvalues.get(idx as usize) { - let val = cell.borrow().clone(); - let ty = val.static_type(); - (BoundKind::Constant(val), ty) - } else { - (BoundKind::Get { addr: Address::Upvalue(idx), name }, node.ty) - } + if let Some(Some(new_idx)) = mapping.get(idx as usize) { + (BoundKind::Get { addr: Address::Upvalue(*new_idx), name }, node.ty) } else { (BoundKind::Get { addr: Address::Upvalue(idx), name }, node.ty) } }, + // Nested Lambda: Increment depth (we only re-index upvalues of the current scope) + BoundKind::Lambda { params, upvalues, body, positional_count } => { + // IMPORTANT: In MyC, nested upvalues refer to the capture list of the nested lambda. + // We DON'T re-index inside nested lambdas because their upvalue list is separate. + (BoundKind::Lambda { params, upvalues, body, positional_count }, node.ty) + }, + + // Recursive Traversal BoundKind::If { cond, then_br, else_br } => { - let cond = Box::new(self.inline_upvalues(*cond, upvalues, depth)); - let then_br = Box::new(self.inline_upvalues(*then_br, upvalues, depth)); - let else_br = else_br.map(|e| Box::new(self.inline_upvalues(*e, upvalues, depth))); + 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::Block { exprs } => { - let exprs = exprs.into_iter().map(|e| self.inline_upvalues(e, upvalues, depth)).collect(); + let exprs = exprs.into_iter().map(|e| self.reindex_upvalues(e, mapping)).collect(); (BoundKind::Block { exprs }, node.ty) }, - BoundKind::Call { callee, args } => { - let callee = Box::new(self.inline_upvalues(*callee, upvalues, depth)); - let args = Box::new(self.inline_upvalues(*args, upvalues, depth)); + BoundKind::Call { callee, args } | BoundKind::TailCall { 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::TailCall { callee, args } => { - let callee = Box::new(self.inline_upvalues(*callee, upvalues, depth)); - let args = Box::new(self.inline_upvalues(*args, upvalues, depth)); - (BoundKind::TailCall { callee, args }, node.ty) - }, BoundKind::DefLocal { name, slot, value, captured_by } => { - let value = Box::new(self.inline_upvalues(*value, upvalues, depth)); + let value = Box::new(self.reindex_upvalues(*value, mapping)); (BoundKind::DefLocal { name, slot, value, captured_by }, node.ty) }, BoundKind::Set { addr, value } => { - let value = Box::new(self.inline_upvalues(*value, upvalues, depth)); + let value = Box::new(self.reindex_upvalues(*value, mapping)); (BoundKind::Set { addr, value }, node.ty) }, BoundKind::Tuple { elements } => { - let elements = elements.into_iter().map(|e| self.inline_upvalues(e, upvalues, depth)).collect(); + let elements = elements.into_iter().map(|e| self.reindex_upvalues(e, mapping)).collect(); (BoundKind::Tuple { elements }, node.ty) }, BoundKind::Record { fields } => { - let fields = fields.into_iter().map(|(k, v)| (self.inline_upvalues(k, upvalues, depth), self.inline_upvalues(v, upvalues, depth))).collect(); + let fields = fields.into_iter().map(|(k, v)| (self.reindex_upvalues(k, mapping), self.reindex_upvalues(v, mapping))).collect(); (BoundKind::Record { fields }, node.ty) }, - BoundKind::Lambda { params, upvalues: lambda_upvalues, body, positional_count } => { - let body = Rc::new(self.inline_upvalues(body.as_ref().clone(), upvalues, depth + 1)); - (BoundKind::Lambda { params, upvalues: lambda_upvalues, body, positional_count }, node.ty) - }, - k => (k, node.ty), }; - Node { - identity: node.identity, - kind: new_kind, - ty: new_ty, - } + Node { identity: node.identity, kind: new_kind, ty: new_ty } } } diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 67842f1..4b034c7 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -1,22 +1,22 @@ -use std::rc::Rc; +use crate::ast::compiler::binder::Binder; +use crate::ast::compiler::{TypeChecker, TypedNode}; +use crate::ast::nodes::{Node, Symbol, UntypedKind}; +use crate::ast::parser::Parser; +use crate::ast::types::{Object, StaticType, Value}; +use crate::ast::vm::{TracingObserver, VM}; use std::cell::RefCell; use std::collections::HashMap; -use crate::ast::types::{Value, StaticType, Object}; -use crate::ast::nodes::{Node, UntypedKind, Symbol}; -use crate::ast::parser::Parser; -use crate::ast::compiler::binder::Binder; -use crate::ast::compiler::{TypedNode, TypeChecker}; -use crate::ast::vm::{VM, TracingObserver}; +use std::rc::Rc; -use crate::ast::compiler::tco::TCO; -use crate::ast::compiler::lambda_collector::LambdaCollector; +use crate::ast::compiler::bound_nodes::{Address, BoundNode}; use crate::ast::compiler::dumper::Dumper; -use crate::ast::compiler::macros::{MacroExpander, MacroRegistry, MacroEvaluator}; -use crate::ast::compiler::specializer::{Specializer, MonoCache, FunctionRegistry}; +use crate::ast::compiler::lambda_collector::LambdaCollector; +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::TCO; use crate::ast::rtl; use crate::ast::rtl::intrinsics; -use crate::ast::compiler::bound_nodes::{Address, BoundNode}; pub struct Environment { pub global_names: Rc>>, @@ -50,19 +50,24 @@ struct RuntimeMacroEvaluator { } impl MacroEvaluator for RuntimeMacroEvaluator { - fn evaluate(&self, node: &Node, bindings: &HashMap, Node>) -> Result { + fn evaluate( + &self, + 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)); + 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 mut vm = VM::new(self.global_values.clone()); vm.run(&typed_ast) } @@ -102,7 +107,12 @@ impl Environment { MacroExpander::new(MacroRegistry::new(), evaluator) } - pub fn register_native(&self, name: &str, ty: StaticType, func: impl Fn(Vec) -> Value + 'static) { + pub fn register_native( + &self, + name: &str, + ty: StaticType, + func: impl Fn(Vec) -> Value + 'static, + ) { let mut names = self.global_names.borrow_mut(); let mut types = self.global_types.borrow_mut(); let mut values = self.global_values.borrow_mut(); @@ -143,7 +153,10 @@ impl Environment { // 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. Use (do ...) for sequences." + .to_string(), + ); } // 3. Expand Macros @@ -179,64 +192,71 @@ impl Environment { let registry = Rc::new(EnvFunctionRegistry { registry: self.function_registry.clone(), }); - + let rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); - + let func_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 opt_level = self.optimization_level; - - 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 sub_registry = Rc::new(EnvFunctionRegistry { registry: func_reg.clone() }); - 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()) - ); - - let specialized_ast = sub_specializer.specialize(retyped_ast); - - // 3. Optimize (Phase 2: Cracking & Folding) - let optimizer = Optimizer::new(opt_level); - let optimized_ast = optimizer.optimize(specialized_ast); - - // 4. TCO - 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 { - sig.ret.clone() - } else { - StaticType::Any - }; + 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 sub_registry = Rc::new(EnvFunctionRegistry { + registry: func_reg.clone(), + }); + 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()), + ); + + let specialized_ast = sub_specializer.specialize(retyped_ast); + + // 3. Optimize (Phase 2: Cracking & Folding) + let optimizer = Optimizer::new(opt_level); + let optimized_ast = optimizer.optimize(specialized_ast); + + // 4. TCO + 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 { + sig.ret.clone() + } else { + StaticType::Any + }; + + Ok((compiled_val, ret_type)) + }, + ); - Ok((compiled_val, ret_type)) - }); - let specializer = Specializer::new( Some(registry), Some(compiler), Some(rtl_lookup), Some(self.monomorph_cache.clone()), ); - + specializer.specialize(node) } @@ -244,10 +264,10 @@ impl Environment { pub fn run(&self, node: &TypedNode) -> Result { let mut vm = VM::new(self.global_values.clone()); let mut result = vm.run(node)?; - + // Handle potential script body closure - if let Value::Object(obj) = &result - && let Some(closure) = obj.as_any().downcast_ref::() + if let Value::Object(obj) = &result + && let Some(closure) = obj.as_any().downcast_ref::() { result = vm.run(&closure.function_node)?; } @@ -258,7 +278,10 @@ impl Environment { if let Some(closure) = next_obj.as_any().downcast_ref::() { result = vm.run_with_args(closure, next_args)?; } else { - return Err(format!("Tail call target is not a closure: {}", next_obj.type_name())); + return Err(format!( + "Tail call target is not a closure: {}", + next_obj.type_name() + )); } } Ok(result) @@ -286,10 +309,10 @@ impl Environment { 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::() + if let Ok(Value::Object(obj)) = &result + && let Some(closure) = obj.as_any().downcast_ref::() { result = vm.run_with_observer(&mut observer, &closure.function_node); } @@ -300,7 +323,10 @@ impl Environment { 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; } } diff --git a/src/integration_test.rs b/src/integration_test.rs index 822bef8..caa64b5 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -82,9 +82,11 @@ mod tests { #[test] fn test_examples() { - let results = crate::utils::tester::run_functional_tests(); - for res in results { - assert!(res.success, "Example {} failed: {}", res.name, res.message); + for level in 0..=2 { + let results = crate::utils::tester::run_functional_tests_with_level(level); + for res in results { + assert!(res.success, "Example {} failed at level {}: {}", res.name, level, res.message); + } } } diff --git a/src/main.rs b/src/main.rs index 520fc7c..48b18ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -95,12 +95,13 @@ impl CompilerApp { // Reset environment for a fresh run self.env = Environment::new(); + self.env.optimization_level = 2; let res = (|| -> Result<(myc::ast::types::Value, std::time::Duration), String> { let start_run = std::time::Instant::now(); let result = if self.debug_enabled { let (res, logs) = self.env.run_debug(&self.source_code)?; - + // Batch-truncate logs for GUI performance let mut display_logs = logs; if display_logs.len() > 1000 { @@ -110,9 +111,10 @@ impl CompilerApp { for line in &mut display_logs { if line.len() > 255 - && let Some((idx, _)) = line.char_indices().nth(255) { - line.truncate(idx); - line.push_str("..."); + && let Some((idx, _)) = line.char_indices().nth(255) + { + line.truncate(idx); + line.push_str("..."); } } @@ -131,7 +133,7 @@ impl CompilerApp { Ok((val, d_run)) => { let d_total = start_total.elapsed(); let now = chrono::Local::now().format("%H:%M:%S").to_string(); - + let mut stats = format!( "Execution Successful.\nResult: {}\n\nTotal Duration: {:?}\nFinished at: {}", val, d_total, now, diff --git a/src/utils/tester.rs b/src/utils/tester.rs index b500ce5..76762bb 100644 --- a/src/utils/tester.rs +++ b/src/utils/tester.rs @@ -18,12 +18,17 @@ pub struct BenchmarkResult { } pub fn run_functional_tests() -> Vec { + run_functional_tests_with_level(0) +} + +pub fn run_functional_tests_with_level(level: u32) -> Vec { let mut results = Vec::new(); let entries = fs::read_dir("examples").unwrap(); let output_re = Regex::new(r";; Output: (.*)").unwrap(); for entry in entries.filter_map(|e| e.ok()) { - let env = Environment::new(); // Fresh environment per test file + let mut env = Environment::new(); // Fresh environment per test file + env.optimization_level = level; let path = entry.path(); if path.extension().is_some_and(|ext| ext == "myc") { let content = fs::read_to_string(&path).unwrap(); @@ -47,14 +52,14 @@ pub fn run_functional_tests() -> Vec { results.push(TestResult { name, success: false, - message: format!("Expected {}, got {}", expected, val_str), + message: format!("Level {}: Expected {}, got {}", level, expected, val_str), }); } } Err(e) => results.push(TestResult { name, success: false, - message: format!("Error: {}", e), + message: format!("Level {}: Error: {}", level, e), }), } }