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