Add access bridges for targeted lambda methods in D8.
Change-Id: I92194f8b525f513a24c32127e4a5fd41e6b9b7fc
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 8ba291b..86b96e8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -299,6 +300,8 @@
public final DexString thisName = createString("this");
public final DexString lambdaInstanceFieldName = createString(LAMBDA_INSTANCE_FIELD_NAME);
+ public final DexString javacLambdaMethodPrefix =
+ createString(LambdaClass.JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX);
// As much as possible, R8 should rely on the content of the static enum field, using
// enumMembers.isValuesFieldCandidate or checking the object state in the optimization info.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
index b4cab05..864a7b4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
@@ -12,6 +12,10 @@
/** Interface for desugaring a single class-file instruction. */
public interface CfInstructionDesugaring {
+ default void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
+ // Default scan is to do nothing.
+ }
+
/**
* Given an instruction, returns the list of instructions that the instruction should be desugared
* to. If no desugaring is needed, {@code null} should be returned (for efficiency).
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index a264d65..3b5a40b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
@@ -272,12 +273,6 @@
// Creates a delegation target for this particular lambda class. Note that we
// should always be able to create targets for the lambdas we support.
private Target createTarget(ProgramMethod accessedFrom) {
- if (appView.options().canAccessModifyLambdaImplementationMethods(appView)
- && descriptor.delegatesToLambdaImplMethod()) {
- return createLambdaImplMethodTarget(accessedFrom);
- }
-
- // Method referenced directly, without lambda$ method.
switch (descriptor.implHandle.type) {
case INVOKE_SUPER:
throw new Unimplemented("Method references to super methods are not yet supported");
@@ -286,8 +281,13 @@
case INVOKE_CONSTRUCTOR:
return createConstructorTarget(accessedFrom);
case INVOKE_STATIC:
- return createStaticMethodTarget(accessedFrom);
+ return canAccessModifyLambdaImplMethod()
+ ? createLambdaImplMethodTarget(accessedFrom)
+ : createStaticMethodTarget(accessedFrom);
case INVOKE_DIRECT:
+ return canAccessModifyLambdaImplMethod()
+ ? createLambdaImplMethodTarget(accessedFrom)
+ : createInstanceMethodTarget(accessedFrom);
case INVOKE_INSTANCE:
return createInstanceMethodTarget(accessedFrom);
default:
@@ -295,12 +295,25 @@
}
}
+ private boolean doesNotNeedAccessor(ProgramMethod accessedFrom) {
+ return canAccessModifyLambdaImplMethod() || !descriptor.needsAccessor(accessedFrom);
+ }
+
+ private boolean canAccessModifyLambdaImplMethod() {
+ MethodHandleType invokeType = descriptor.implHandle.type;
+ return appView.options().canAccessModifyLambdaImplementationMethods(appView)
+ && (invokeType.isInvokeDirect() || invokeType.isInvokeStatic())
+ && descriptor.delegatesToLambdaImplMethod(appView.dexItemFactory())
+ && !desugaring.isDirectTargetedLambdaImplementationMethod(descriptor.implHandle);
+ }
+
private Target createLambdaImplMethodTarget(ProgramMethod accessedFrom) {
DexMethodHandle implHandle = descriptor.implHandle;
assert implHandle != null;
DexMethod implMethod = implHandle.asMethod();
// Lambda$ method. We should always find it. If not found an ICCE can be expected to be thrown.
+ assert descriptor.delegatesToLambdaImplMethod(appView.dexItemFactory());
assert implMethod.holder == accessedFrom.getHolderType();
assert descriptor.verifyTargetFoundInClass(accessedFrom.getHolderType());
if (implHandle.type.isInvokeStatic()) {
@@ -318,12 +331,10 @@
result.getResolvedHolder().asProgramClass(), result.getResolvedMethod()));
}
- assert implHandle.type.isInvokeInstance() || implHandle.type.isInvokeDirect();
-
+ assert implHandle.type.isInvokeDirect();
// If the lambda$ method is an instance-private method on an interface we convert it into a
// public static method as it will be placed on the companion class.
- if (implHandle.type.isInvokeDirect()
- && appView.definitionFor(implMethod.holder).isInterface()) {
+ if (appView.definitionFor(implMethod.holder).isInterface()) {
DexProto implProto = implMethod.proto;
DexType[] implParams = implProto.parameters.values;
DexType[] newParams = new DexType[implParams.length + 1];
@@ -356,7 +367,7 @@
assert descriptor.implHandle.type.isInvokeInstance() ||
descriptor.implHandle.type.isInvokeDirect();
- if (!descriptor.needsAccessor(appView, accessedFrom)) {
+ if (doesNotNeedAccessor(accessedFrom)) {
return new NoAccessorMethodTarget(Invoke.Type.VIRTUAL);
}
// We need to generate an accessor method in `accessedFrom` class/interface
@@ -388,7 +399,7 @@
private Target createStaticMethodTarget(ProgramMethod accessedFrom) {
assert descriptor.implHandle.type.isInvokeStatic();
- if (!descriptor.needsAccessor(appView, accessedFrom)) {
+ if (doesNotNeedAccessor(accessedFrom)) {
return new NoAccessorMethodTarget(Invoke.Type.STATIC);
}
@@ -412,7 +423,7 @@
assert implHandle != null;
assert implHandle.type.isInvokeConstructor();
- if (!descriptor.needsAccessor(appView, accessedFrom)) {
+ if (doesNotNeedAccessor(accessedFrom)) {
return new NoAccessorMethodTarget(Invoke.Type.DIRECT);
}
@@ -435,7 +446,7 @@
// Create targets for interface methods.
private Target createInterfaceMethodTarget(ProgramMethod accessedFrom) {
assert descriptor.implHandle.type.isInvokeInterface();
- assert !descriptor.needsAccessor(appView, accessedFrom);
+ assert doesNotNeedAccessor(accessedFrom);
return new NoAccessorMethodTarget(Invoke.Type.INTERFACE);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 3cc480d..edfa934 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -4,11 +4,8 @@
package com.android.tools.r8.ir.desugar;
-import static com.android.tools.r8.ir.desugar.LambdaClass.JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX;
-
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -180,14 +177,9 @@
return targetHolder == type;
}
- public boolean canAccessModifyLambdaImplementationMethods(AppView<?> appView) {
- return appView.enableWholeProgramOptimizations();
- }
-
/** If the lambda delegates to lambda$ method. */
- public boolean delegatesToLambdaImplMethod() {
- String methodName = implHandle.asMethod().getName().toString();
- return methodName.startsWith(JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX);
+ public boolean delegatesToLambdaImplMethod(DexItemFactory factory) {
+ return implHandle.asMethod().getName().startsWith(factory.javacLambdaMethodPrefix);
}
/** Is a stateless lambda, i.e. lambda does not capture any values */
@@ -196,12 +188,7 @@
}
/** Checks if call site needs a accessor when referenced from `accessedFrom`. */
- boolean needsAccessor(AppView<?> appView, ProgramMethod accessedFrom) {
- if (appView.options().canAccessModifyLambdaImplementationMethods(appView)
- && delegatesToLambdaImplMethod()) {
- return false;
- }
-
+ boolean needsAccessor(ProgramMethod accessedFrom) {
if (implHandle.type.isInvokeInterface()) {
// Interface methods must be public.
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 3a7a195..433e023 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -98,9 +98,7 @@
@Override
public void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
ensureCfCode(method);
- if (recordRewriter != null) {
- recordRewriter.scan(method, eventConsumer);
- }
+ desugarings.forEach(d -> d.scan(method, eventConsumer));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
index 469f119..d8d47e8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
@@ -78,6 +78,7 @@
recordHashCodeHelperProto = factory.createProto(factory.intType, factory.recordType);
}
+ @Override
public void scan(
ProgramMethod programMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
CfCode cfCode = programMethod.getDefinition().getCode().asCfCode();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
index 34dda4e..59411a7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
@@ -15,6 +15,9 @@
import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.ProgramMethod;
@@ -28,20 +31,42 @@
import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.Box;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
+import java.util.Set;
import org.objectweb.asm.Opcodes;
public class LambdaInstructionDesugaring implements CfInstructionDesugaring {
private final AppView<?> appView;
+ private final Set<DexMethod> directTargetedLambdaImplementationMethods =
+ Sets.newIdentityHashSet();
+
+ public boolean isDirectTargetedLambdaImplementationMethod(DexMethodHandle implMethod) {
+ return implMethod.type.isInvokeDirect()
+ && directTargetedLambdaImplementationMethods.contains(implMethod.asMethod());
+ }
public LambdaInstructionDesugaring(AppView<?> appView) {
this.appView = appView;
}
@Override
+ public void scan(ProgramMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
+ CfCode code = method.getDefinition().getCode().asCfCode();
+ for (CfInstruction instruction : code.getInstructions()) {
+ if (instruction.isInvokeSpecial()) {
+ DexMethod target = instruction.asInvoke().getMethod();
+ if (target.getName().startsWith(appView.dexItemFactory().javacLambdaMethodPrefix)) {
+ directTargetedLambdaImplementationMethods.add(target);
+ }
+ }
+ }
+ }
+
+ @Override
public Collection<CfInstruction> desugarInstruction(
CfInstruction instruction,
FreshLocalProvider freshLocalProvider,
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/ExplicitCallToJavacGeneratedInstanceLambdaMethodTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/ExplicitCallToJavacGeneratedInstanceLambdaMethodTest.java
index 721e41d..ac20adf 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/ExplicitCallToJavacGeneratedInstanceLambdaMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/ExplicitCallToJavacGeneratedInstanceLambdaMethodTest.java
@@ -43,10 +43,7 @@
.addProgramClasses(Main.class, A.class, FunctionalInterface.class)
.addProgramClassFileData(getProgramClassFileData())
.run(parameters.getRuntime(), Main.class)
- .applyIf(
- parameters.isCfRuntime(),
- result -> result.assertSuccessWithOutputLines("Hello world!", "Hello world!"),
- result -> result.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
+ .assertSuccessWithOutputLines("Hello world!", "Hello world!");
}
@Test