Don't use accessors for lambdas to interface companion methods.

This CL adds in nested desugaring so that the invoke-dynamic target is
desugared *in context* of the invoke-dynamic.  This allows desugaring
the interface targets in the correct context and avoids the need to
introduce access bridges.

This should allow moving the finalization of interface companion methods
up into method processing in D8.

Change-Id: I6bd011f8a88770d1b27a246b9c32d9b0eae55b14
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index 71bc104..227270d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -28,6 +28,7 @@
     INVOKE_DIRECT((short) 0x07),
     INVOKE_INTERFACE((short) 0x08),
     // Internal method handle needed by lambda desugaring.
+    // TODO(b/200254463): Remove this now that lambda desugaring is CF/CF.
     INVOKE_SUPER((short) 0x09);
 
     private final short value;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 9b2a145..2e9fcab 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -75,6 +75,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     if (!instruction.isInvoke()) {
       return null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BufferCovariantReturnTypeRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BufferCovariantReturnTypeRewriter.java
index ea1cd46..4d9ec0e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BufferCovariantReturnTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BufferCovariantReturnTypeRewriter.java
@@ -38,6 +38,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     if (!isInvokeCandidate(instruction)) {
       return null;
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 513a9d5..810812a 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
@@ -40,6 +40,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory);
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index ada494f..5053f86 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.Collection;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -56,6 +58,15 @@
       MethodProcessingContext methodProcessingContext,
       CfInstructionDesugaringEventConsumer eventConsumer);
 
+  /** Selective desugaring of a single invoke instruction assuming a given context. */
+  public abstract Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext);
+
   public boolean isEmpty() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
index 027b27c..ef793c4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
@@ -13,6 +14,7 @@
 import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.Collection;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -47,6 +49,18 @@
   }
 
   @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    // Nothing to desugar.
+    return null;
+  }
+
+  @Override
   public boolean isEmpty() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InvokeToPrivateRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InvokeToPrivateRewriter.java
