paulk-asert commented on code in PR #2680:
URL: https://github.com/apache/groovy/pull/2680#discussion_r3558244936


##########
src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java:
##########
@@ -253,29 +272,206 @@ private String createLambdaFactoryMethodDescriptor(final 
ClassNode functionalInt
 
     private GeneratedLambda getOrAddGeneratedLambda(final LambdaExpression 
expression, final MethodNode abstractMethod) {
         return generatedLambdas.computeIfAbsent(expression, expr -> {
+            // Build the lambda class first: this resolves 
capture/instance-member analysis correctly (a
+            // doCall on the lambda class sees the enclosing class as an 
outer), and makes doCall static iff
+            // the lambda is non-capturing.
             ClassNode lambdaClass = createLambdaClass(expr, ACC_FINAL | 
ACC_PUBLIC | ACC_STATIC, abstractMethod);
+            MethodNode lambdaMethod = getLambdaMethod(lambdaClass);
+
+            // Like Java, a non-capturing (static doCall), non-serializable 
lambda needs no generated class:
+            // hoist its static impl onto the enclosing class and bootstrap 
the metafactory directly against it.
+            // Opt-in (default off) while IDE/tooling compatibility is 
verified; set -Dgroovy.target.lambda.hoist=true.
+            // Only hoist lambdas sitting directly in a real method: a lambda 
inside a closure/lambda would be
+            // hoisted onto that generated function rather than a user class, 
so leave those as generated classes.
+            boolean baseHoistable = isLambdaHoistEnabled() && 
!controller.isInGeneratedFunction()
+                    && !expr.isSerializable() && 
!containsNestedFunction(expr.getCode());
+
+            if (baseHoistable && !requiresLambdaInstance(lambdaMethod)) {
+                MethodNode implMethod = 
hoistLambdaMethodToEnclosingClass(lambdaMethod);
+                return new GeneratedLambda(controller.getClassNode(), 
implMethod, null, Parameter.EMPTY_ARRAY, true, false, null);
+            }
+
+            // Capturing but needing no 'this': hoist onto the enclosing class 
as a static method taking the
+            // captured values as leading params, with the metafactory 
capturing them directly (like Java) -
+            // eliminating the lambda class, its instance, and the Reference 
wrapping. Read-only captures only.
+            Parameter[] shared = getStoredLambdaSharedVariables(expr);
+            if (baseHoistable && shared.length != 0 && 
!lambdaAnalyzer.accessesInstanceMembers(lambdaMethod)
+                    && !writesAnyCapture(lambdaMethod.getCode(), shared)) {
+                GeneratedLambda hoisted = hoistCapturingLambda(lambdaClass, 
lambdaMethod, shared);
+                if (hoisted != null) return hoisted;
+            }
+
             controller.getAcg().addInnerClass(lambdaClass);
             lambdaClass.addInterface(GENERATED_LAMBDA_TYPE);
             
lambdaClass.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, 
Boolean.TRUE);
             lambdaClass.putNodeMetaData(WriterControllerFactory.class, 
(WriterControllerFactory) x -> controller);
-            MethodNode lambdaMethod = getLambdaMethod(lambdaClass);
             return new GeneratedLambda(
                 lambdaClass,
                 lambdaMethod,
                 getGeneratedConstructor(lambdaClass),
                 getStoredLambdaSharedVariables(expr),
                 !requiresLambdaInstance(lambdaMethod),
-                lambdaAnalyzer.accessesInstanceMembers(lambdaMethod)
+                lambdaAnalyzer.accessesInstanceMembers(lambdaMethod),
+                null
             );
         });
     }
 
