Feat: Enable nested destructuring optimization

This commit introduces optimizations for nested destructuring, allowing
tuples and records to be flattened and matched directly against function
arguments. This significantly improves performance by enabling more
constant folding and reducing intermediate allocations.

The changes include:
- Modifying the `Binder` to correctly count nested parameters.
- Enhancing `flatten_tuple` in the `Optimizer` to handle records and NOP
  nodes.
- Updating `map_params_to_args` to recursively destructure nested
  compound arguments.
- Adding integration tests to verify the correctness of tuple-to-tuple
  and record-to-tuple destructuring optimizations.
This commit is contained in:
Michael Schimmel
2026-02-22 16:34:40 +01:00
parent c03b2af770
commit 2e8d5284c2
5 changed files with 68 additions and 23 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
;; Benchmark: 2.1us
;; Benchmark-Repeat: 961
;; Benchmark: 969ns
;; Benchmark-Repeat: 2078
;; Comprehensive Destructuring Test
;; Covers: Nested tuples, mixed params, dynamic passing
+2 -2
View File
@@ -2,8 +2,8 @@
;; This test calls a function that destructures a record
;; Current implementation is now zero-allocation for destructuring.
;; Output: 3
;; Benchmark: 698ns
;; Benchmark-Repeat: 2875
;; Benchmark: 129ns
;; Benchmark-Repeat: 15432
(do
(def process (fn [[x y]]
+15 -16
View File
@@ -247,24 +247,23 @@ 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 }
}
// 3. Static optimization: count total parameters needed in flat argument list
fn count_params(node: &BoundNode) -> Option<u32> {
match &node.kind {
BoundKind::Parameter { .. } => Some(1),
BoundKind::Tuple { elements } => {
let mut total = 0;
for e in elements {
total += count_params(e)?;
}
Some(total)
}
BoundKind::Nop => Some(0),
_ => None,
};
}
}
let positional_count = count_params(&params_bound);
Ok(self.make_node(
identity,
+33 -4
View File
@@ -695,10 +695,15 @@ impl Optimizer {
}
fn flatten_tuple(&self, node: AnalyzedNode, into: &mut Vec<AnalyzedNode>) {
if let BoundKind::Tuple { elements } = &node.kind {
for el in elements { self.flatten_tuple(el.clone(), into); }
} else if !matches!(node.kind, BoundKind::Nop) {
into.push(node);
match node.kind {
BoundKind::Tuple { elements } => {
for el in elements { self.flatten_tuple(el, into); }
}
BoundKind::Record { fields } => {
for (_, v) in fields { self.flatten_tuple(v, into); }
}
BoundKind::Nop => {}
_ => into.push(node),
}
}
@@ -725,6 +730,30 @@ impl Optimizer {
*offset += 1;
}
BoundKind::Tuple { elements } => {
// RECURSIVE DESTRUCTURING SUPPORT
// Check if the current argument at 'offset' is itself a Tuple or Record literal.
if let Some(arg) = args.get(*offset) {
let mut sub_args = Vec::new();
let is_compound = match &arg.kind {
BoundKind::Tuple { .. } | BoundKind::Record { .. } => {
self.flatten_tuple(arg.clone(), &mut sub_args);
true
}
_ => false,
};
if is_compound {
// Match inner elements against the flattened compound argument
let mut sub_offset = 0;
for el in elements {
self.map_params_to_args(el, &sub_args, &mut sub_offset, sub);
}
*offset += 1;
return;
}
}
// Fallback: Continue flat matching (original behavior)
for el in elements { self.map_params_to_args(el, args, offset, sub); }
}
_ => {}
+17
View File
@@ -278,4 +278,21 @@ mod tests {
}
assert_eq!(format!("{}", result.unwrap()), "60");
}
#[test]
fn test_nested_destructuring_optimization() {
let env = Environment::new();
// 1. Tuple-to-Tuple
let source_tuple = "((fn [[x y]] (+ x y)) [10 20])";
assert_eq!(format!("{}", env.run_script(source_tuple).unwrap()), "30");
let dump_tuple = env.dump_ast(source_tuple).unwrap();
assert!(dump_tuple.contains("Constant: 30"), "Nested tuple should be folded to 30. Dump:\n{}", dump_tuple);
// 2. Record-to-Tuple
let source_record = "((fn [[x y]] (+ x y)) {:a 5 :b 7})";
assert_eq!(format!("{}", env.run_script(source_record).unwrap()), "12");
let dump_record = env.dump_ast(source_record).unwrap();
assert!(dump_record.contains("Constant: 12"), "Record-to-Tuple destructuring should be folded to 12. Dump:\n{}", dump_record);
}
}