Refactor lambda binding and parameter handling

Introduce `BoundKind::Parameter` to represent function parameters.
Modify `Binder` to correctly handle parameters within lambda
definitions, ensuring they are only defined within function scopes.
Update `LambdaCollector` to register the body of lambdas as templates
for global definitions.
Adjust `Dumper` to accurately represent lambda parameters.
Update `Specializer` and `TCO` to handle the new `BoundKind::Parameter`.
Refactor `Call` and `TailCall` to use a single `args` node, often a
tuple.
Adjust type signatures in RTL to use `StaticType::Tuple` for function
parameters.
This commit is contained in:
Michael Schimmel
2026-02-20 12:14:22 +01:00
parent 8d6d3be5c2
commit e2279f214b
17 changed files with 497 additions and 247 deletions
+56 -9
View File
@@ -102,6 +102,8 @@ macro_rules! dispatch_eval {
BoundKind::Nop => Ok(Value::Void),
BoundKind::Constant(v) => Ok(v.clone()),
BoundKind::Parameter { .. } => Ok(Value::Void),
BoundKind::DefGlobal { global_index, value, .. } => {
let val = $self.$eval_method($($observer,)? value)?;
let idx = *global_index as usize;
@@ -161,7 +163,10 @@ macro_rules! dispatch_eval {
Ok(last)
},
BoundKind::Lambda { param_count: _, upvalues, body } => {
BoundKind::Lambda { params: _, upvalues, body } => {
// PERFORMANCE: Creating a closure captures upvalues.
// The actual execution of the lambda (in Call branch) now skips
// the Lambda node itself and jumps directly to the body.
let mut captured = Vec::with_capacity(upvalues.len());
for addr in upvalues {
captured.push($self.capture_upvalue(*addr)?);
@@ -177,9 +182,28 @@ macro_rules! dispatch_eval {
BoundKind::TailCall { callee, args } => {
let func_val = $self.$eval_method($($observer,)? callee)?;
let mut arg_vals = Vec::with_capacity(args.len());
for arg in args {
arg_vals.push($self.$eval_method($($observer,)? arg)?);
// PERFORMANCE OPTIMIZATION: "Everything is a Tuple" Unification
// 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 } => {
arg_vals.reserve(elements.len());
for e in elements {
arg_vals.push($self.$eval_method($($observer,)? e)?);
}
}
_ => {
// Fallback for dynamic tuples (e.g. arguments passed as a variable)
let v = $self.$eval_method($($observer,)? args)?;
if let Value::List(l) = v {
arg_vals = (*l).clone();
} else {
arg_vals.push(v);
}
}
}
match func_val {
@@ -192,9 +216,24 @@ macro_rules! dispatch_eval {
BoundKind::Call { callee, args } => {
let mut func_val = $self.$eval_method($($observer,)? callee)?;
let mut arg_vals = Vec::with_capacity(args.len());
for arg in args {
arg_vals.push($self.$eval_method($($observer,)? arg)?);
// PERFORMANCE OPTIMIZATION: Same as in TailCall above.
// Short-circuiting the Tuple -> Value::List -> Vec conversion to save heap cycles.
let mut arg_vals = Vec::new();
match &args.kind {
BoundKind::Tuple { elements } => {
arg_vals.reserve(elements.len());
for e in elements {
arg_vals.push($self.$eval_method($($observer,)? e)?);
}
}
_ => {
let v = $self.$eval_method($($observer,)? args)?;
if let Value::List(l) = v {
arg_vals = (*l).clone();
} else {
arg_vals.push(v);
}
}
}
loop {
@@ -542,7 +581,11 @@ mod tests {
identity: id.clone(),
ty: StaticType::Any,
kind: BoundKind::Lambda {
param_count: 0,
params: Box::new(Node {
identity: id.clone(),
ty: StaticType::Tuple(vec![]),
kind: BoundKind::Tuple { elements: vec![] },
}),
upvalues: vec![Address::Local(0)], // Capture x
body: Rc::new(lambda_body),
},
@@ -559,7 +602,11 @@ mod tests {
ty: StaticType::Any,
kind: BoundKind::Get { addr: Address::Local(1), name: Symbol::from("f") },
}),
args: vec![],
args: Box::new(Node {
identity: id.clone(),
ty: StaticType::Tuple(vec![]),
kind: BoundKind::Tuple { elements: vec![] },
}),
},
},
// return x (Local 0)