+    /**
+     * Re-homes a non-capturing lambda's already-prepared {@code static 
doCall} (types resolved, outer static
+     * references qualified) as a {@code private static} synthetic method on 
the enclosing class, so no per-lambda
+     * inner class is generated and the metafactory bootstraps directly 
against the enclosing class.
+     */
+    private MethodNode hoistLambdaMethodToEnclosingClass(final MethodNode 
lambdaMethod) {
+        MethodNode implMethod = new MethodNode(nextLambdaImplMethodName(),
+                ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, 
lambdaMethod.getReturnType(),
+                lambdaMethod.getParameters(), lambdaMethod.getExceptions(), 
lambdaMethod.getCode());
+        implMethod.setSourcePosition(lambdaMethod);
+        
implMethod.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, 
Boolean.TRUE);
+        addGeneratedMethod(controller.getClassNode(), implMethod);
+        // Added after the ReturnAdder phase (an inner class would get it via 
full compilation), so wire the
+        // implicit return for an expression-bodied lambda here (idempotent 
when returns already present).
+        new org.codehaus.groovy.classgen.ReturnAdder().visitMethod(implMethod);
+        return implMethod;
+    }
+
+    /**
+     * Hoists a read-only capturing lambda onto the enclosing class as a 
static method taking the captured
+     * values as leading parameters. The lambda-class {@code doCall} already 
reads its captures from Reference
+     * fields (accessedVariable = FieldNode); repointing those accesses to 
plain value parameters makes codegen
+     * load the value directly, and the metafactory then captures the values 
as factory arguments.
+     */
+    private GeneratedLambda hoistCapturingLambda(final ClassNode lambdaClass, 
final MethodNode lambdaMethod, final Parameter[] shared) {
+        Parameter[] samParams = lambdaMethod.getParameters();
+        Parameter[] valueParams = new Parameter[shared.length];
+        Map<String, Parameter> byName = new HashMap<>();
+        for (int i = 0; i < shared.length; i++) {
+            Parameter p = new Parameter(shared[i].getOriginType(), 
shared[i].getName());
+            valueParams[i] = p;
+            byName.put(p.getName(), p);
+        }
+        Parameter[] implParams = new Parameter[valueParams.length + 
samParams.length];
+        System.arraycopy(valueParams, 0, implParams, 0, valueParams.length);
+        System.arraycopy(samParams, 0, implParams, valueParams.length, 
samParams.length);
+
+        Statement body = lambdaMethod.getCode();
+        rebindCapturedFieldsToValueParams(body, byName);
+
+        MethodNode implMethod = new MethodNode(nextLambdaImplMethodName(),
+                ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, 
lambdaMethod.getReturnType(),
+                implParams, lambdaMethod.getExceptions(), body);
+        implMethod.setSourcePosition(lambdaMethod);
+        
implMethod.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, 
Boolean.TRUE);
+        addGeneratedMethod(controller.getClassNode(), implMethod);
+        new org.codehaus.groovy.classgen.ReturnAdder().visitMethod(implMethod);
+
+        // nonCapturing=true so no lambda instance is loaded; 
capturedValueParams drives the dedicated factory.
+        return new GeneratedLambda(controller.getClassNode(), implMethod, 
null, Parameter.EMPTY_ARRAY, true, false, valueParams);
+    }
+
+    /** Repoints captured Reference-field accesses in a hoisted body to plain 
value parameters. */
+    private static void rebindCapturedFieldsToValueParams(final Statement 
body, final Map<String, Parameter> valueParams) {
+        body.visit(new CodeVisitorSupport() {
+            @Override public void visitVariableExpression(final 
VariableExpression ve) {
+                Parameter p = valueParams.get(ve.getName());
+                if (p != null && ve.getAccessedVariable() instanceof 
FieldNode) {
+                    ve.setAccessedVariable(p);
+                    ve.setClosureSharedVariable(false);
+                }
+            }
+        });
+    }
+
+    /**
+     * True if the body assigns (or increments) a captured variable — such a 
lambda needs the shared Reference,
+     * so decline the value-capture hoist. Matched by name (conservative: 
never value-captures a mutated capture).
+     */
+    private static boolean writesAnyCapture(final Statement body, final 
Parameter[] shared) {
+        Set<String> names = new HashSet<>();
+        for (Parameter p : shared) names.add(p.getName());
+        boolean[] written = {false};
+        body.visit(new CodeVisitorSupport() {
+            @Override public void visitBinaryExpression(final BinaryExpression 
be) {
+                if (Types.isAssignment(be.getOperation().getType())) 
flagIfCaptured(be.getLeftExpression());
+                super.visitBinaryExpression(be);
+            }
+            @Override public void visitPostfixExpression(final 
PostfixExpression pe) { flagIfCaptured(pe.getExpression()); 
super.visitPostfixExpression(pe); }
+            @Override public void visitPrefixExpression(final PrefixExpression 
pe) { flagIfCaptured(pe.getExpression()); super.visitPrefixExpression(pe); }
+            private void flagIfCaptured(final Expression e) {
+                if (e instanceof VariableExpression && 
names.contains(((VariableExpression) e).getName())) {
+                    written[0] = true;
+                }
+            }
+        });
+        return written[0];
+    }
+
+    /**
+     * Emits the invokedynamic for a hoisted capturing lambda: push the 
captured values as metafactory factory
+     * arguments and bootstrap against the static impl method on the enclosing 
class (H_INVOKESTATIC).
+     */
+    private void writeHoistedCapturingLambda(final ClassNode functionalType, 
final MethodNode abstractMethod, final GeneratedLambda gl, final boolean 
serializable, final ClassNode[] markers) {
+        Parameter[] captured = gl.capturedValueParams();
+        for (Parameter p : captured) {
+            new VariableExpression(p.getName()).visit(controller.getAcg()); // 
load captured value (unwraps the Reference)
+            controller.getOperandStack().doGroovyCast(p.getType());
+        }
+        Parameter[] full = gl.lambdaMethod().getParameters();
+        Parameter[] samParams = Arrays.copyOfRange(full, captured.length, 
full.length);
+        writeFunctionalInterfaceIndy(
+            controller.getMethodVisitor(),
+            abstractMethod.getName(),
+            createFunctionalInterfaceFactoryDescriptor(functionalType, 
captured),
+            createMethodDescriptor(abstractMethod),
+            H_INVOKESTATIC,
+            controller.getClassNode(),
+            gl.lambdaMethod(),
+            samParams,
+            serializable,
+            markers);
+        controller.getOperandStack().replace(functionalType, captured.length);
+    }
+
     /** {@inheritDoc} */
     @Override
     protected ClassNode createClosureClass(final ClosureExpression expression, 
final int modifiers) {
         return staticTypesClosureWriter.createClosureClass(expression, 
modifiers);
     }
 
+    private static boolean containsNestedFunction(final Statement code) {
+        if (code == null) return false;
+        boolean[] found = {false};
+        code.visit(new CodeVisitorSupport() {
+            @Override public void visitClosureExpression(final 
ClosureExpression e) { found[0] = true; }
+            @Override public void visitLambdaExpression(final LambdaExpression 
e) { found[0] = true; }
+        });

Review Comment:
   good catch - fixed.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to