index 7fd31a8..65f23dc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InvokeToPrivateRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InvokeToPrivateRewriter.java
@@ -35,6 +35,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     if (!instruction.isInvokeVirtual() && !instruction.isInvokeInterface()) {
       return null;
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 afedd24..2c815a1 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
@@ -33,6 +33,7 @@
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring.DesugarInvoke;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
@@ -40,6 +41,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
 
 /**
  * Represents lambda class generated for a lambda descriptor in context of lambda instantiation
@@ -81,7 +83,8 @@
       AppView<?> appView,
       LambdaInstructionDesugaring desugaring,
       ProgramMethod accessedFrom,
-      LambdaDescriptor descriptor) {
+      LambdaDescriptor descriptor,
+      DesugarInvoke desugarInvoke) {
     assert desugaring != null;
     assert descriptor != null;
     this.type = builder.getType();
@@ -105,7 +108,7 @@
         !stateless ? null : factory.createField(type, type, factory.lambdaInstanceFieldName);
 
     // Synthesize the program class once all fields are set.
-    synthesizeLambdaClass(builder);
+    synthesizeLambdaClass(builder, desugarInvoke);
   }
 
   public final DexProgramClass getLambdaProgramClass() {
@@ -124,12 +127,13 @@
     this.clazz = clazz;
   }
 
-  private void synthesizeLambdaClass(SyntheticProgramClassBuilder builder) {
+  private void synthesizeLambdaClass(
+      SyntheticProgramClassBuilder builder, DesugarInvoke desugarInvoke) {
     builder.setInterfaces(descriptor.interfaces);
     synthesizeStaticFields(builder);
     synthesizeInstanceFields(builder);
     synthesizeDirectMethods(builder);
-    synthesizeVirtualMethods(builder);
+    synthesizeVirtualMethods(builder, desugarInvoke);
   }
 
   final DexField getCaptureField(int index) {
@@ -146,7 +150,8 @@
   }
 
   // Synthesize virtual methods.
-  private void synthesizeVirtualMethods(SyntheticProgramClassBuilder builder) {
+  private void synthesizeVirtualMethods(
+      SyntheticProgramClassBuilder builder, DesugarInvoke desugarInvoke) {
     DexMethod mainMethod =
         appView.dexItemFactory().createMethod(type, descriptor.erasedProto, descriptor.name);
 
@@ -159,7 +164,7 @@
             .setAccessFlags(
                 MethodAccessFlags.fromSharedAccessFlags(
                     Constants.ACC_PUBLIC | Constants.ACC_FINAL, false))
-            .setCode(LambdaMainMethodSourceCode.build(this, mainMethod))
+            .setCode(LambdaMainMethodSourceCode.build(this, mainMethod, desugarInvoke))
             // The api level is computed when tracing.
             .disableAndroidApiLevelCheck()
             .build());
@@ -261,6 +266,21 @@
     }
   }
 
+  public static int getAsmOpcodeForInvokeType(MethodHandleType type) {
+    switch (type) {
+      case INVOKE_INTERFACE:
+        return Opcodes.INVOKEINTERFACE;
+      case INVOKE_STATIC:
+        return Opcodes.INVOKESTATIC;
+      case INVOKE_DIRECT:
+        return Opcodes.INVOKESPECIAL;
+      case INVOKE_INSTANCE:
+        return Opcodes.INVOKEVIRTUAL;
+      default:
+        throw new Unreachable("Unexpected method handle type: " + type);
+    }
+  }
+
   // 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) {
@@ -287,12 +307,22 @@
   }
 
   private boolean doesNotNeedAccessor(ProgramMethod accessedFrom) {
-    return canAccessModifyLambdaImplMethod() || !descriptor.needsAccessor(accessedFrom);
+    return canAccessModifyLambdaImplMethod()
+        || isPrivateOrStaticInterfaceMethodInvokeThatWillBeDesugared()
+        || !descriptor.needsAccessor(accessedFrom);
+  }
+
+  private boolean isPrivateOrStaticInterfaceMethodInvokeThatWillBeDesugared() {
+    return appView.options().isInterfaceMethodDesugaringEnabled()
+        && descriptor.implHandle.isInterface
+        && (descriptor.implHandle.type.isInvokeDirect()
+            || descriptor.implHandle.type.isInvokeStatic());
   }
 
   private boolean canAccessModifyLambdaImplMethod() {
     MethodHandleType invokeType = descriptor.implHandle.type;
     return appView.options().canAccessModifyLambdaImplementationMethods(appView)
+        && !isPrivateOrStaticInterfaceMethodInvokeThatWillBeDesugared()
         && (invokeType.isInvokeDirect() || invokeType.isInvokeStatic())
         && descriptor.delegatesToLambdaImplMethod(appView.dexItemFactory())
         && !desugaring.isDirectTargetedLambdaImplementationMethod(descriptor.implHandle);
@@ -328,7 +358,7 @@
 
     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.
+    // public static method so it is accessible.
     if (appView.definitionFor(implMethod.holder).isInterface()) {
       DexProto implProto = implMethod.proto;
       DexType[] implParams = implProto.parameters.values;
@@ -367,7 +397,11 @@
 
     if (doesNotNeedAccessor(accessedFrom)) {
       return new NoAccessorMethodTarget(
-          descriptor.implHandle.asMethod(), Type.VIRTUAL, descriptor.implHandle.isInterface);
+          descriptor.implHandle.asMethod(),
+          descriptor.implHandle.type.isInvokeDirect()
+              ? Type.DIRECT
+              : descriptor.implHandle.type.isInvokeStatic() ? Type.STATIC : Type.VIRTUAL,
+          descriptor.implHandle.isInterface);
     }
     // We need to generate an accessor method in `accessedFrom` class/interface
     // for accessing the original instance impl-method. Note that impl-method's
@@ -523,7 +557,7 @@
   }
 
   // Used for targeting methods referenced directly without creating accessors.
-  private static final class NoAccessorMethodTarget extends Target {
+  public static final class NoAccessorMethodTarget extends Target {
 
     NoAccessorMethodTarget(DexMethod method, Type invokeType, boolean isInterface) {
       super(method, invokeType, isInterface);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index e0a9c07..acfee7b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -25,13 +25,18 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.LambdaClass.InvalidLambdaImplTarget;
+import com.android.tools.r8.ir.desugar.LambdaClass.NoAccessorMethodTarget;
+import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring.DesugarInvoke;
+import com.android.tools.r8.utils.IntBox;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
 
@@ -166,7 +171,8 @@
     }
   }
 
-  public static CfCode build(LambdaClass lambda, DexMethod mainMethod) {
+  public static CfCode build(
+      LambdaClass lambda, DexMethod mainMethod, DesugarInvoke desugarInvoke) {
     DexItemFactory factory = lambda.appView.dexItemFactory();
     LambdaClass.Target target = lambda.target;
     if (target instanceof InvalidLambdaImplTarget) {
@@ -184,11 +190,13 @@
 
     // Only constructor call should use direct invoke type since super
     // and private methods require accessor methods.
-    boolean constructorTarget = target.invokeType == Invoke.Type.DIRECT;
-    assert !constructorTarget || methodToCall.name == factory.constructorMethodName;
+    boolean constructorTarget = methodToCall.name == factory.constructorMethodName;
+    assert !constructorTarget || target.invokeType == Type.DIRECT;
 
     boolean targetWithReceiver =
-        target.invokeType == Invoke.Type.VIRTUAL || target.invokeType == Invoke.Type.INTERFACE;
+        target.invokeType == Invoke.Type.VIRTUAL
+            || target.invokeType == Invoke.Type.INTERFACE
+            || (target.invokeType == Type.DIRECT && !constructorTarget);
     List<DexType> implReceiverAndArgs = new ArrayList<>();
     if (targetWithReceiver) {
       implReceiverAndArgs.add(methodToCall.holder);
@@ -241,8 +249,24 @@
               erasedParams[i], enforcedParams[i], expectedParamType, instructions, factory);
     }
 
-    instructions.add(
-        new CfInvoke(target.invokeType.getCfOpcode(), methodToCall, target.isInterface()));
+    CfInvoke invoke =
+        new CfInvoke(target.invokeType.getCfOpcode(), methodToCall, target.isInterface());
+    if (target instanceof NoAccessorMethodTarget) {
+      IntBox locals = new IntBox();
+      IntBox stack = new IntBox();
+      Collection<CfInstruction> is =
+          desugarInvoke.desugarInvoke(invoke, locals::getAndIncrement, stack::getAndIncrement);
+      if (is != null) {
+        instructions.addAll(is);
+        maxLocals += locals.get();
+        maxStack += stack.get();
+      } else {
+        instructions.add(invoke);
+      }
+    } else {
+      instructions.add(invoke);
+    }
+
     DexType methodToCallReturnType = methodToCall.getReturnType();
     if (!methodToCallReturnType.isVoidType()) {
       maxStack =
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 7a3dd75..0b20c42 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
@@ -244,7 +244,8 @@
     return true;
   }
 
-  private Collection<CfInstruction> desugarInstruction(
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
       CfInstruction instruction,
       FreshLocalProvider freshLocalProvider,
       LocalStackAllocator localStackAllocator,
@@ -263,6 +264,7 @@
               eventConsumer,
               context,
               methodProcessingContext,
+              this,
               appView.dexItemFactory());
       if (replacement != null) {
         assert desugaring.needsDesugaring(instruction, context);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
index 8ef2e18..1d55e7e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
@@ -46,6 +47,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     if (instruction.isConstDynamic()) {
       return desugarConstDynamicInstruction(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
index 4634977..2b6c9d4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAPIConverter.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
@@ -97,6 +98,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     if (needsDesugaring(instruction, context)) {
       assert instruction.isInvoke();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
index 73d7f8f..5c6d6c1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryRetargeter.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
@@ -69,6 +70,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     InvokeRetargetingResult invokeRetargetingResult = computeNewInvokeTarget(instruction, context);
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
index dfb797c..428bdd0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
@@ -73,6 +74,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     if (instruction.isInvokeSpecial()) {
       return desugarInvokeInstruction(instruction.asInvoke(), eventConsumer, context);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 09c13c0..d91a6fb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugarDescription;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
@@ -256,6 +257,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     assert !isSyntheticMethodThatShouldNotBeDoubleProcessed(context);
     return computeDescription(instruction, context)
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 71010c3..206c276 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
@@ -24,6 +24,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LambdaClass;
@@ -75,6 +76,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     if (instruction.isInvokeDynamic()) {
       return desugarInvokeDynamicInstruction(
@@ -83,19 +85,36 @@
           localStackAllocator,
           eventConsumer,
           context,
-          methodProcessingContext);
+          methodProcessingContext,
+          (invoke, localProvider, stackAllocator) ->
+              desugaringCollection.desugarInstruction(
+                  invoke,
+                  localProvider,
+                  stackAllocator,
+                  eventConsumer,
+                  context,
+                  methodProcessingContext));
     }
     return null;
   }
 
+  public interface DesugarInvoke {
+    Collection<CfInstruction> desugarInvoke(
+        CfInvoke invoke,
+        FreshLocalProvider freshLocalProvider,
+        LocalStackAllocator localStackAllocator);
+  }
+
   private Collection<CfInstruction> desugarInvokeDynamicInstruction(
       CfInvokeDynamic invoke,
       FreshLocalProvider freshLocalProvider,
       LocalStackAllocator localStackAllocator,
       LambdaDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
-    LambdaClass lambdaClass = createLambdaClass(invoke, context, methodProcessingContext);
+      MethodProcessingContext methodProcessingContext,
+      DesugarInvoke desugarInvoke) {
+    LambdaClass lambdaClass =
+        createLambdaClass(invoke, context, methodProcessingContext, desugarInvoke);
     if (lambdaClass == null) {
       return null;
     }
@@ -134,7 +153,8 @@
   private LambdaClass createLambdaClass(
       CfInvokeDynamic invoke,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
+      MethodProcessingContext methodProcessingContext,
+      DesugarInvoke desugarInvoke) {
     LambdaDescriptor descriptor =
         LambdaDescriptor.tryInfer(invoke.getCallSite(), appView.appInfoForDesugaring(), context);
     if (descriptor == null) {
@@ -149,7 +169,10 @@
                 SyntheticNaming.SyntheticKind.LAMBDA,
                 methodProcessingContext.createUniqueContext(),
                 appView,
-                builder -> box.set(new LambdaClass(builder, appView, this, context, descriptor)));
+                builder ->
+                    box.set(
+                        new LambdaClass(
+                            builder, appView, this, context, descriptor, desugarInvoke)));
     // Immediately set the actual program class on the lambda.
     LambdaClass lambdaClass = box.get();
     lambdaClass.setClass(clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
index 87adb53..f9ca2e9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
@@ -268,6 +269,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     if (instruction.isFieldInstruction()) {
       return desugarFieldInstruction(instruction.asFieldInstruction(), context, eventConsumer);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
index 1f16c1b..3f5c1cb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
@@ -167,6 +168,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     assert !instruction.isInitClass();
     if (!needsDesugaring(instruction, context)) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
index 607b088..065fc4c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
@@ -76,6 +77,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     if (instruction.isInvokeDynamic()) {
       // We are interested in bootstrap methods StringConcatFactory::makeConcat
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
index ee71bc0..716b6e2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
@@ -57,6 +58,7 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     if (!instruction.isInvoke()) {
       return null;
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaPrivateInstanceInterfaceMethodWithNonLambdaCallSiteTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaPrivateInstanceInterfaceMethodWithNonLambdaCallSiteTest.java
index 7d0b2fa..6961fe9 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaPrivateInstanceInterfaceMethodWithNonLambdaCallSiteTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaPrivateInstanceInterfaceMethodWithNonLambdaCallSiteTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.desugar.lambdas;
 
+import static org.junit.Assert.assertFalse;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -41,7 +43,21 @@
         .addProgramClasses(Main.class, A.class, FunctionalInterface.class)
         .addProgramClassFileData(getProgramClassFileData())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("Hello world!", "Hello world!");
+        .assertSuccessWithOutputLines("Hello world!", "Hello world!")
+        .inspect(
+            inspector -> {
+              if (parameters.isDexRuntime()
+                  && !parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                inspector
+                    .clazz(I.class)
+                    .toCompanionClass()
+                    .forAllMethods(
+                        m ->
+                            // We don't expect any synthetic accessors to be needed for the private
+                            // interface method.
+                            assertFalse("Unexpected synthetic method: " + m, m.isSynthetic()));
+              }
+            });
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaWithPrivateInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaWithPrivateInterfaceInvokeTest.java
new file mode 100644
index 0000000..d454273
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaWithPrivateInterfaceInvokeTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaWithPrivateInterfaceInvokeTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LambdaWithPrivateInterfaceInvokeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(TestClass.class, MyFun.class, A.class)
+        .addProgramClassFileData(getTransformForI())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, MyFun.class, A.class)
+        .addProgramClassFileData(getTransformForI())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private byte[] getTransformForI() throws Exception {
+    return transformer(I.class).setPrivate(I.class.getDeclaredMethod("bar")).transform();
+  }
+
+  interface I {
+    /* private */ default String bar() {
+      return "Hello world";
+    }
+
+    default void foo() {
+      TestClass.run(
+          () -> {
+            System.out.println(bar());
+          });
+    }
+  }
+
+  interface MyFun {
+    void run();
+  }
+
+  static class A implements I {}
+
+  static class TestClass {
+
+    public static void run(MyFun fn) {
+      fn.run();
+    }
+
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}