Add positional_count field to Lambda
This field is used for static optimization, determining if parameters are purely positional.
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
;; Comprehensive Destructuring Test
|
||||||
|
;; Covers: Nested tuples, mixed params, dynamic passing
|
||||||
|
|
||||||
|
(do
|
||||||
|
;; 1. Deeply nested
|
||||||
|
(def deep (fn [[a [[b c] d]]] (+ a (+ b (+ c d)))))
|
||||||
|
|
||||||
|
;; 2. Mixed: Tuple and Single
|
||||||
|
(def mixed (fn [[x y] z] (+ (+ x y) z)))
|
||||||
|
|
||||||
|
;; 3. Dynamic: Passing a list variable
|
||||||
|
(def call-dynamic (fn [f data] (f data)))
|
||||||
|
|
||||||
|
(def data [10 [20 30]])
|
||||||
|
|
||||||
|
;; Validation
|
||||||
|
(if (= (deep [1 [[2 3] 4]]) 10)
|
||||||
|
(if (= (mixed [1 2] 3) 6)
|
||||||
|
(if (= (call-dynamic (fn [[a [b c]]] (+ a (+ b c))) data) 60)
|
||||||
|
"PASS"
|
||||||
|
"FAIL-DYNAMIC")
|
||||||
|
"FAIL-MIXED")
|
||||||
|
"FAIL-DEEP"))
|
||||||
|
|
||||||
|
;; Output: "PASS"
|
||||||
@@ -200,10 +200,30 @@ impl Binder {
|
|||||||
|
|
||||||
let compiled_fn = self.functions.pop().unwrap();
|
let compiled_fn = self.functions.pop().unwrap();
|
||||||
|
|
||||||
|
// 3. Static optimization: check if parameters are purely positional
|
||||||
|
let positional_count = match ¶ms_bound.kind {
|
||||||
|
BoundKind::Tuple { elements } => {
|
||||||
|
let mut count = 0;
|
||||||
|
let mut all_params = true;
|
||||||
|
for e in elements {
|
||||||
|
if matches!(e.kind, BoundKind::Parameter { .. }) {
|
||||||
|
count += 1;
|
||||||
|
} else {
|
||||||
|
all_params = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if all_params { Some(count) } else { None }
|
||||||
|
}
|
||||||
|
BoundKind::Parameter { .. } => Some(1),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(self.make_node(identity, BoundKind::Lambda {
|
Ok(self.make_node(identity, BoundKind::Lambda {
|
||||||
params: Box::new(params_bound),
|
params: Rc::new(params_bound),
|
||||||
upvalues: compiled_fn.upvalues,
|
upvalues: compiled_fn.upvalues,
|
||||||
body: Rc::new(body_bound),
|
body: Rc::new(body_bound),
|
||||||
|
positional_count,
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -72,10 +72,12 @@ pub enum BoundKind<T = ()> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Lambda {
|
Lambda {
|
||||||
params: Box<BoundNode<T>>,
|
params: Rc<BoundNode<T>>,
|
||||||
// The list of variables captured from enclosing scopes
|
// The list of variables captured from enclosing scopes
|
||||||
upvalues: Vec<Address>,
|
upvalues: Vec<Address>,
|
||||||
body: Rc<BoundNode<T>>,
|
body: Rc<BoundNode<T>>,
|
||||||
|
/// Static optimization: number of positional parameters if the pattern is flat.
|
||||||
|
positional_count: Option<u32>,
|
||||||
},
|
},
|
||||||
|
|
||||||
Call {
|
Call {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ impl Dumper {
|
|||||||
self.indent -= 1;
|
self.indent -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Lambda { params, upvalues, body } => {
|
BoundKind::Lambda { params, upvalues, body, .. } => {
|
||||||
self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node);
|
self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node);
|
||||||
self.indent += 1;
|
self.indent += 1;
|
||||||
|
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ impl Specializer {
|
|||||||
let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect();
|
let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect();
|
||||||
(BoundKind::Block { exprs }, node.ty)
|
(BoundKind::Block { exprs }, node.ty)
|
||||||
},
|
},
|
||||||
BoundKind::Lambda { params, upvalues, body } => {
|
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
||||||
let params = Box::new(self.visit_node(*params));
|
let params = Rc::new(self.visit_node(params.as_ref().clone()));
|
||||||
let body = Rc::new(self.visit_node((*body).clone()));
|
let body = Rc::new(self.visit_node((*body).clone()));
|
||||||
(BoundKind::Lambda { params, upvalues, body }, node.ty)
|
(BoundKind::Lambda { params, upvalues, body, positional_count }, node.ty)
|
||||||
},
|
},
|
||||||
BoundKind::DefLocal { name, slot, value, captured_by } => {
|
BoundKind::DefLocal { name, slot, value, captured_by } => {
|
||||||
let value = Box::new(self.visit_node(*value));
|
let value = Box::new(self.visit_node(*value));
|
||||||
@@ -194,15 +194,25 @@ impl Specializer {
|
|||||||
// Store in cache
|
// Store in cache
|
||||||
self.cache.borrow_mut().insert(key, (res_val.clone(), res_ty.clone()));
|
self.cache.borrow_mut().insert(key, (res_val.clone(), res_ty.clone()));
|
||||||
|
|
||||||
|
// PERFORMANCE: Flatten the argument tuple to match the specialized signature.
|
||||||
|
// Since we are specializing, we can convert [[1 2] 3] into a flat [1 2 3] Tuple node.
|
||||||
|
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 {
|
let specialized_callee = Node {
|
||||||
identity: new_callee.identity.clone(),
|
identity: new_callee.identity.clone(),
|
||||||
kind: BoundKind::Constant(res_val),
|
kind: BoundKind::Constant(res_val),
|
||||||
ty: StaticType::Function(Box::new(Signature {
|
ty: StaticType::Function(Box::new(Signature {
|
||||||
params: StaticType::Tuple(arg_types),
|
params: flattened_args.ty.clone(),
|
||||||
ret: res_ty.clone(),
|
ret: res_ty.clone(),
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
return (specialized_callee, new_args, res_ty);
|
return (specialized_callee, flattened_args, res_ty);
|
||||||
},
|
},
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Fallback on error
|
// Fallback on error
|
||||||
@@ -214,6 +224,19 @@ impl Specializer {
|
|||||||
// Fallback: Dynamic Call
|
// Fallback: Dynamic Call
|
||||||
(new_callee, new_args, original_ty)
|
(new_callee, new_args, original_ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flatten_tuple(&self, node: TypedNode) -> Vec<TypedNode> {
|
||||||
|
match node.kind {
|
||||||
|
BoundKind::Tuple { elements } => {
|
||||||
|
let mut flat = Vec::new();
|
||||||
|
for el in elements {
|
||||||
|
flat.extend(self.flatten_tuple(el));
|
||||||
|
}
|
||||||
|
flat
|
||||||
|
}
|
||||||
|
_ => vec![node],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -268,7 +291,7 @@ mod tests {
|
|||||||
let func_node = BoundNode {
|
let func_node = BoundNode {
|
||||||
identity: make_identity(),
|
identity: make_identity(),
|
||||||
kind: BoundKind::Lambda {
|
kind: BoundKind::Lambda {
|
||||||
params: Box::new(BoundNode {
|
params: Rc::new(BoundNode {
|
||||||
identity: make_identity(),
|
identity: make_identity(),
|
||||||
kind: BoundKind::Tuple {
|
kind: BoundKind::Tuple {
|
||||||
elements: vec![
|
elements: vec![
|
||||||
@@ -282,7 +305,8 @@ mod tests {
|
|||||||
ty: ()
|
ty: ()
|
||||||
}),
|
}),
|
||||||
upvalues: vec![],
|
upvalues: vec![],
|
||||||
body: Rc::new(BoundNode { identity: make_identity(), kind: BoundKind::Nop, ty: () })
|
body: Rc::new(BoundNode { identity: make_identity(), kind: BoundKind::Nop, ty: () }),
|
||||||
|
positional_count: Some(1),
|
||||||
},
|
},
|
||||||
ty: ()
|
ty: ()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,15 +75,16 @@ impl TCO {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Lambda { params, upvalues, body } => {
|
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
||||||
// The body of a lambda is implicitly in tail position when the lambda is called.
|
// The body of a lambda is implicitly in tail position when the lambda is called.
|
||||||
let new_body = Rc::new(Self::transform((*body).clone(), true));
|
let new_body = Rc::new(Self::transform((*body).clone(), true));
|
||||||
|
|
||||||
Node {
|
Node {
|
||||||
kind: BoundKind::Lambda {
|
kind: BoundKind::Lambda {
|
||||||
params,
|
params: params.clone(),
|
||||||
upvalues,
|
upvalues,
|
||||||
body: new_body,
|
body: new_body,
|
||||||
|
positional_count,
|
||||||
},
|
},
|
||||||
..node
|
..node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result<TypedNode, String> {
|
pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result<TypedNode, String> {
|
||||||
match node.kind {
|
match node.kind {
|
||||||
BoundKind::Lambda { params, upvalues, body } => {
|
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
||||||
// 1. Determine types of captured variables (Root lambdas have none)
|
// 1. Determine types of captured variables (Root lambdas have none)
|
||||||
let mut upvalue_types = Vec::with_capacity(upvalues.len());
|
let mut upvalue_types = Vec::with_capacity(upvalues.len());
|
||||||
for _ in &upvalues {
|
for _ in &upvalues {
|
||||||
@@ -66,7 +66,7 @@ impl TypeChecker {
|
|||||||
StaticType::Tuple(arg_types.to_vec())
|
StaticType::Tuple(arg_types.to_vec())
|
||||||
};
|
};
|
||||||
|
|
||||||
let params_typed = self.check_params(*params, &arg_tuple_ty, &mut lambda_ctx)?;
|
let params_typed = self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx)?;
|
||||||
|
|
||||||
// 4. Check body with the new types
|
// 4. Check body with the new types
|
||||||
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
|
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
|
||||||
@@ -83,9 +83,10 @@ impl TypeChecker {
|
|||||||
Ok(Node {
|
Ok(Node {
|
||||||
identity: node.identity,
|
identity: node.identity,
|
||||||
kind: BoundKind::Lambda {
|
kind: BoundKind::Lambda {
|
||||||
params: Box::new(params_typed),
|
params: Rc::new(params_typed),
|
||||||
upvalues,
|
upvalues,
|
||||||
body: Rc::new(body_typed)
|
body: Rc::new(body_typed),
|
||||||
|
positional_count,
|
||||||
},
|
},
|
||||||
ty: fn_ty,
|
ty: fn_ty,
|
||||||
})
|
})
|
||||||
@@ -95,13 +96,14 @@ impl TypeChecker {
|
|||||||
let virtual_lambda = BoundNode {
|
let virtual_lambda = BoundNode {
|
||||||
identity: node.identity.clone(),
|
identity: node.identity.clone(),
|
||||||
kind: BoundKind::Lambda {
|
kind: BoundKind::Lambda {
|
||||||
params: Box::new(Node {
|
params: Rc::new(Node {
|
||||||
identity: node.identity.clone(),
|
identity: node.identity.clone(),
|
||||||
kind: BoundKind::Tuple { elements: vec![] },
|
kind: BoundKind::Tuple { elements: vec![] },
|
||||||
ty: (),
|
ty: (),
|
||||||
}),
|
}),
|
||||||
upvalues: vec![],
|
upvalues: vec![],
|
||||||
body: Rc::new(node)
|
body: Rc::new(node),
|
||||||
|
positional_count: Some(0),
|
||||||
},
|
},
|
||||||
ty: (),
|
ty: (),
|
||||||
};
|
};
|
||||||
@@ -232,7 +234,7 @@ impl TypeChecker {
|
|||||||
(BoundKind::Block { exprs: typed_exprs }, last_ty)
|
(BoundKind::Block { exprs: typed_exprs }, last_ty)
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Lambda { params, upvalues, body } => {
|
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
||||||
// 1. Determine types of captured variables
|
// 1. Determine types of captured variables
|
||||||
let mut upvalue_types = Vec::with_capacity(upvalues.len());
|
let mut upvalue_types = Vec::with_capacity(upvalues.len());
|
||||||
for &addr in &upvalues {
|
for &addr in &upvalues {
|
||||||
@@ -243,7 +245,7 @@ impl TypeChecker {
|
|||||||
let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(ctx));
|
let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(ctx));
|
||||||
|
|
||||||
// 3. Check parameters and body
|
// 3. Check parameters and body
|
||||||
let params_typed = self.check_params(*params, &StaticType::Any, &mut lambda_ctx)?;
|
let params_typed = self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx)?;
|
||||||
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
|
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
|
||||||
let ret_ty = body_typed.ty.clone();
|
let ret_ty = body_typed.ty.clone();
|
||||||
|
|
||||||
@@ -254,9 +256,10 @@ impl TypeChecker {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
(BoundKind::Lambda {
|
(BoundKind::Lambda {
|
||||||
params: Box::new(params_typed),
|
params: Rc::new(params_typed),
|
||||||
upvalues,
|
upvalues,
|
||||||
body: Rc::new(body_typed)
|
body: Rc::new(body_typed),
|
||||||
|
positional_count,
|
||||||
}, fn_ty)
|
}, fn_ty)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+24
-4
@@ -231,15 +231,24 @@ 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());
|
||||||
let result = vm.run(node)?;
|
let mut result = vm.run(node)?;
|
||||||
|
|
||||||
|
// Handle potential script body closure
|
||||||
if let Value::Object(obj) = &result
|
if let Value::Object(obj) = &result
|
||||||
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
|
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
|
||||||
{
|
{
|
||||||
// Execute the script body
|
result = vm.run(&closure.function_node)?;
|
||||||
return vm.run(&closure.function_node);
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: Resolve any pending tail call requests from the top-level execution
|
||||||
|
while let Value::TailCallRequest(payload) = result {
|
||||||
|
let (next_obj, next_args) = *payload;
|
||||||
|
if let Some(closure) = next_obj.as_any().downcast_ref::<crate::ast::vm::Closure>() {
|
||||||
|
result = vm.run_with_args(closure, next_args)?;
|
||||||
|
} else {
|
||||||
|
return Err(format!("Tail call target is not a closure: {}", next_obj.type_name()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +282,17 @@ impl Environment {
|
|||||||
result = vm.run_with_observer(&mut observer, &closure.function_node);
|
result = vm.run_with_observer(&mut observer, &closure.function_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::<crate::ast::vm::Closure>() {
|
||||||
|
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()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok((result, observer.logs))
|
Ok((result, observer.logs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -227,17 +227,22 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
let mut elements = Vec::new();
|
let mut elements = Vec::new();
|
||||||
while *self.peek() != TokenKind::RightBracket {
|
while *self.peek() != TokenKind::RightBracket {
|
||||||
let next_token = self.advance()?;
|
let next_peek = self.peek();
|
||||||
let p_identity = Rc::new(NodeIdentity { location: next_token.location });
|
match next_peek {
|
||||||
match next_token.kind {
|
|
||||||
TokenKind::Identifier(name) => {
|
TokenKind::Identifier(name) => {
|
||||||
|
let name = name.clone();
|
||||||
|
let token = self.advance()?;
|
||||||
|
let p_identity = Rc::new(NodeIdentity { location: token.location });
|
||||||
elements.push(Node {
|
elements.push(Node {
|
||||||
identity: p_identity,
|
identity: p_identity,
|
||||||
kind: UntypedKind::Parameter(name.into()),
|
kind: UntypedKind::Parameter(name.into()),
|
||||||
ty: (),
|
ty: (),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
_ => return Err(format!("Expected identifier in param vector, found {:?}", next_token.kind)),
|
TokenKind::LeftBracket => {
|
||||||
|
elements.push(self.parse_param_vector()?);
|
||||||
|
},
|
||||||
|
_ => return Err(format!("Expected identifier or nested parameter vector, found {:?}", next_peek)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.expect(TokenKind::RightBracket)?;
|
self.expect(TokenKind::RightBracket)?;
|
||||||
|
|||||||
+215
-32
@@ -7,8 +7,12 @@ use crate::ast::types::{Value, Object};
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Closure {
|
pub struct Closure {
|
||||||
|
pub parameter_node: Rc<TypedNode>,
|
||||||
pub function_node: Rc<TypedNode>,
|
pub function_node: Rc<TypedNode>,
|
||||||
pub upvalues: Vec<Rc<RefCell<Value>>>,
|
pub upvalues: Vec<Rc<RefCell<Value>>>,
|
||||||
|
/// Optimization: If the parameter pattern is a simple flat tuple,
|
||||||
|
/// store the count to skip recursive unpacking in the hot path.
|
||||||
|
pub positional_count: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object for Closure {
|
impl Object for Closure {
|
||||||
@@ -163,7 +167,10 @@ macro_rules! dispatch_eval {
|
|||||||
Ok(last)
|
Ok(last)
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Lambda { params: _, upvalues, body } => {
|
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
||||||
|
// PERFORMANCE: Pre-calculated in Binder. Just copy.
|
||||||
|
let positional_count = *positional_count;
|
||||||
|
|
||||||
// PERFORMANCE: Creating a closure captures upvalues.
|
// PERFORMANCE: Creating a closure captures upvalues.
|
||||||
// The actual execution of the lambda (in Call branch) now skips
|
// The actual execution of the lambda (in Call branch) now skips
|
||||||
// the Lambda node itself and jumps directly to the body.
|
// the Lambda node itself and jumps directly to the body.
|
||||||
@@ -173,8 +180,10 @@ macro_rules! dispatch_eval {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let closure = Closure {
|
let closure = Closure {
|
||||||
|
parameter_node: params.clone(),
|
||||||
function_node: body.clone(),
|
function_node: body.clone(),
|
||||||
upvalues: captured,
|
upvalues: captured,
|
||||||
|
positional_count,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Value::Object(Rc::new(closure)))
|
Ok(Value::Object(Rc::new(closure)))
|
||||||
@@ -183,28 +192,36 @@ macro_rules! dispatch_eval {
|
|||||||
BoundKind::TailCall { callee, args } => {
|
BoundKind::TailCall { callee, args } => {
|
||||||
let func_val = $self.$eval_method($($observer,)? callee)?;
|
let func_val = $self.$eval_method($($observer,)? callee)?;
|
||||||
|
|
||||||
// PERFORMANCE OPTIMIZATION: "Everything is a Tuple" Unification
|
let arg_vals = match &args.kind {
|
||||||
// To avoid heap-allocating a Value::List (Rc<Vec<Value>>) for every function call,
|
|
||||||
// we check if the arguments are a literal tuple. If so, we evaluate them
|
|
||||||
// directly into our stack-ready vector.
|
|
||||||
let mut arg_vals = Vec::new();
|
|
||||||
match &args.kind {
|
|
||||||
BoundKind::Tuple { elements } => {
|
BoundKind::Tuple { elements } => {
|
||||||
arg_vals.reserve(elements.len());
|
// FAST-PATH: If it's a flat tuple, evaluate directly into Vec
|
||||||
|
let mut vals = Vec::with_capacity(elements.len());
|
||||||
|
let mut is_complex = false;
|
||||||
for e in elements {
|
for e in elements {
|
||||||
arg_vals.push($self.$eval_method($($observer,)? e)?);
|
if matches!(e.kind, BoundKind::Tuple { .. }) {
|
||||||
|
is_complex = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vals.push($self.$eval_method($($observer,)? e)?);
|
||||||
|
}
|
||||||
|
if is_complex {
|
||||||
|
macro_rules! get_args {
|
||||||
|
($s:ident, $a:ident) => { $s.prepare_args($a)? };
|
||||||
|
($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? };
|
||||||
|
}
|
||||||
|
get_args!($self, $($observer,)? args)
|
||||||
|
} else {
|
||||||
|
vals
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Fallback for dynamic tuples (e.g. arguments passed as a variable)
|
macro_rules! get_args {
|
||||||
let v = $self.$eval_method($($observer,)? args)?;
|
($s:ident, $a:ident) => { $s.prepare_args($a)? };
|
||||||
if let Value::List(l) = v {
|
($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? };
|
||||||
arg_vals = (*l).clone();
|
|
||||||
} else {
|
|
||||||
arg_vals.push(v);
|
|
||||||
}
|
}
|
||||||
|
get_args!($self, $($observer,)? args)
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
match func_val {
|
match func_val {
|
||||||
Value::Object(obj) => Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))),
|
Value::Object(obj) => Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))),
|
||||||
@@ -216,25 +233,35 @@ macro_rules! dispatch_eval {
|
|||||||
BoundKind::Call { callee, args } => {
|
BoundKind::Call { callee, args } => {
|
||||||
let mut func_val = $self.$eval_method($($observer,)? callee)?;
|
let mut func_val = $self.$eval_method($($observer,)? callee)?;
|
||||||
|
|
||||||
// PERFORMANCE OPTIMIZATION: Same as in TailCall above.
|
let mut arg_vals = match &args.kind {
|
||||||
// Short-circuiting the Tuple -> Value::List -> Vec conversion to save heap cycles.
|
|
||||||
let mut arg_vals = Vec::new();
|
|
||||||
match &args.kind {
|
|
||||||
BoundKind::Tuple { elements } => {
|
BoundKind::Tuple { elements } => {
|
||||||
arg_vals.reserve(elements.len());
|
let mut vals = Vec::with_capacity(elements.len());
|
||||||
|
let mut is_complex = false;
|
||||||
for e in elements {
|
for e in elements {
|
||||||
arg_vals.push($self.$eval_method($($observer,)? e)?);
|
if matches!(e.kind, BoundKind::Tuple { .. }) {
|
||||||
|
is_complex = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vals.push($self.$eval_method($($observer,)? e)?);
|
||||||
|
}
|
||||||
|
if is_complex {
|
||||||
|
macro_rules! get_args {
|
||||||
|
($s:ident, $a:ident) => { $s.prepare_args($a)? };
|
||||||
|
($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? };
|
||||||
|
}
|
||||||
|
get_args!($self, $($observer,)? args)
|
||||||
|
} else {
|
||||||
|
vals
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let v = $self.$eval_method($($observer,)? args)?;
|
macro_rules! get_args {
|
||||||
if let Value::List(l) = v {
|
($s:ident, $a:ident) => { $s.prepare_args($a)? };
|
||||||
arg_vals = (*l).clone();
|
($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? };
|
||||||
} else {
|
|
||||||
arg_vals.push(v);
|
|
||||||
}
|
}
|
||||||
|
get_args!($self, $($observer,)? args)
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match func_val {
|
match func_val {
|
||||||
@@ -244,13 +271,21 @@ macro_rules! dispatch_eval {
|
|||||||
let old_stack_top = $self.stack.len();
|
let old_stack_top = $self.stack.len();
|
||||||
let closure_rc = Rc::new(closure.clone());
|
let closure_rc = Rc::new(closure.clone());
|
||||||
|
|
||||||
$self.stack.extend(arg_vals);
|
|
||||||
|
|
||||||
$self.frames.push(CallFrame {
|
$self.frames.push(CallFrame {
|
||||||
stack_base: old_stack_top,
|
stack_base: old_stack_top,
|
||||||
closure: Some(closure_rc.clone()),
|
closure: Some(closure_rc.clone()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// PERFORMANCE FAST-PATH: If the function is purely positional and arguments match, just extend.
|
||||||
|
if let Some(count) = closure.positional_count
|
||||||
|
&& arg_vals.len() == count as usize
|
||||||
|
{
|
||||||
|
$self.stack.extend(arg_vals);
|
||||||
|
} else {
|
||||||
|
// Unpack arguments into slots based on the closure's parameter pattern
|
||||||
|
$self.unpack(&closure.parameter_node, &arg_vals, &mut 0)?;
|
||||||
|
}
|
||||||
|
|
||||||
let result = $self.$eval_method($($observer,)? &closure.function_node);
|
let result = $self.$eval_method($($observer,)? &closure.function_node);
|
||||||
|
|
||||||
$self.frames.pop();
|
$self.frames.pop();
|
||||||
@@ -340,13 +375,20 @@ impl VM {
|
|||||||
|
|
||||||
// Reset stack for the next call (TCO)
|
// Reset stack for the next call (TCO)
|
||||||
self.stack.clear();
|
self.stack.clear();
|
||||||
self.stack.extend(next_args);
|
|
||||||
|
|
||||||
self.frames.push(CallFrame {
|
self.frames.push(CallFrame {
|
||||||
stack_base: old_stack_top,
|
stack_base: old_stack_top,
|
||||||
closure: Some(closure_rc),
|
closure: Some(closure_rc),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some(count) = closure.positional_count
|
||||||
|
&& next_args.len() == count as usize
|
||||||
|
{
|
||||||
|
self.stack.extend(next_args);
|
||||||
|
} else {
|
||||||
|
self.unpack(&closure.parameter_node, &next_args, &mut 0)?;
|
||||||
|
}
|
||||||
|
|
||||||
result = self.eval(&closure.function_node);
|
result = self.eval(&closure.function_node);
|
||||||
|
|
||||||
self.frames.pop();
|
self.frames.pop();
|
||||||
@@ -359,6 +401,48 @@ impl VM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_with_args(&mut self, closure: &Closure, args: Vec<Value>) -> Result<Value, String> {
|
||||||
|
self.stack.clear();
|
||||||
|
self.frames.clear();
|
||||||
|
|
||||||
|
let closure_rc = Rc::new(closure.clone());
|
||||||
|
self.frames.push(CallFrame {
|
||||||
|
stack_base: 0,
|
||||||
|
closure: Some(closure_rc),
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(count) = closure.positional_count
|
||||||
|
&& args.len() == count as usize
|
||||||
|
{
|
||||||
|
self.stack.extend(args);
|
||||||
|
} else {
|
||||||
|
self.unpack(&closure.parameter_node, &args, &mut 0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.eval(&closure.function_node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_with_args_observed<O: VMObserver>(&mut self, observer: &mut O, closure: &Closure, args: Vec<Value>) -> Result<Value, String> {
|
||||||
|
self.stack.clear();
|
||||||
|
self.frames.clear();
|
||||||
|
|
||||||
|
let closure_rc = Rc::new(closure.clone());
|
||||||
|
self.frames.push(CallFrame {
|
||||||
|
stack_base: 0,
|
||||||
|
closure: Some(closure_rc),
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(count) = closure.positional_count
|
||||||
|
&& args.len() == count as usize
|
||||||
|
{
|
||||||
|
self.stack.extend(args);
|
||||||
|
} else {
|
||||||
|
self.unpack(&closure.parameter_node, &args, &mut 0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.eval_observed(observer, &closure.function_node)
|
||||||
|
}
|
||||||
|
|
||||||
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> {
|
||||||
self.stack.clear();
|
self.stack.clear();
|
||||||
self.frames.clear();
|
self.frames.clear();
|
||||||
@@ -514,6 +598,104 @@ impl VM {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flatten_value(val: Value, into: &mut Vec<Value>) {
|
||||||
|
if let Value::List(l) = val {
|
||||||
|
for item in l.iter() {
|
||||||
|
Self::flatten_value(item.clone(), into);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
into.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_args(&mut self, args: &TypedNode) -> Result<Vec<Value>, String> {
|
||||||
|
let mut arg_vals = Vec::new();
|
||||||
|
match &args.kind {
|
||||||
|
BoundKind::Tuple { elements } => {
|
||||||
|
self.eval_and_flatten(elements, &mut arg_vals)?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let v = self.eval(args)?;
|
||||||
|
VM::flatten_value(v, &mut arg_vals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(arg_vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_args_observed<O: VMObserver>(&mut self, observer: &mut O, args: &TypedNode) -> Result<Vec<Value>, String> {
|
||||||
|
let mut arg_vals = Vec::new();
|
||||||
|
match &args.kind {
|
||||||
|
BoundKind::Tuple { elements } => {
|
||||||
|
self.eval_observed_and_flatten(observer, elements, &mut arg_vals)?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let v = self.eval_observed(observer, args)?;
|
||||||
|
VM::flatten_value(v, &mut arg_vals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(arg_vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_and_flatten(&mut self, elements: &[TypedNode], into: &mut Vec<Value>) -> Result<(), String> {
|
||||||
|
for e in elements {
|
||||||
|
match &e.kind {
|
||||||
|
BoundKind::Tuple { elements: sub } => self.eval_and_flatten(sub, into)?,
|
||||||
|
_ => into.push(self.eval(e)?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_observed_and_flatten<O: VMObserver>(&mut self, observer: &mut O, elements: &[TypedNode], into: &mut Vec<Value>) -> Result<(), String> {
|
||||||
|
for e in elements {
|
||||||
|
match &e.kind {
|
||||||
|
BoundKind::Tuple { elements: sub } => self.eval_observed_and_flatten(observer, sub, into)?,
|
||||||
|
_ => into.push(self.eval_observed(observer, e)?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps values into stack slots based on the parameter pattern.
|
||||||
|
/// Returns the number of slots filled.
|
||||||
|
fn unpack(&mut self, pattern: &TypedNode, values: &[Value], offset: &mut usize) -> Result<(), String> {
|
||||||
|
match &pattern.kind {
|
||||||
|
BoundKind::Parameter { slot, .. } => {
|
||||||
|
let val = values.get(*offset).cloned().unwrap_or(Value::Void);
|
||||||
|
*offset += 1;
|
||||||
|
|
||||||
|
let frame = self.frames.last().ok_or("No call frame")?;
|
||||||
|
let abs_index = frame.stack_base + (*slot as usize);
|
||||||
|
|
||||||
|
if abs_index == self.stack.len() {
|
||||||
|
self.stack.push(val);
|
||||||
|
} else if abs_index < self.stack.len() {
|
||||||
|
self.stack[abs_index] = val;
|
||||||
|
} else {
|
||||||
|
return Err(format!("Stack gap during unpack at slot {}", slot));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
BoundKind::Tuple { elements } => {
|
||||||
|
// If the current value at offset is a List, we dive into it.
|
||||||
|
// Otherwise, we assume the list was already flattened (e.g. by Specializer).
|
||||||
|
if let Some(Value::List(l)) = values.get(*offset) {
|
||||||
|
*offset += 1;
|
||||||
|
let mut sub_offset = 0;
|
||||||
|
for el in elements {
|
||||||
|
self.unpack(el, l, &mut sub_offset)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for el in elements {
|
||||||
|
self.unpack(el, values, offset)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Err("Invalid node in parameter pattern".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -581,13 +763,14 @@ mod tests {
|
|||||||
identity: id.clone(),
|
identity: id.clone(),
|
||||||
ty: StaticType::Any,
|
ty: StaticType::Any,
|
||||||
kind: BoundKind::Lambda {
|
kind: BoundKind::Lambda {
|
||||||
params: Box::new(Node {
|
params: Rc::new(Node {
|
||||||
identity: id.clone(),
|
identity: id.clone(),
|
||||||
ty: StaticType::Tuple(vec![]),
|
ty: StaticType::Tuple(vec![]),
|
||||||
kind: BoundKind::Tuple { elements: vec![] },
|
kind: BoundKind::Tuple { elements: vec![] },
|
||||||
}),
|
}),
|
||||||
upvalues: vec![Address::Local(0)], // Capture x
|
upvalues: vec![Address::Local(0)], // Capture x
|
||||||
body: Rc::new(lambda_body),
|
body: Rc::new(lambda_body),
|
||||||
|
positional_count: Some(0),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -183,4 +183,20 @@ mod tests {
|
|||||||
panic!("Expected DateTime, got {:?}", res);
|
panic!("Expected DateTime, got {:?}", res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dynamic_call_destructuring_underflow() {
|
||||||
|
let env = Environment::new();
|
||||||
|
let source = "(do
|
||||||
|
(def call-dynamic (fn [f data] (f data)))
|
||||||
|
(def data [10 [20 30]])
|
||||||
|
(def x (fn [[a [b c]]] (+ a (+ b c))))
|
||||||
|
(call-dynamic x data))";
|
||||||
|
|
||||||
|
let result = env.run_script(source);
|
||||||
|
if let Err(e) = &result {
|
||||||
|
panic!("Failed: {}", e);
|
||||||
|
}
|
||||||
|
assert_eq!(format!("{}", result.unwrap()), "60");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user