[
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)