This is an automated email from the ASF dual-hosted git repository. sunlan pushed a commit to branch GROOVY-11905 in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 1b01ffee6d70278218a6ef9264c4deb8686c341f Author: Daniel Sun <[email protected]> AuthorDate: Sun Apr 5 22:28:54 2026 +0900 GROOVY-11905: Optimize non-capturing lambdas --- .../classgen/asm/sc/StaticTypesLambdaWriter.java | 37 ++++++++++++++++------ .../groovy/classgen/asm/TypeAnnotationsTest.groovy | 2 +- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java index b804702754..7843c8504e 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java @@ -72,6 +72,7 @@ import static org.objectweb.asm.Opcodes.ACC_STATIC; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.CHECKCAST; import static org.objectweb.asm.Opcodes.DUP; +import static org.objectweb.asm.Opcodes.H_INVOKESTATIC; import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.ICONST_0; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; @@ -113,27 +114,45 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun ClassNode lambdaClass = getOrAddLambdaClass(expression, abstractMethod); MethodNode lambdaMethod = lambdaClass.getMethods(DO_CALL).get(0); - boolean canDeserialize = controller.getClassNode().hasMethod(createDeserializeLambdaMethodName(lambdaClass), createDeserializeLambdaMethodParams()); - if (!canDeserialize) { - if (expression.isSerializable()) { - addDeserializeLambdaMethodForEachLambdaExpression(expression, lambdaClass); - addDeserializeLambdaMethod(); + Parameter[] lambdaSharedVariables = expression.getNodeMetaData(LAMBDA_SHARED_VARIABLES); + boolean accessingInstanceMembers = isAccessingInstanceMembersOfEnclosingClass(lambdaMethod); + // For non-capturing lambdas: make doCall static and use a capture-free invokedynamic, + // so LambdaMetafactory creates a singleton instance — just like Java non-capturing lambdas. + boolean isNonCapturing = lambdaSharedVariables.length == 0 && !accessingInstanceMembers && !expression.isSerializable(); + + if (isNonCapturing) { + lambdaMethod.setModifiers(lambdaMethod.getModifiers() | ACC_STATIC); + } else { + boolean canDeserialize = controller.getClassNode().hasMethod(createDeserializeLambdaMethodName(lambdaClass), createDeserializeLambdaMethodParams()); + if (!canDeserialize) { + if (expression.isSerializable()) { + addDeserializeLambdaMethodForEachLambdaExpression(expression, lambdaClass); + addDeserializeLambdaMethod(); + } + newGroovyLambdaWrapperAndLoad(lambdaClass, expression, accessingInstanceMembers); } - newGroovyLambdaWrapperAndLoad(lambdaClass, expression, isAccessingInstanceMembersOfEnclosingClass(lambdaMethod)); } MethodVisitor mv = controller.getMethodVisitor(); mv.visitInvokeDynamicInsn( abstractMethod.getName(), - createAbstractMethodDesc(functionalType.redirect(), lambdaClass), + isNonCapturing + ? BytecodeHelper.getMethodDescriptor(functionalType.redirect(), Parameter.EMPTY_ARRAY) + : createAbstractMethodDesc(functionalType.redirect(), lambdaClass), createBootstrapMethod(controller.getClassNode().isInterface(), expression.isSerializable()), - createBootstrapMethodArguments(createMethodDescriptor(abstractMethod), H_INVOKEVIRTUAL, lambdaClass, lambdaMethod, lambdaMethod.getParameters(), expression.isSerializable()) + createBootstrapMethodArguments(createMethodDescriptor(abstractMethod), + isNonCapturing ? H_INVOKESTATIC : H_INVOKEVIRTUAL, + lambdaClass, lambdaMethod, lambdaMethod.getParameters(), expression.isSerializable()) ); if (expression.isSerializable()) { mv.visitTypeInsn(CHECKCAST, "java/io/Serializable"); } - controller.getOperandStack().replace(functionalType.redirect(), 1); + if (isNonCapturing) { + controller.getOperandStack().push(functionalType.redirect()); + } else { + controller.getOperandStack().replace(functionalType.redirect(), 1); + } } private static Parameter[] createDeserializeLambdaMethodParams() { diff --git a/src/test/groovy/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy index c5270ddec5..cdcf626338 100644 --- a/src/test/groovy/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy +++ b/src/test/groovy/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy @@ -299,7 +299,7 @@ final class TypeAnnotationsTest extends AbstractBytecodeTestCase { } ''') assert bytecode.hasStrictSequence([ - 'public doCall(I)I', + 'public static doCall(I)I', '@LTypeAnno1;() : METHOD_FORMAL_PARAMETER 0, null', 'L0' ])
