Skip to content

Commit 078cdef

Browse files
committed
GROOVY-11905: Optimize non-capturing lambdas
1 parent d009dd2 commit 078cdef

File tree

4 files changed

+2117
-117
lines changed

4 files changed

+2117
-117
lines changed
Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.codehaus.groovy.classgen.asm.sc;
20+
21+
import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
22+
import org.codehaus.groovy.ast.ClassNode;
23+
import org.codehaus.groovy.ast.CodeVisitorSupport;
24+
import org.codehaus.groovy.ast.FieldNode;
25+
import org.codehaus.groovy.ast.MethodNode;
26+
import org.codehaus.groovy.ast.Parameter;
27+
import org.codehaus.groovy.ast.PropertyNode;
28+
import org.codehaus.groovy.ast.Variable;
29+
import org.codehaus.groovy.ast.expr.AttributeExpression;
30+
import org.codehaus.groovy.ast.expr.ClassExpression;
31+
import org.codehaus.groovy.ast.expr.Expression;
32+
import org.codehaus.groovy.ast.expr.MethodCallExpression;
33+
import org.codehaus.groovy.ast.expr.PropertyExpression;
34+
import org.codehaus.groovy.ast.expr.VariableExpression;
35+
import org.codehaus.groovy.control.SourceUnit;
36+
import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
37+
38+
import java.util.LinkedHashMap;
39+
import java.util.List;
40+
import java.util.Map;
41+
42+
import static org.apache.groovy.util.BeanUtils.capitalize;
43+
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
44+
import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET;
45+
46+
/**
47+
* Analysis helper for lambda expressions that determines whether a lambda
48+
* captures its enclosing instance or only references static members.
49+
* <p>
50+
* When a lambda is non-capturing (no shared variables and no instance member
51+
* access), the lambda writer can emit it as a static method with
52+
* {@code LambdaMetafactory}, avoiding per-call allocation.
53+
* <p>
54+
* This analyzer also qualifies outer-class static member references so that
55+
* the generated {@code doCall} method can invoke them without an enclosing
56+
* instance receiver.
57+
*/
58+
class StaticTypesLambdaAnalyzer {
59+
60+
StaticTypesLambdaAnalyzer(final SourceUnit sourceUnit) {
61+
this.sourceUnit = sourceUnit;
62+
}
63+
64+
boolean isNonCapturing(final MethodNode lambdaMethod, final Parameter[] sharedVariables) {
65+
return (sharedVariables == null || sharedVariables.length == 0)
66+
&& !accessesInstanceMembers(lambdaMethod);
67+
}
68+
69+
boolean accessesInstanceMembers(final MethodNode lambdaMethod) {
70+
Boolean accessingInstanceMembers = lambdaMethod.getNodeMetaData(MetaDataKey.ACCESSES_INSTANCE_MEMBERS);
71+
if (accessingInstanceMembers != null) return accessingInstanceMembers;
72+
73+
InstanceMemberAccessFinder finder = new InstanceMemberAccessFinder(resolveOuterStaticMemberOwner(lambdaMethod));
74+
lambdaMethod.getCode().visit(finder);
75+
76+
accessingInstanceMembers = finder.isAccessingInstanceMembers();
77+
lambdaMethod.putNodeMetaData(MetaDataKey.ACCESSES_INSTANCE_MEMBERS, accessingInstanceMembers);
78+
return accessingInstanceMembers;
79+
}
80+
81+
void qualifyOuterStaticMemberReferences(final MethodNode lambdaMethod) {
82+
lambdaMethod.getCode().visit(new OuterStaticMemberQualifier(sourceUnit, resolveOuterStaticMemberOwner(lambdaMethod)));
83+
}
84+
85+
private static OuterStaticMemberResolver resolveOuterStaticMemberOwner(final MethodNode lambdaMethod) {
86+
return new OuterStaticMemberResolver(lambdaMethod.getDeclaringClass().getOuterClasses());
87+
}
88+
89+
private static boolean isThisReceiver(final Expression expression) {
90+
return expression instanceof VariableExpression receiver && receiver.isThisExpression();
91+
}
92+
93+
private static boolean isEnclosingInstanceReceiver(final Expression expression) {
94+
return isThisReceiver(expression) || isQualifiedEnclosingInstanceReference(expression);
95+
}
96+
97+
private static boolean isQualifiedEnclosingInstanceReference(final Expression expression) {
98+
if (!(expression instanceof PropertyExpression propertyExpression)) return false;
99+
if (!(propertyExpression.getObjectExpression() instanceof ClassExpression)) return false;
100+
101+
String property = propertyExpression.getPropertyAsString();
102+
return "this".equals(property) || "super".equals(property);
103+
}
104+
105+
/**
106+
* Transforms unqualified outer-class static member references in a lambda
107+
* body into class-qualified references (e.g., {@code label} becomes
108+
* {@code Outer.label}), enabling the static {@code doCall} method to
109+
* invoke them without an enclosing instance.
110+
*/
111+
private static final class OuterStaticMemberQualifier extends ClassCodeExpressionTransformer {
112+
113+
private final SourceUnit sourceUnit;
114+
private final OuterStaticMemberResolver resolver;
115+
116+
private OuterStaticMemberQualifier(final SourceUnit sourceUnit, final OuterStaticMemberResolver resolver) {
117+
this.sourceUnit = sourceUnit;
118+
this.resolver = resolver;
119+
}
120+
121+
@Override
122+
protected SourceUnit getSourceUnit() {
123+
return sourceUnit;
124+
}
125+
126+
@Override
127+
public Expression transform(final Expression expression) {
128+
if (expression instanceof VariableExpression variableExpression) {
129+
Expression qualifiedReference = qualify(variableExpression);
130+
if (qualifiedReference != null) return qualifiedReference;
131+
}
132+
if (expression instanceof AttributeExpression attributeExpression) {
133+
Expression qualifiedReference = qualify(attributeExpression);
134+
if (qualifiedReference != null) return qualifiedReference;
135+
}
136+
if (expression instanceof PropertyExpression propertyExpression) {
137+
Expression qualifiedReference = qualify(propertyExpression);
138+
if (qualifiedReference != null) return qualifiedReference;
139+
}
140+
if (expression instanceof MethodCallExpression methodCallExpression) {
141+
Expression qualifiedReference = qualify(methodCallExpression);
142+
if (qualifiedReference != null) return qualifiedReference;
143+
}
144+
return super.transform(expression);
145+
}
146+
147+
private Expression qualify(final VariableExpression expression) {
148+
ClassNode owner = resolver.findOwner(expression);
149+
if (owner == null) return null;
150+
151+
PropertyExpression qualifiedReference = new PropertyExpression(classX(owner), expression.getName());
152+
qualifiedReference.setImplicitThis(false);
153+
qualifiedReference.copyNodeMetaData(expression);
154+
setSourcePosition(qualifiedReference, expression);
155+
return qualifiedReference;
156+
}
157+
158+
private Expression qualify(final AttributeExpression expression) {
159+
ClassNode owner = resolver.findOwner(expression);
160+
if (owner == null) return null;
161+
162+
AttributeExpression qualifiedReference = new AttributeExpression(
163+
classX(owner),
164+
transform(expression.getProperty()),
165+
expression.isSafe()
166+
);
167+
qualifiedReference.setImplicitThis(false);
168+
qualifiedReference.setSpreadSafe(expression.isSpreadSafe());
169+
qualifiedReference.copyNodeMetaData(expression);
170+
setSourcePosition(qualifiedReference, expression);
171+
return qualifiedReference;
172+
}
173+
174+
private Expression qualify(final PropertyExpression expression) {
175+
ClassNode owner = resolver.findOwner(expression);
176+
if (owner == null) return null;
177+
178+
PropertyExpression qualifiedReference = new PropertyExpression(
179+
classX(owner),
180+
transform(expression.getProperty()),
181+
expression.isSafe()
182+
);
183+
qualifiedReference.setImplicitThis(false);
184+
qualifiedReference.setSpreadSafe(expression.isSpreadSafe());
185+
qualifiedReference.copyNodeMetaData(expression);
186+
setSourcePosition(qualifiedReference, expression);
187+
return qualifiedReference;
188+
}
189+
190+
private Expression qualify(final MethodCallExpression expression) {
191+
ClassNode owner = resolver.findOwner(expression);
192+
if (owner == null) return null;
193+
194+
MethodCallExpression qualifiedReference = new MethodCallExpression(
195+
classX(owner),
196+
transform(expression.getMethod()),
197+
transform(expression.getArguments())
198+
);
199+
qualifiedReference.setImplicitThis(false);
200+
qualifiedReference.setSafe(expression.isSafe());
201+
qualifiedReference.setSpreadSafe(expression.isSpreadSafe());
202+
qualifiedReference.setGenericsTypes(expression.getGenericsTypes());
203+
qualifiedReference.setMethodTarget(expression.getMethodTarget());
204+
qualifiedReference.copyNodeMetaData(expression);
205+
setSourcePosition(qualifiedReference, expression);
206+
return qualifiedReference;
207+
}
208+
}
209+
210+
/**
211+
* Resolves outer-class static member references for a lambda body by
212+
* walking the enclosing class hierarchy (including supertypes and
213+
* interfaces) to determine ownership of static fields, properties, and
214+
* methods.
215+
*/
216+
private static final class OuterStaticMemberResolver {
217+
private final Map<String, ClassNode> referenceOwnerIndex;
218+
219+
private OuterStaticMemberResolver(final List<ClassNode> outerClasses) {
220+
this.referenceOwnerIndex = new LinkedHashMap<>();
221+
for (ClassNode outerClass : outerClasses) {
222+
collectReferenceOwners(outerClass, referenceOwnerIndex);
223+
}
224+
}
225+
226+
private ClassNode findOwner(final VariableExpression expression) {
227+
ClassNode owner = expression.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER);
228+
if (!isReferenceOwner(owner)) return null;
229+
230+
return isStaticReference(expression) ? owner : null;
231+
}
232+
233+
private ClassNode findOwner(final MethodCallExpression expression) {
234+
if (!expression.isImplicitThis() && !isQualifiedEnclosingInstanceReference(expression.getObjectExpression())) {
235+
return null;
236+
}
237+
238+
MethodNode directMethodCallTarget = expression.getMethodTarget();
239+
if (directMethodCallTarget == null || !directMethodCallTarget.isStatic()) return null;
240+
241+
ClassNode owner = directMethodCallTarget.getDeclaringClass();
242+
if (!isReferenceOwner(owner)) return null;
243+
244+
return isEnclosingInstanceReceiver(expression.getObjectExpression()) ? owner : null;
245+
}
246+
247+
private ClassNode findOwner(final PropertyExpression expression) {
248+
if (!expression.isImplicitThis() && !isQualifiedEnclosingInstanceReference(expression.getObjectExpression())) {
249+
return null;
250+
}
251+
252+
MethodNode directMethodCallTarget = expression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
253+
if (directMethodCallTarget != null && directMethodCallTarget.isStatic() && isReferenceOwner(directMethodCallTarget.getDeclaringClass())) {
254+
return directMethodCallTarget.getDeclaringClass();
255+
}
256+
257+
String propertyName = expression.getPropertyAsString();
258+
if (propertyName == null) return null;
259+
260+
for (ClassNode referenceOwner : referenceOwnerIndex.values()) {
261+
if (isStaticMemberNamed(propertyName, referenceOwner)) {
262+
return referenceOwner;
263+
}
264+
}
265+
266+
return null;
267+
}
268+
269+
private boolean isReferenceOwner(final ClassNode owner) {
270+
return owner != null && referenceOwnerIndex.containsKey(owner.redirect().getName());
271+
}
272+
273+
private static void collectReferenceOwners(final ClassNode owner, final Map<String, ClassNode> referenceOwnerIndex) {
274+
if (owner == null) return;
275+
276+
ClassNode redirectedOwner = owner.redirect();
277+
if (referenceOwnerIndex.putIfAbsent(redirectedOwner.getName(), redirectedOwner) != null) return;
278+
279+
collectReferenceOwners(redirectedOwner.getSuperClass(), referenceOwnerIndex);
280+
for (ClassNode interfaceNode : redirectedOwner.getInterfaces()) {
281+
collectReferenceOwners(interfaceNode, referenceOwnerIndex);
282+
}
283+
}
284+
285+
private static boolean isStaticReference(final VariableExpression expression) {
286+
Variable accessedVariable = expression.getAccessedVariable();
287+
288+
if (accessedVariable instanceof Parameter) return false;
289+
290+
return (accessedVariable instanceof FieldNode || accessedVariable instanceof PropertyNode)
291+
&& accessedVariable.isStatic();
292+
}
293+
294+
private static boolean isStaticMemberNamed(final String propertyName, final ClassNode owner) {
295+
FieldNode field = owner.getField(propertyName);
296+
if (field != null && field.isStatic()) return true;
297+
298+
PropertyNode property = owner.getProperty(propertyName);
299+
if (property != null && property.isStatic()) return true;
300+
301+
String capitalizedPropertyName = capitalize(propertyName);
302+
MethodNode getter = owner.getGetterMethod("is" + capitalizedPropertyName);
303+
if (getter == null) getter = owner.getGetterMethod("get" + capitalizedPropertyName);
304+
305+
return getter != null && getter.isStatic();
306+
}
307+
}
308+
309+
/**
310+
* Visits a lambda body to detect any reference to enclosing-instance
311+
* members. Short-circuits on the first instance member access found.
312+
*/
313+
private static final class InstanceMemberAccessFinder extends CodeVisitorSupport {
314+
315+
private final OuterStaticMemberResolver resolver;
316+
private boolean accessingInstanceMembers;
317+
318+
private InstanceMemberAccessFinder(final OuterStaticMemberResolver resolver) {
319+
this.resolver = resolver;
320+
}
321+
322+
@Override
323+
public void visitVariableExpression(final VariableExpression expression) {
324+
if (accessingInstanceMembers) return;
325+
if (expression.isThisExpression() || expression.isSuperExpression() || "thisObject".equals(expression.getName())) {
326+
accessingInstanceMembers = true;
327+
return;
328+
}
329+
330+
ClassNode owner = expression.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER);
331+
if (owner != null && resolver.isReferenceOwner(owner) && resolver.findOwner(expression) == null) {
332+
accessingInstanceMembers = true;
333+
return;
334+
}
335+
336+
super.visitVariableExpression(expression);
337+
}
338+
339+
@Override
340+
public void visitPropertyExpression(final PropertyExpression expression) {
341+
if (accessingInstanceMembers) return;
342+
if (resolver.findOwner(expression) != null) {
343+
expression.getProperty().visit(this);
344+
return;
345+
}
346+
if (isQualifiedEnclosingInstanceReference(expression)) {
347+
accessingInstanceMembers = true;
348+
return;
349+
}
350+
351+
super.visitPropertyExpression(expression);
352+
}
353+
354+
@Override
355+
public void visitAttributeExpression(final AttributeExpression expression) {
356+
if (accessingInstanceMembers) return;
357+
if (resolver.findOwner(expression) != null) {
358+
expression.getProperty().visit(this);
359+
return;
360+
}
361+
if (isQualifiedEnclosingInstanceReference(expression.getObjectExpression())) {
362+
accessingInstanceMembers = true;
363+
return;
364+
}
365+
366+
super.visitAttributeExpression(expression);
367+
}
368+
369+
@Override
370+
public void visitMethodCallExpression(final MethodCallExpression expression) {
371+
if (accessingInstanceMembers) return;
372+
if (resolver.findOwner(expression) != null) {
373+
expression.getMethod().visit(this);
374+
expression.getArguments().visit(this);
375+
return;
376+
}
377+
378+
super.visitMethodCallExpression(expression);
379+
}
380+
381+
private boolean isAccessingInstanceMembers() {
382+
return accessingInstanceMembers;
383+
}
384+
}
385+
386+
private enum MetaDataKey {
387+
ACCESSES_INSTANCE_MEMBERS
388+
}
389+
390+
private final SourceUnit sourceUnit;
391+
}

0 commit comments

Comments
 (0)