From 2e8d5284c22489b62021c22912725b4532702b3b Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Sun, 22 Feb 2026 16:34:40 +0100 Subject: [PATCH] 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. --- examples/destructuring.myc | 4 ++-- examples/record_unpack.myc | 4 ++-- src/ast/compiler/binder.rs | 29 +++++++++++++-------------- src/ast/compiler/optimizer.rs | 37 +++++++++++++++++++++++++++++++---- src/integration_test.rs | 17 ++++++++++++++++ 5 files changed, 68 insertions(+), 23 deletions(-) diff --git a/examples/destructuring.myc b/examples/destructuring.myc index ab53f72..23ad46e 100644 --- a/examples/destructuring.myc +++ b/examples/destructuring.myc @@ -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 diff --git a/examples/record_unpack.myc b/examples/record_unpack.myc index 568fcba..f1d99ef 100644 --- a/examples/record_unpack.myc +++ b/examples/record_unpack.myc @@ -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]] (+ x y))) diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index f3ace9d..187d151 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -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 ¶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; + // 3. Static optimization: count total parameters needed in flat argument list + fn count_params(node: &BoundNode) -> Option { + match &node.kind { + BoundKind::Parameter { .. } => Some(1), + BoundKind::Tuple { elements } => { + let mut total = 0; + for e in elements { + total += count_params(e)?; } + Some(total) } - if all_params { Some(count) } else { None } + BoundKind::Nop => Some(0), + _ => None, } - BoundKind::Parameter { .. } => Some(1), - _ => None, - }; + } + + let positional_count = count_params(¶ms_bound); Ok(self.make_node( identity, diff --git a/src/ast/compiler/optimizer.rs b/src/ast/compiler/optimizer.rs index 094aaff..70c4e93 100644 --- a/src/ast/compiler/optimizer.rs +++ b/src/ast/compiler/optimizer.rs @@ -695,10 +695,15 @@ impl Optimizer { } fn flatten_tuple(&self, node: AnalyzedNode, into: &mut Vec) { - 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); } } _ => {} diff --git a/src/integration_test.rs b/src/integration_test.rs index d97fd74..973d33f 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -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); + } }