Skip to content

Commit 738ec2b

Browse files
committed
GROOVY-11905: Optimize non-capturing lambdas
1 parent 3bc4500 commit 738ec2b

File tree

3 files changed

+342
-27
lines changed

3 files changed

+342
-27
lines changed

src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import static org.objectweb.asm.Opcodes.ALOAD;
7373
import static org.objectweb.asm.Opcodes.CHECKCAST;
7474
import static org.objectweb.asm.Opcodes.DUP;
75+
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
7576
import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL;
7677
import static org.objectweb.asm.Opcodes.ICONST_0;
7778
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
@@ -113,27 +114,47 @@ public void writeLambda(final LambdaExpression expression) {
113114
ClassNode lambdaClass = getOrAddLambdaClass(expression, abstractMethod);
114115
MethodNode lambdaMethod = lambdaClass.getMethods(DO_CALL).get(0);
115116

117+
Parameter[] lambdaSharedVariables = expression.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
118+
boolean accessingInstanceMembers = isAccessingInstanceMembersOfEnclosingClass(lambdaMethod);
119+
// For non-capturing lambdas: make doCall static and use a capture-free invokedynamic,
120+
// so LambdaMetafactory creates a singleton instance — just like Java non-capturing lambdas.
121+
boolean isNonCapturing = lambdaSharedVariables.length == 0 && !accessingInstanceMembers;
116122
boolean canDeserialize = controller.getClassNode().hasMethod(createDeserializeLambdaMethodName(lambdaClass), createDeserializeLambdaMethodParams());
117-
if (!canDeserialize) {
123+
124+
if (isNonCapturing) {
125+
lambdaMethod.setModifiers(lambdaMethod.getModifiers() | ACC_STATIC);
126+
if (expression.isSerializable() && !canDeserialize) {
127+
addDeserializeLambdaMethodForEachLambdaExpression(expression, lambdaClass, true);
128+
addDeserializeLambdaMethod();
129+
}
130+
} else if (!canDeserialize) {
118131
if (expression.isSerializable()) {
119-
addDeserializeLambdaMethodForEachLambdaExpression(expression, lambdaClass);
132+
addDeserializeLambdaMethodForEachLambdaExpression(expression, lambdaClass, false);
120133
addDeserializeLambdaMethod();
121134
}
122-
newGroovyLambdaWrapperAndLoad(lambdaClass, expression, isAccessingInstanceMembersOfEnclosingClass(lambdaMethod));
135+
newGroovyLambdaWrapperAndLoad(lambdaClass, expression, accessingInstanceMembers);
123136
}
124137

125138
MethodVisitor mv = controller.getMethodVisitor();
126139
mv.visitInvokeDynamicInsn(
127140
abstractMethod.getName(),
128-
createAbstractMethodDesc(functionalType.redirect(), lambdaClass),
141+
isNonCapturing
142+
? BytecodeHelper.getMethodDescriptor(functionalType.redirect(), Parameter.EMPTY_ARRAY)
143+
: createAbstractMethodDesc(functionalType.redirect(), lambdaClass),
129144
createBootstrapMethod(controller.getClassNode().isInterface(), expression.isSerializable()),
130-
createBootstrapMethodArguments(createMethodDescriptor(abstractMethod), H_INVOKEVIRTUAL, lambdaClass, lambdaMethod, lambdaMethod.getParameters(), expression.isSerializable())
145+
createBootstrapMethodArguments(createMethodDescriptor(abstractMethod),
146+
isNonCapturing ? H_INVOKESTATIC : H_INVOKEVIRTUAL,
147+
lambdaClass, lambdaMethod, lambdaMethod.getParameters(), expression.isSerializable())
131148
);
132149
if (expression.isSerializable()) {
133150
mv.visitTypeInsn(CHECKCAST, "java/io/Serializable");
134151
}
135152

136-
controller.getOperandStack().replace(functionalType.redirect(), 1);
153+
if (isNonCapturing) {
154+
controller.getOperandStack().push(functionalType.redirect());
155+
} else {
156+
controller.getOperandStack().replace(functionalType.redirect(), 1);
157+
}
137158
}
138159

139160
private static Parameter[] createDeserializeLambdaMethodParams() {
@@ -325,27 +346,32 @@ private void addDeserializeLambdaMethod() {
325346
code);
326347
}
327348

328-
private void addDeserializeLambdaMethodForEachLambdaExpression(final LambdaExpression expression, final ClassNode lambdaClass) {
349+
private void addDeserializeLambdaMethodForEachLambdaExpression(final LambdaExpression expression, final ClassNode lambdaClass, final boolean isNonCapturing) {
329350
ClassNode enclosingClass = controller.getClassNode();
330-
Statement code = block(
331-
new BytecodeSequence(new BytecodeInstruction() {
332-
@Override
333-
public void visit(final MethodVisitor mv) {
334-
mv.visitVarInsn(ALOAD, 0);
335-
mv.visitInsn(ICONST_0);
336-
mv.visitMethodInsn(
337-
INVOKEVIRTUAL,
338-
"java/lang/invoke/SerializedLambda",
339-
"getCapturedArg",
340-
"(I)Ljava/lang/Object;",
341-
false);
342-
mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(lambdaClass));
343-
OperandStack operandStack = controller.getOperandStack();
344-
operandStack.push(lambdaClass);
345-
}
346-
}),
347-
returnS(expression)
348-
);
351+
Statement code;
352+
if (isNonCapturing) {
353+
code = block(returnS(expression));
354+
} else {
355+
code = block(
356+
new BytecodeSequence(new BytecodeInstruction() {
357+
@Override
358+
public void visit(final MethodVisitor mv) {
359+
mv.visitVarInsn(ALOAD, 0);
360+
mv.visitInsn(ICONST_0);
361+
mv.visitMethodInsn(
362+
INVOKEVIRTUAL,
363+
"java/lang/invoke/SerializedLambda",
364+
"getCapturedArg",
365+
"(I)Ljava/lang/Object;",
366+
false);
367+
mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(lambdaClass));
368+
OperandStack operandStack = controller.getOperandStack();
369+
operandStack.push(lambdaClass);
370+
}
371+
}),
372+
returnS(expression)
373+
);
374+
}
349375

350376
enclosingClass.addSyntheticMethod(
351377
createDeserializeLambdaMethodName(lambdaClass),

0 commit comments

Comments
 (0)