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:
@@ -1,5 +1,5 @@
|
|||||||
;; Closure Scope Test
|
;; Closure Scope Test
|
||||||
;; Benchmark: 600ns
|
;; Benchmark: 700ns
|
||||||
;; Output: 15
|
;; Output: 15
|
||||||
(do
|
(do
|
||||||
(def make-adder (fn [x]
|
(def make-adder (fn [x]
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
;; Complex Data Structure Test
|
;; Complex Data Structure Test
|
||||||
;; Benchmark: 49.4us
|
;; Benchmark: 51.8us
|
||||||
;; Output: {:input 10, :name "Fibonacci", :output 55}
|
;; Output: {:input 10, :name "Fibonacci", :output 55}
|
||||||
(do
|
(do
|
||||||
(def fib (fn [n]
|
(def fib (fn [n]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
;; Benchmark: 1.5us
|
;; Benchmark: 1.6us
|
||||||
;; Output: 36
|
;; Output: 36
|
||||||
(do
|
(do
|
||||||
;; Excessive capture test
|
;; Excessive capture test
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
;; Benchmark: 300ns
|
;; Benchmark: 400ns
|
||||||
;; Nested Macro and Arithmetic example
|
;; Nested Macro and Arithmetic example
|
||||||
;; Output: 81
|
;; Output: 81
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
;; Benchmark: 200ns
|
;; Benchmark: 300ns
|
||||||
;; Macro Splicing example
|
;; Macro Splicing example
|
||||||
;; Demonstrates unrolling a list into another list.
|
;; Demonstrates unrolling a list into another list.
|
||||||
;; Output: [0 1 2 3 4]
|
;; Output: [0 1 2 3 4]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
;; Benchmark: 100ns
|
;; Benchmark: 200ns
|
||||||
;; Classic "unless" macro example
|
;; Classic "unless" macro example
|
||||||
;; Demonstrates simple template substitution.
|
;; Demonstrates simple template substitution.
|
||||||
;; Output: 42
|
;; Output: 42
|
||||||
|
|||||||
+5
-6
@@ -1,17 +1,16 @@
|
|||||||
;; Benchmark: 602.1us
|
|
||||||
;; Takeuchi Function Benchmark
|
;; Takeuchi Function Benchmark
|
||||||
;; Benchmark: ~150ms (Specialized) vs ~1.5s (Dynamic)
|
|
||||||
;; heavily recursive, benefits significantly from specialization of integer arithmetic
|
;; heavily recursive, benefits significantly from specialization of integer arithmetic
|
||||||
;; and direct function calls (skipping dynamic dispatch).
|
;; and direct function calls (skipping dynamic dispatch).
|
||||||
;; Output: 7
|
;; Output: 4
|
||||||
|
|
||||||
|
;; Benchmark: 468.7us
|
||||||
|
|
||||||
(do
|
(do
|
||||||
(def tak (fn [x y z]
|
(def tak (fn [x y z]
|
||||||
y
|
(if (<= x y)
|
||||||
z
|
z
|
||||||
(tak (tak (- x 1) y z)
|
(tak (tak (- x 1) y z)
|
||||||
(tak (- y 1) z x)
|
(tak (- y 1) z x)
|
||||||
(tak (- z 1) x y)))))
|
(tak (- z 1) x y)))))
|
||||||
(tak 10 6 3)
|
|
||||||
(tak 12 8 4)
|
(tak 12 8 4)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
;; Benchmark: 2.7ms
|
;; Benchmark: 2.8ms
|
||||||
;; Output: "done"
|
;; Output: "done"
|
||||||
(do
|
(do
|
||||||
(def count_down (fn [n]
|
(def count_down (fn [n]
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ mod tests {
|
|||||||
|
|
||||||
// Mock Registry
|
// Mock Registry
|
||||||
struct MockRegistry {
|
struct MockRegistry {
|
||||||
functions: HashMap<Address, TypedNode>,
|
functions: HashMap<Address, BoundNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockRegistry {
|
impl MockRegistry {
|
||||||
@@ -240,13 +240,13 @@ mod tests {
|
|||||||
Self { functions: HashMap::new() }
|
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);
|
self.functions.insert(addr, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FunctionRegistry for MockRegistry {
|
impl FunctionRegistry for MockRegistry {
|
||||||
fn resolve(&self, addr: Address) -> Option<TypedNode> {
|
fn resolve(&self, addr: Address) -> Option<BoundNode> {
|
||||||
self.functions.get(&addr).cloned()
|
self.functions.get(&addr).cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,19 +259,19 @@ mod tests {
|
|||||||
let name = Symbol::from("test_func");
|
let name = Symbol::from("test_func");
|
||||||
|
|
||||||
// Def: (fn [x] x) -- generic identity
|
// Def: (fn [x] x) -- generic identity
|
||||||
let func_node = TypedNode {
|
let func_node = BoundNode {
|
||||||
identity: make_identity(),
|
identity: make_identity(),
|
||||||
kind: BoundKind::Lambda {
|
kind: BoundKind::Lambda {
|
||||||
param_count: 1,
|
param_count: 1,
|
||||||
upvalues: vec![],
|
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);
|
registry.register(addr, func_node);
|
||||||
|
|
||||||
// Setup Compiler Mock
|
// 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
|
// Return a specialized "compiled" value
|
||||||
Ok((Value::Int(12345), StaticType::Int))
|
Ok((Value::Int(12345), StaticType::Int))
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -288,42 +288,54 @@ mod tests {
|
|||||||
env.compile(source).unwrap()
|
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]
|
#[test]
|
||||||
fn test_inference_constants() {
|
fn test_inference_constants() {
|
||||||
assert_eq!(check_source("10").ty, StaticType::Int);
|
assert_eq!(get_ret_type(&check_source("10")), StaticType::Int);
|
||||||
assert_eq!(check_source("10.5").ty, StaticType::Float);
|
assert_eq!(get_ret_type(&check_source("10.5")), StaticType::Float);
|
||||||
assert_eq!(check_source("true").ty, StaticType::Bool);
|
assert_eq!(get_ret_type(&check_source("true")), StaticType::Bool);
|
||||||
assert_eq!(check_source("\"hello\"").ty, StaticType::Text);
|
assert_eq!(get_ret_type(&check_source("\"hello\"")), StaticType::Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_inference_variable_propagation() {
|
fn test_inference_variable_propagation() {
|
||||||
// (do (def x 10) x) -> The last 'x' must be Int
|
// (do (def x 10) x) -> The last 'x' must be Int
|
||||||
let typed = check_source("(do (def x 10) x)");
|
let typed = check_source("(do (def x 10) x)");
|
||||||
if let BoundKind::Block { exprs } = typed.kind {
|
// Outer is Lambda, Body is Block
|
||||||
let last_expr = exprs.last().unwrap();
|
if let BoundKind::Lambda { body, .. } = &typed.kind {
|
||||||
assert_eq!(last_expr.ty, StaticType::Int, "Variable 'x' should be inferred as Int");
|
if let BoundKind::Block { exprs } = &body.kind {
|
||||||
} else { panic!("Expected block"); }
|
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]
|
#[test]
|
||||||
fn test_inference_block_type() {
|
fn test_inference_block_type() {
|
||||||
// Block type = last expression type
|
// Block type = last expression type
|
||||||
assert_eq!(check_source("(do 1 2.5)").ty, StaticType::Float);
|
assert_eq!(get_ret_type(&check_source("(do 1 2.5)")), StaticType::Float);
|
||||||
assert_eq!(check_source("(do 1.5 \"test\")").ty, StaticType::Text);
|
assert_eq!(get_ret_type(&check_source("(do 1.5 \"test\")")), StaticType::Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_inference_lambda_return() {
|
fn test_inference_lambda_return() {
|
||||||
// (fn [a] 10) -> fn(any) -> Int
|
// (fn [a] 10) -> fn(any) -> Int
|
||||||
|
// Since it's already a Lambda, it's NOT wrapped further.
|
||||||
let typed = check_source("(fn [a] 10)");
|
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);
|
assert_eq!(sig.ret, StaticType::Int);
|
||||||
} else { panic!("Expected function type, got {:?}", typed.ty); }
|
} else { panic!("Expected function type, got {:?}", typed.ty); }
|
||||||
|
|
||||||
// Nested: (fn [] (do 1 2.5)) -> fn() -> Float
|
// Nested: (fn [] (do 1 2.5)) -> fn() -> Float
|
||||||
let typed_nested = check_source("(fn [] (do 1 2.5))");
|
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);
|
assert_eq!(sig.ret, StaticType::Float);
|
||||||
} else { panic!("Expected function type"); }
|
} else { panic!("Expected function type"); }
|
||||||
}
|
}
|
||||||
@@ -332,29 +344,31 @@ mod tests {
|
|||||||
fn test_inference_assignment_updates_type() {
|
fn test_inference_assignment_updates_type() {
|
||||||
// (do (def x 10) (assign x 20.5) x) -> x becomes Float after assignment
|
// (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)");
|
let typed = check_source("(do (def x 10) (assign x 20.5) x)");
|
||||||
if let BoundKind::Block { exprs } = typed.kind {
|
if let BoundKind::Lambda { body, .. } = &typed.kind {
|
||||||
let last_expr = exprs.last().unwrap();
|
if let BoundKind::Block { exprs } = &body.kind {
|
||||||
assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment");
|
let last_expr = exprs.last().unwrap();
|
||||||
} else { panic!("Expected block"); }
|
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]
|
#[test]
|
||||||
fn test_operator_overloading_inference() {
|
fn test_operator_overloading_inference() {
|
||||||
assert_eq!(check_source("(+ 1 2)").ty, StaticType::Int);
|
assert_eq!(get_ret_type(&check_source("(+ 1 2)")), StaticType::Int);
|
||||||
assert_eq!(check_source("(+ 1.0 2.0)").ty, StaticType::Float);
|
assert_eq!(get_ret_type(&check_source("(+ 1.0 2.0)")), StaticType::Float);
|
||||||
assert_eq!(check_source("(+ \"a\" \"b\")").ty, StaticType::Text);
|
assert_eq!(get_ret_type(&check_source("(+ \"a\" \"b\")")), StaticType::Text);
|
||||||
assert_eq!(check_source("(/ 1 2)").ty, StaticType::Float);
|
assert_eq!(get_ret_type(&check_source("(/ 1 2)")), StaticType::Float);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_datetime_inference() {
|
fn test_datetime_inference() {
|
||||||
// date("2023-01-01") -> DateTime
|
// 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
|
// 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)
|
// 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
|
// 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
@@ -45,10 +45,8 @@ struct RuntimeMacroEvaluator {
|
|||||||
global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
|
global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
|
||||||
global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
|
global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
|
||||||
global_values: Rc<RefCell<Vec<Value>>>,
|
global_values: Rc<RefCell<Vec<Value>>>,
|
||||||
function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl MacroEvaluator for RuntimeMacroEvaluator {
|
impl MacroEvaluator for RuntimeMacroEvaluator {
|
||||||
fn evaluate(&self, node: &Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Value, String> {
|
fn evaluate(&self, node: &Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Value, String> {
|
||||||
// 1. Check if it's a simple parameter substitution
|
// 1. Check if it's a simple parameter substitution
|
||||||
@@ -97,7 +95,6 @@ impl Environment {
|
|||||||
global_names: self.global_names.clone(),
|
global_names: self.global_names.clone(),
|
||||||
global_types: self.global_types.clone(),
|
global_types: self.global_types.clone(),
|
||||||
global_values: self.global_values.clone(),
|
global_values: self.global_values.clone(),
|
||||||
function_registry: self.function_registry.clone(),
|
|
||||||
};
|
};
|
||||||
MacroExpander::new(MacroRegistry::new(), evaluator)
|
MacroExpander::new(MacroRegistry::new(), evaluator)
|
||||||
}
|
}
|
||||||
@@ -234,7 +231,16 @@ impl Environment {
|
|||||||
/// Runtime: Execute the linked AST in the VM
|
/// Runtime: Execute the linked AST in the VM
|
||||||
pub fn run(&self, node: &TypedNode) -> Result<Value, String> {
|
pub fn run(&self, node: &TypedNode) -> Result<Value, String> {
|
||||||
let mut vm = VM::new(self.global_values.clone());
|
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> {
|
pub fn run_script(&self, source: &str) -> Result<Value, String> {
|
||||||
@@ -258,8 +264,15 @@ impl Environment {
|
|||||||
// Execute with TracingObserver
|
// Execute with TracingObserver
|
||||||
let mut vm = VM::new(self.global_values.clone());
|
let mut vm = VM::new(self.global_values.clone());
|
||||||
let mut observer = TracingObserver::new();
|
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))
|
Ok((result, observer.logs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-3
@@ -288,10 +288,36 @@ impl VM {
|
|||||||
closure: None,
|
closure: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = self.eval(root);
|
let mut result = self.eval(root);
|
||||||
|
|
||||||
self.frames.pop();
|
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> {
|
pub fn run_with_observer<O: VMObserver>(&mut self, observer: &mut O, root: &TypedNode) -> Result<Value, String> {
|
||||||
|
|||||||
+4
-19
@@ -1,10 +1,6 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use crate::ast::parser::Parser;
|
use crate::ast::parser::Parser;
|
||||||
use crate::ast::compiler::binder::Binder;
|
|
||||||
use crate::ast::vm::VM;
|
|
||||||
use crate::ast::types::Value;
|
use crate::ast::types::Value;
|
||||||
use crate::ast::nodes::UntypedKind;
|
use crate::ast::nodes::UntypedKind;
|
||||||
use crate::ast::environment::Environment;
|
use crate::ast::environment::Environment;
|
||||||
@@ -73,21 +69,10 @@ mod tests {
|
|||||||
)
|
)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let mut parser = Parser::new(source).expect("Failed to create parser");
|
let env = Environment::new();
|
||||||
let ast = parser.parse_expression().expect("Failed to parse");
|
let compiled = env.compile(source).expect("Failed to compile");
|
||||||
|
let linked = env.link(compiled);
|
||||||
let globals = Rc::new(RefCell::new(std::collections::HashMap::new()));
|
let result = env.run(&linked);
|
||||||
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);
|
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"),
|
Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"),
|
||||||
|
|||||||
Reference in New Issue
Block a user