GROOVY-11905: Optimize non-capturing lambdas#2438
GROOVY-11905: Optimize non-capturing lambdas#2438daniellansun wants to merge 1 commit intomasterfrom
Conversation
1b01ffe to
a5a53a8
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2438 +/- ##
==================================================
+ Coverage 66.4917% 66.5402% +0.0485%
- Complexity 30294 30334 +40
==================================================
Files 1412 1413 +1
Lines 118108 118315 +207
Branches 20999 21054 +55
==================================================
+ Hits 78532 78727 +195
Misses 33095 33095
- Partials 6481 6493 +12
🚀 New features to boost your workflow:
|
738ec2b to
e0e80a6
Compare
a5d3dfb to
bbd31a1
Compare
There was a problem hiding this comment.
Pull request overview
This PR implements GROOVY-11905 by optimizing statically-compiled, non-capturing lambdas to avoid per-invocation allocations by emitting a static doCall target and using capture-free invokedynamic/LambdaMetafactory bootstraps, with extensive new bytecode and behavioral tests.
Changes:
- Add a lambda analysis pass to detect instance-member capture and qualify outer static member references to remain capture-free.
- Update static-compilation lambda bytecode generation to use
H_INVOKESTATIC/no captured receiver when safe, and adjust serializable-lambda deserialization helpers accordingly. - Expand test coverage for non-capturing vs capturing lambdas (behavioral + bytecode assertions) and update an existing type-annotation expectation.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/test/groovy/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy | Updates expected lambda doCall signature to match new static non-capturing implementation. |
| src/test/groovy/groovy/transform/stc/LambdaTest.groovy | Adds a large nested test suite covering non-capturing/capturing semantics and emitted bytecode patterns. |
| src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java | Implements capture-free invokedynamic generation for non-capturing lambdas and refactors serialize/deserialize support. |
| src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaAnalyzer.java | New analyzer to detect capture and qualify outer static member references for static doCall. |
| src/main/java/org/codehaus/groovy/ast/ClassNode.java | Caches getOuterClasses() result. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private List<ClassNode> outerClasses; | ||
| public List<ClassNode> getOuterClasses() { | ||
| List<ClassNode> ocs = outerClasses; | ||
| if (ocs != null) return ocs; | ||
|
|
||
| ClassNode outer = getOuterClass(); | ||
| if (outer == null) { | ||
| return Collections.emptyList(); | ||
| return outerClasses = Collections.emptyList(); | ||
| } | ||
| List<ClassNode> result = new ArrayList<>(4); | ||
| do { | ||
| result.add(outer); | ||
| } while ((outer = outer.getOuterClass()) != null); | ||
|
|
||
| return result; | ||
| return outerClasses = Collections.unmodifiableList(result); |
There was a problem hiding this comment.
getOuterClasses() now caches the computed list in a field, but the implementation doesn’t account for redirect/proxy ClassNodes. Since setRedirect(...) can be called later for non-primary nodes, a proxy may cache Collections.emptyList() (or an outdated chain) before redirect is set/changed and then return stale results thereafter. Consider delegating to redirect().getOuterClasses() when redirect != null and/or clearing outerClasses in setRedirect(...) to keep the cache correct.
bbd31a1 to
176cdd4
Compare
176cdd4 to
078cdef
Compare
https://issues.apache.org/jira/browse/GROOVY-11905