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.
This commit is contained in:
Michael Schimmel
2026-02-19 23:54:53 +01:00
parent 3c0f2ec8ce
commit 1331aabbe1
13 changed files with 108 additions and 71 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
;; Closure Scope Test
;; Benchmark: 600ns
;; Benchmark: 700ns
;; Output: 15
(do
(def make-adder (fn [x]
+1 -1
View File
@@ -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]
+1 -1
View File
@@ -1,4 +1,4 @@
;; Benchmark: 1.5us
;; Benchmark: 1.6us
;; Output: 36
(do
;; Excessive capture test
+1 -1
View File
@@ -1,4 +1,4 @@
;; Benchmark: 300ns
;; Benchmark: 400ns
;; Nested Macro and Arithmetic example
;; Output: 81
+1 -1
View File
@@ -1,4 +1,4 @@
;; Benchmark: 200ns
;; Benchmark: 300ns
;; Macro Splicing example
;; Demonstrates unrolling a list into another list.
;; Output: [0 1 2 3 4]
+1 -1
View File
@@ -1,4 +1,4 @@
;; Benchmark: 100ns
;; Benchmark: 200ns
;; Classic "unless" macro example
;; Demonstrates simple template substitution.
;; Output: 42
+5 -6
View File
@@ -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]
y
(if (<= x 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)
+1 -1
View File
@@ -1,4 +1,4 @@
;; Benchmark: 2.7ms
;; Benchmark: 2.8ms
;; Output: "done"
(do
(def count_down (fn [n]
+7 -7
View File
@@ -232,7 +232,7 @@ mod tests {
// Mock Registry
struct MockRegistry {
functions: HashMap<Address, TypedNode>,
functions: HashMap<Address, BoundNode>,
}
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<TypedNode> {
fn resolve(&self, addr: Address) -> Option<BoundNode> {
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))
});
+38 -24
View File
@@ -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);
}
}
+18 -5
View File
@@ -45,10 +45,8 @@ struct RuntimeMacroEvaluator {
global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
global_values: Rc<RefCell<Vec<Value>>>,
function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
}
impl MacroEvaluator for RuntimeMacroEvaluator {
fn evaluate(&self, node: &Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Value, String> {
// 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<Value, String> {
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::<crate::ast::vm::Closure>() {
// Execute the script body
return vm.run(&closure.function_node);
}
}
Ok(result)
}
pub fn run_script(&self, source: &str) -> Result<Value, String> {
@@ -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::<crate::ast::vm::Closure>() {
result = vm.run_with_observer(&mut observer, &closure.function_node);
}
}
Ok((result, observer.logs))
}
}
+29 -3
View File
@@ -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::<Closure>() {
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<O: VMObserver>(&mut self, observer: &mut O, root: &TypedNode) -> Result<Value, String> {
+4 -19
View File
@@ -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"),