Add positional_count field to Lambda

This field is used for static optimization, determining if parameters
are purely positional.
This commit is contained in:
Michael Schimmel
2026-02-20 14:40:56 +01:00
parent 56d6c3bbde
commit 4e812c1afb
11 changed files with 361 additions and 62 deletions
+21 -1
View File
@@ -200,10 +200,30 @@ impl Binder {
let compiled_fn = self.functions.pop().unwrap();
// 3. Static optimization: check if parameters are purely positional
let positional_count = match &params_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 {
params: Box::new(params_bound),
params: Rc::new(params_bound),
upvalues: compiled_fn.upvalues,
body: Rc::new(body_bound),
positional_count,
}))
},
+3 -1
View File
@@ -72,10 +72,12 @@ pub enum BoundKind<T = ()> {
},
Lambda {
params: Box<BoundNode<T>>,
params: Rc<BoundNode<T>>,
// The list of variables captured from enclosing scopes
upvalues: Vec<Address>,
body: Rc<BoundNode<T>>,
/// Static optimization: number of positional parameters if the pattern is flat.
positional_count: Option<u32>,
},
Call {
+1 -1
View File
@@ -117,7 +117,7 @@ impl Dumper {
self.indent -= 1;
}
BoundKind::Lambda { params, upvalues, body } => {
BoundKind::Lambda { params, upvalues, body, .. } => {
self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node);
self.indent += 1;
+31 -7
View File
@@ -69,10 +69,10 @@ impl Specializer {
let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect();
(BoundKind::Block { exprs }, node.ty)
},
BoundKind::Lambda { params, upvalues, body } => {
let params = Box::new(self.visit_node(*params));
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).clone()));
(BoundKind::Lambda { params, upvalues, body }, node.ty)
(BoundKind::Lambda { params, upvalues, body, positional_count }, node.ty)
},
BoundKind::DefLocal { name, slot, value, captured_by } => {
let value = Box::new(self.visit_node(*value));
@@ -194,15 +194,25 @@ impl Specializer {
// Store in cache
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 {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(res_val),
ty: StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(arg_types),
params: flattened_args.ty.clone(),
ret: res_ty.clone(),
})),
};
return (specialized_callee, new_args, res_ty);
return (specialized_callee, flattened_args, res_ty);
},
Err(_) => {
// Fallback on error
@@ -214,6 +224,19 @@ impl Specializer {
// Fallback: Dynamic Call
(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)]
@@ -268,7 +291,7 @@ mod tests {
let func_node = BoundNode {
identity: make_identity(),
kind: BoundKind::Lambda {
params: Box::new(BoundNode {
params: Rc::new(BoundNode {
identity: make_identity(),
kind: BoundKind::Tuple {
elements: vec![
@@ -282,7 +305,8 @@ mod tests {
ty: ()
}),
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: ()
};
+3 -2
View File
@@ -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.
let new_body = Rc::new(Self::transform((*body).clone(), true));
Node {
kind: BoundKind::Lambda {
params,
params: params.clone(),
upvalues,
body: new_body,
positional_count,
},
..node
}
+13 -10
View File
@@ -48,7 +48,7 @@ impl TypeChecker {
pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result<TypedNode, String> {
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)
let mut upvalue_types = Vec::with_capacity(upvalues.len());
for _ in &upvalues {
@@ -66,7 +66,7 @@ impl TypeChecker {
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
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
@@ -83,9 +83,10 @@ impl TypeChecker {
Ok(Node {
identity: node.identity,
kind: BoundKind::Lambda {
params: Box::new(params_typed),
params: Rc::new(params_typed),
upvalues,
body: Rc::new(body_typed)
body: Rc::new(body_typed),
positional_count,
},
ty: fn_ty,
})
@@ -95,13 +96,14 @@ impl TypeChecker {
let virtual_lambda = BoundNode {
identity: node.identity.clone(),
kind: BoundKind::Lambda {
params: Box::new(Node {
params: Rc::new(Node {
identity: node.identity.clone(),
kind: BoundKind::Tuple { elements: vec![] },
ty: (),
}),
upvalues: vec![],
body: Rc::new(node)
body: Rc::new(node),
positional_count: Some(0),
},
ty: (),
};
@@ -232,7 +234,7 @@ impl TypeChecker {
(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
let mut upvalue_types = Vec::with_capacity(upvalues.len());
for &addr in &upvalues {
@@ -243,7 +245,7 @@ impl TypeChecker {
let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(ctx));
// 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 ret_ty = body_typed.ty.clone();
@@ -254,9 +256,10 @@ impl TypeChecker {
}));
(BoundKind::Lambda {
params: Box::new(params_typed),
params: Rc::new(params_typed),
upvalues,
body: Rc::new(body_typed)
body: Rc::new(body_typed),
positional_count,
}, fn_ty)
},