From 1331aabbe159bf5d6a8eca655023f49b4ae575cd Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Thu, 19 Feb 2026 23:54:53 +0100 Subject: [PATCH] Update benchmarks and adjust tests This commit updates benchmark values in several example files to reflect minor performance variations. It also includes adjustments to integration and unit tests to align with recent changes in the type checking and VM logic, specifically concerning closures and TCO handling. --- examples/closure.myc | 2 +- examples/data.myc | 2 +- examples/extreme_capture.myc | 2 +- examples/macro_power.myc | 2 +- examples/macro_splice.myc | 2 +- examples/macro_unless.myc | 2 +- examples/tak.myc | 11 +++--- examples/tco_test.myc | 2 +- src/ast/compiler/specializer.rs | 14 ++++---- src/ast/compiler/type_checker.rs | 62 +++++++++++++++++++------------- src/ast/environment.rs | 23 +++++++++--- src/ast/vm.rs | 32 +++++++++++++++-- src/integration_test.rs | 23 +++--------- 13 files changed, 108 insertions(+), 71 deletions(-) diff --git a/examples/closure.myc b/examples/closure.myc index bf02503..b44f791 100644 --- a/examples/closure.myc +++ b/examples/closure.myc @@ -1,5 +1,5 @@ ;; Closure Scope Test -;; Benchmark: 600ns +;; Benchmark: 700ns ;; Output: 15 (do (def make-adder (fn [x] diff --git a/examples/data.myc b/examples/data.myc index 916fcd2..c97be19 100644 --- a/examples/data.myc +++ b/examples/data.myc @@ -1,5 +1,5 @@ ;; Complex Data Structure Test -;; Benchmark: 49.4us +;; Benchmark: 51.8us ;; Output: {:input 10, :name "Fibonacci", :output 55} (do (def fib (fn [n] diff --git a/examples/extreme_capture.myc b/examples/extreme_capture.myc index 9b80bfc..e86b19e 100644 --- a/examples/extreme_capture.myc +++ b/examples/extreme_capture.myc @@ -1,4 +1,4 @@ -;; Benchmark: 1.5us +;; Benchmark: 1.6us ;; Output: 36 (do ;; Excessive capture test diff --git a/examples/macro_power.myc b/examples/macro_power.myc index c1c85b3..0b0b025 100644 --- a/examples/macro_power.myc +++ b/examples/macro_power.myc @@ -1,4 +1,4 @@ -;; Benchmark: 300ns +;; Benchmark: 400ns ;; Nested Macro and Arithmetic example ;; Output: 81 diff --git a/examples/macro_splice.myc b/examples/macro_splice.myc index a6f4a28..9a2a931 100644 --- a/examples/macro_splice.myc +++ b/examples/macro_splice.myc @@ -1,4 +1,4 @@ -;; Benchmark: 200ns +;; Benchmark: 300ns ;; Macro Splicing example ;; Demonstrates unrolling a list into another list. ;; Output: [0 1 2 3 4] diff --git a/examples/macro_unless.myc b/examples/macro_unless.myc index dcd7f78..63d8e0e 100644 --- a/examples/macro_unless.myc +++ b/examples/macro_unless.myc @@ -1,4 +1,4 @@ -;; Benchmark: 100ns +;; Benchmark: 200ns ;; Classic "unless" macro example ;; Demonstrates simple template substitution. ;; Output: 42 diff --git a/examples/tak.myc b/examples/tak.myc index b46a2c7..568f61e 100644 --- a/examples/tak.myc +++ b/examples/tak.myc @@ -1,17 +1,16 @@ -;; Benchmark: 602.1us ;; Takeuchi Function Benchmark -;; Benchmark: ~150ms (Specialized) vs ~1.5s (Dynamic) ;; heavily recursive, benefits significantly from specialization of integer arithmetic ;; and direct function calls (skipping dynamic dispatch). -;; Output: 7 - +;; Output: 4 + ;; Benchmark: 468.7us + (do (def tak (fn [x y z] (if (<= x y) - y + z (tak (tak (- x 1) y z) (tak (- y 1) z x) (tak (- z 1) x y))))) - (tak 10 6 3) + (tak 12 8 4) ) diff --git a/examples/tco_test.myc b/examples/tco_test.myc index 2ac1006..02e98f0 100644 --- a/examples/tco_test.myc +++ b/examples/tco_test.myc @@ -1,4 +1,4 @@ -;; Benchmark: 2.7ms +;; Benchmark: 2.8ms ;; Output: "done" (do (def count_down (fn [n] diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs index c2ae00c..4d64fda 100644 --- a/src/ast/compiler/specializer.rs +++ b/src/ast/compiler/specializer.rs @@ -232,7 +232,7 @@ mod tests { // Mock Registry struct MockRegistry { - functions: HashMap, + functions: HashMap, } impl MockRegistry { @@ -240,13 +240,13 @@ mod tests { Self { functions: HashMap::new() } } - fn register(&mut self, addr: Address, node: TypedNode) { + fn register(&mut self, addr: Address, node: BoundNode) { self.functions.insert(addr, node); } } impl FunctionRegistry for MockRegistry { - fn resolve(&self, addr: Address) -> Option { + fn resolve(&self, addr: Address) -> Option { self.functions.get(&addr).cloned() } } @@ -259,19 +259,19 @@ mod tests { let name = Symbol::from("test_func"); // Def: (fn [x] x) -- generic identity - let func_node = TypedNode { + let func_node = BoundNode { identity: make_identity(), kind: BoundKind::Lambda { param_count: 1, upvalues: vec![], - body: Rc::new(TypedNode { identity: make_identity(), kind: BoundKind::Nop, ty: StaticType::Void }) + body: Rc::new(BoundNode { identity: make_identity(), kind: BoundKind::Nop, ty: () }) }, - ty: StaticType::Void + ty: () }; registry.register(addr, func_node); // Setup Compiler Mock - let compiler: CompileFunc = Rc::new(|_node: TypedNode, _args: &[StaticType]| -> Result<(Value, StaticType), String> { + let compiler: CompileFunc = Rc::new(|_node: BoundNode, _args: &[StaticType]| -> Result<(Value, StaticType), String> { // Return a specialized "compiled" value Ok((Value::Int(12345), StaticType::Int)) }); diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 59a42b7..02216d9 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -288,42 +288,54 @@ mod tests { env.compile(source).unwrap() } + fn get_ret_type(node: &TypedNode) -> StaticType { + if let StaticType::Function(sig) = &node.ty { + sig.ret.clone() + } else { + node.ty.clone() + } + } + #[test] fn test_inference_constants() { - assert_eq!(check_source("10").ty, StaticType::Int); - assert_eq!(check_source("10.5").ty, StaticType::Float); - assert_eq!(check_source("true").ty, StaticType::Bool); - assert_eq!(check_source("\"hello\"").ty, StaticType::Text); + assert_eq!(get_ret_type(&check_source("10")), StaticType::Int); + assert_eq!(get_ret_type(&check_source("10.5")), StaticType::Float); + assert_eq!(get_ret_type(&check_source("true")), StaticType::Bool); + assert_eq!(get_ret_type(&check_source("\"hello\"")), StaticType::Text); } #[test] fn test_inference_variable_propagation() { // (do (def x 10) x) -> The last 'x' must be Int let typed = check_source("(do (def x 10) x)"); - if let BoundKind::Block { exprs } = typed.kind { - let last_expr = exprs.last().unwrap(); - assert_eq!(last_expr.ty, StaticType::Int, "Variable 'x' should be inferred as Int"); - } else { panic!("Expected block"); } + // Outer is Lambda, Body is Block + if let BoundKind::Lambda { body, .. } = &typed.kind { + if let BoundKind::Block { exprs } = &body.kind { + let last_expr = exprs.last().unwrap(); + assert_eq!(last_expr.ty, StaticType::Int, "Variable 'x' should be inferred as Int"); + } else { panic!("Expected block in lambda body"); } + } else { panic!("Expected Lambda wrapper"); } } #[test] fn test_inference_block_type() { // Block type = last expression type - assert_eq!(check_source("(do 1 2.5)").ty, StaticType::Float); - assert_eq!(check_source("(do 1.5 \"test\")").ty, StaticType::Text); + assert_eq!(get_ret_type(&check_source("(do 1 2.5)")), StaticType::Float); + assert_eq!(get_ret_type(&check_source("(do 1.5 \"test\")")), StaticType::Text); } #[test] fn test_inference_lambda_return() { // (fn [a] 10) -> fn(any) -> Int + // Since it's already a Lambda, it's NOT wrapped further. let typed = check_source("(fn [a] 10)"); - if let StaticType::Function(sig) = typed.ty { + if let StaticType::Function(sig) = &typed.ty { assert_eq!(sig.ret, StaticType::Int); } else { panic!("Expected function type, got {:?}", typed.ty); } // Nested: (fn [] (do 1 2.5)) -> fn() -> Float let typed_nested = check_source("(fn [] (do 1 2.5))"); - if let StaticType::Function(sig) = typed_nested.ty { + if let StaticType::Function(sig) = &typed_nested.ty { assert_eq!(sig.ret, StaticType::Float); } else { panic!("Expected function type"); } } @@ -332,29 +344,31 @@ mod tests { fn test_inference_assignment_updates_type() { // (do (def x 10) (assign x 20.5) x) -> x becomes Float after assignment let typed = check_source("(do (def x 10) (assign x 20.5) x)"); - if let BoundKind::Block { exprs } = typed.kind { - let last_expr = exprs.last().unwrap(); - assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment"); - } else { panic!("Expected block"); } + if let BoundKind::Lambda { body, .. } = &typed.kind { + if let BoundKind::Block { exprs } = &body.kind { + let last_expr = exprs.last().unwrap(); + assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment"); + } else { panic!("Expected block"); } + } else { panic!("Expected Lambda"); } } #[test] fn test_operator_overloading_inference() { - assert_eq!(check_source("(+ 1 2)").ty, StaticType::Int); - assert_eq!(check_source("(+ 1.0 2.0)").ty, StaticType::Float); - assert_eq!(check_source("(+ \"a\" \"b\")").ty, StaticType::Text); - assert_eq!(check_source("(/ 1 2)").ty, StaticType::Float); + assert_eq!(get_ret_type(&check_source("(+ 1 2)")), StaticType::Int); + assert_eq!(get_ret_type(&check_source("(+ 1.0 2.0)")), StaticType::Float); + assert_eq!(get_ret_type(&check_source("(+ \"a\" \"b\")")), StaticType::Text); + assert_eq!(get_ret_type(&check_source("(/ 1 2)")), StaticType::Float); } #[test] fn test_datetime_inference() { // date("2023-01-01") -> DateTime - assert_eq!(check_source("(date \"2023-01-01\")").ty, StaticType::DateTime); + assert_eq!(get_ret_type(&check_source("(date \"2023-01-01\")")), StaticType::DateTime); // DateTime + Int -> DateTime - assert_eq!(check_source("(+ (date \"2023-01-01\") 86400000)").ty, StaticType::DateTime); + assert_eq!(get_ret_type(&check_source("(+ (date \"2023-01-01\") 86400000)")), StaticType::DateTime); // DateTime - DateTime -> Int (Duration) - assert_eq!(check_source("(- (date \"2023-01-02\") (date \"2023-01-01\"))").ty, StaticType::Int); + assert_eq!(get_ret_type(&check_source("(- (date \"2023-01-02\") (date \"2023-01-01\"))")), StaticType::Int); // DateTime comparison -> Bool - assert_eq!(check_source("(> (date \"2023-01-02\") (date \"2023-01-01\"))").ty, StaticType::Bool); + assert_eq!(get_ret_type(&check_source("(> (date \"2023-01-02\") (date \"2023-01-01\"))")), StaticType::Bool); } } diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 9d77d68..f7ee9f2 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -45,10 +45,8 @@ struct RuntimeMacroEvaluator { global_names: Rc>>, global_types: Rc>>, global_values: Rc>>, - function_registry: Rc>>, } - impl MacroEvaluator for RuntimeMacroEvaluator { fn evaluate(&self, node: &Node, bindings: &HashMap, Node>) -> Result { // 1. Check if it's a simple parameter substitution @@ -97,7 +95,6 @@ impl Environment { global_names: self.global_names.clone(), global_types: self.global_types.clone(), global_values: self.global_values.clone(), - function_registry: self.function_registry.clone(), }; MacroExpander::new(MacroRegistry::new(), evaluator) } @@ -234,7 +231,16 @@ impl Environment { /// Runtime: Execute the linked AST in the VM pub fn run(&self, node: &TypedNode) -> Result { let mut vm = VM::new(self.global_values.clone()); - vm.run(node) + let result = vm.run(node)?; + + if let Value::Object(obj) = &result { + if let Some(closure) = obj.as_any().downcast_ref::() { + // Execute the script body + return vm.run(&closure.function_node); + } + } + + Ok(result) } pub fn run_script(&self, source: &str) -> Result { @@ -258,8 +264,15 @@ impl Environment { // Execute with TracingObserver let mut vm = VM::new(self.global_values.clone()); let mut observer = TracingObserver::new(); - let result = vm.run_with_observer(&mut observer, &linked); + 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 { + if let Some(closure) = obj.as_any().downcast_ref::() { + result = vm.run_with_observer(&mut observer, &closure.function_node); + } + } + Ok((result, observer.logs)) } } diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 4826106..64aea82 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -288,10 +288,36 @@ impl VM { closure: None, }); - let result = self.eval(root); - + let mut result = self.eval(root); self.frames.pop(); - result + + loop { + match result { + Ok(Value::TailCallRequest(payload)) => { + let (next_obj, next_args) = *payload; + if let Some(closure) = next_obj.as_any().downcast_ref::() { + let old_stack_top = 0; // Root is always base 0 + let closure_rc = Rc::new(closure.clone()); + + // Reset stack for the next call (TCO) + self.stack.clear(); + self.stack.extend(next_args); + + self.frames.push(CallFrame { + stack_base: old_stack_top, + closure: Some(closure_rc), + }); + + result = self.eval(&closure.function_node); + + self.frames.pop(); + } else { + return Err(format!("Tail call target is not a closure: {}", next_obj.type_name())); + } + } + _ => return result, + } + } } pub fn run_with_observer(&mut self, observer: &mut O, root: &TypedNode) -> Result { diff --git a/src/integration_test.rs b/src/integration_test.rs index 529c2e6..a71ba30 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -1,10 +1,6 @@ #[cfg(test)] mod tests { - use std::rc::Rc; - use std::cell::RefCell; use crate::ast::parser::Parser; - use crate::ast::compiler::binder::Binder; - use crate::ast::vm::VM; use crate::ast::types::Value; use crate::ast::nodes::UntypedKind; use crate::ast::environment::Environment; @@ -73,21 +69,10 @@ mod tests { ) "#; - let mut parser = Parser::new(source).expect("Failed to create parser"); - let ast = parser.parse_expression().expect("Failed to parse"); - - let globals = Rc::new(RefCell::new(std::collections::HashMap::new())); - let mut binder = Binder::new(globals.clone()); - let bound_ast = binder.bind(&ast).expect("Failed to bind"); - - // Use the real TypeChecker instead of the dummy placeholder - let global_types = Rc::new(RefCell::new(std::collections::HashMap::new())); - let checker = crate::ast::compiler::TypeChecker::new(global_types); - let typed_ast = checker.check(bound_ast).expect("Type check failed"); - - let vm_globals = Rc::new(RefCell::new(Vec::new())); - let mut vm = VM::new(vm_globals); - let result = vm.run(&typed_ast); + let env = Environment::new(); + let compiled = env.compile(source).expect("Failed to compile"); + let linked = env.link(compiled); + let result = env.run(&linked); match result { Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"),