[ 
https://issues.apache.org/jira/browse/GROOVY-12143?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18095134#comment-18095134
 ] 

ASF GitHub Bot commented on GROOVY-12143:
-----------------------------------------

blackdrag commented on code in PR #2680:
URL: https://github.com/apache/groovy/pull/2680#discussion_r3555804197


##########
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:
   This is fine for me, but why not use that fluent API you added before?





> GEP-27: lambda hoisting 
> ------------------------
>
>                 Key: GROOVY-12143
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12143
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to