Reland "Interface method desugaring instruction desugaring cf to cf"

This reverts commit e088ba0e0686c0dd6485db27fcc833e3a891dbf2.

Bug: 166397278
Change-Id: I5ed91a5129bb03c0edcdd7e84e7d63d14db6c8cf
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 7b6c2e7..625430b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -144,7 +144,9 @@
     }
   }
 
-  private Invoke.Type getInvokeType(DexClassAndMethod context) {
+  // We should avoid interpreting a CF invoke using DEX semantics.
+  @Deprecated
+  public Invoke.Type getInvokeType(DexClassAndMethod context) {
     switch (opcode) {
       case Opcodes.INVOKEINTERFACE:
         return Type.INTERFACE;
@@ -171,10 +173,12 @@
     return getMethod().isInstanceInitializer(dexItemFactory);
   }
 
+  // We should avoid interpreting a CF invoke using DEX semantics.
+  @Deprecated
   public boolean isInvokeSuper(DexType clazz) {
-    return opcode == Opcodes.INVOKESPECIAL &&
-        method.holder != clazz &&
-        !method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME);
+    return opcode == Opcodes.INVOKESPECIAL
+        && method.holder != clazz
+        && !method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 785fb64..0243104 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1475,7 +1475,8 @@
 
     previous = printMethod(code, "IR after class inlining (SSA)", previous);
 
-    if (interfaceMethodRewriter != null) {
+    // TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
+    if (interfaceMethodRewriter != null && appView.enableWholeProgramOptimizations()) {
       timing.begin("Rewrite interface methods");
       interfaceMethodRewriter.rewriteMethodReferences(
           code, methodProcessor, methodProcessingContext);
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 cc28692..44c3df3 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
@@ -87,8 +87,11 @@
 
   @Override
   public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
-    return instruction.isInvoke()
-        && getMethodProviderOrNull(instruction.asInvoke().getMethod()) != null;
+    return instruction.isInvoke() && methodIsBackport(instruction.asInvoke().getMethod());
+  }
+
+  public boolean methodIsBackport(DexMethod method) {
+    return getMethodProviderOrNull(method) != null;
   }
 
   public static List<DexMethod> generateListOfBackportedMethods(
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 11c18d0..9d7b629 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
@@ -36,4 +36,14 @@
    * <p>This should return true if-and-only-if {@link #desugarInstruction} returns non-null.
    */
   boolean needsDesugaring(CfInstruction instruction, ProgramMethod context);
+
+  /**
+   * Returns true if and only if needsDesugaring() answering true implies a desugaring is needed.
+   * Some optimizations may have some heuristics, so that needsDesugaring() answers true in rare
+   * case even if no desugaring is needed.
+   */
+  // TODO(b/187913003): Fixing interface desugaring should eliminate the need for this.
+  default boolean hasPreciseNeedsDesugaring() {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index a0c4c64..6951b39 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
 import com.android.tools.r8.ir.desugar.lambda.LambdaDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaringEventConsumer;
@@ -40,7 +41,8 @@
         LambdaDesugaringEventConsumer,
         NestBasedAccessDesugaringEventConsumer,
         RecordDesugaringEventConsumer,
-        TwrCloseResourceDesugaringEventConsumer {
+        TwrCloseResourceDesugaringEventConsumer,
+        InterfaceMethodDesugaringEventConsumer {
 
   public static D8CfInstructionDesugaringEventConsumer createForD8(
       D8MethodProcessor methodProcessor) {
@@ -59,6 +61,17 @@
     return new CfInstructionDesugaringEventConsumer() {
 
       @Override
+      public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
+        assert false;
+      }
+
+      @Override
+      public void acceptInvokeStaticInterfaceOutliningMethod(
+          ProgramMethod method, ProgramMethod context) {
+        assert false;
+      }
+
+      @Override
       public void acceptRecordClass(DexProgramClass recordClass) {
         assert false;
       }
@@ -168,6 +181,17 @@
       methodProcessor.scheduleDesugaredMethodForProcessing(closeMethod);
     }
 
+    @Override
+    public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
+    @Override
+    public void acceptInvokeStaticInterfaceOutliningMethod(
+        ProgramMethod method, ProgramMethod context) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
     public List<ProgramMethod> finalizeDesugaring(
         AppView<?> appView, ClassConverterResult.Builder classConverterResultBuilder) {
       List<ProgramMethod> needsProcessing = new ArrayList<>();
@@ -257,6 +281,17 @@
     }
 
     @Override
+    public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
+      assert false : "TODO(b/183998768): To be implemented";
+    }
+
+    @Override
+    public void acceptInvokeStaticInterfaceOutliningMethod(
+        ProgramMethod method, ProgramMethod context) {
+      assert false : "TODO(b/183998768): To be implemented";
+    }
+
+    @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
       // Intentionally empty. The backported method will be hit by the tracing in R8 as if it was
       // present in the input code, and thus nothing needs to be done.
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 26a2c5c..0d4a470 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
@@ -15,13 +15,13 @@
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.NonEmptyCfClassDesugaringCollection;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.twr.TwrCloseResourceInstructionDesugaring;
 import com.android.tools.r8.utils.IntBox;
-import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -43,19 +43,26 @@
   NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
     this.appView = appView;
     this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView);
+    BackportedMethodRewriter backportedMethodRewriter = null;
+    if (appView.options().enableBackportedMethodRewriting()) {
+      backportedMethodRewriter = new BackportedMethodRewriter(appView);
+    }
+    // Place TWR before Interface desugaring to eliminate potential $closeResource interface calls.
+    if (appView.options().enableTryWithResourcesDesugaring()) {
+      desugarings.add(new TwrCloseResourceInstructionDesugaring(appView));
+    }
+    // TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
+    if (appView.options().isInterfaceMethodDesugaringEnabled()
+        && !appView.enableWholeProgramOptimizations()) {
+      desugarings.add(new InterfaceMethodRewriter(appView, backportedMethodRewriter));
+    }
     desugarings.add(new LambdaInstructionDesugaring(appView));
     desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
     desugarings.add(new InvokeToPrivateRewriter());
     desugarings.add(new StringConcatInstructionDesugaring(appView));
     desugarings.add(new BufferCovariantReturnTypeRewriter(appView));
-    if (appView.options().enableBackportedMethodRewriting()) {
-      BackportedMethodRewriter backportedMethodRewriter = new BackportedMethodRewriter(appView);
-      if (backportedMethodRewriter.hasBackports()) {
-        desugarings.add(backportedMethodRewriter);
-      }
-    }
-    if (appView.options().enableTryWithResourcesDesugaring()) {
-      desugarings.add(new TwrCloseResourceInstructionDesugaring(appView));
+    if (backportedMethodRewriter != null && backportedMethodRewriter.hasBackports()) {
+      desugarings.add(backportedMethodRewriter);
     }
     if (nestBasedAccessDesugaring != null) {
       desugarings.add(nestBasedAccessDesugaring);
@@ -152,10 +159,32 @@
       cfCode.setMaxLocals(maxLocalsForCode.get());
       cfCode.setMaxStack(maxStackForCode.get());
     } else {
-      assert false : "Expected code to be desugared";
+      assert noDesugaringBecauseOfImpreciseDesugaring(method);
     }
   }
 
+  private boolean noDesugaringBecauseOfImpreciseDesugaring(ProgramMethod method) {
+    assert desugarings.stream().anyMatch(desugaring -> !desugaring.hasPreciseNeedsDesugaring())
+        : "Expected code to be desugared";
+    assert needsDesugaring(method);
+    boolean foundFalsePositive = false;
+    for (CfInstruction instruction :
+        method.getDefinition().getCode().asCfCode().getInstructions()) {
+      for (CfInstructionDesugaring impreciseDesugaring :
+          Iterables.filter(desugarings, desugaring -> !desugaring.hasPreciseNeedsDesugaring())) {
+        if (impreciseDesugaring.needsDesugaring(instruction, method)) {
+          foundFalsePositive = true;
+        }
+      }
+      for (CfInstructionDesugaring preciseDesugaring :
+          Iterables.filter(desugarings, desugaring -> desugaring.hasPreciseNeedsDesugaring())) {
+        assert !preciseDesugaring.needsDesugaring(instruction, method);
+      }
+    }
+    assert foundFalsePositive;
+    return true;
+  }
+
   @Override
   public CfClassDesugaringCollection createClassDesugaringCollection() {
     if (recordRewriter == null) {
@@ -185,8 +214,8 @@
               methodProcessingContext,
               appView.dexItemFactory());
       if (replacement != null) {
-        assert verifyNoOtherDesugaringNeeded(
-            instruction, context, methodProcessingContext, iterator);
+        assert desugaring.needsDesugaring(instruction, context);
+        assert verifyNoOtherDesugaringNeeded(instruction, context, iterator, desugaring);
         return replacement;
       }
     }
@@ -220,26 +249,26 @@
   private boolean verifyNoOtherDesugaringNeeded(
       CfInstruction instruction,
       ProgramMethod context,
-      MethodProcessingContext methodProcessingContext,
-      Iterator<CfInstructionDesugaring> iterator) {
-    assert IteratorUtils.nextUntil(
-            iterator,
-            desugaring ->
-                desugaring.desugarInstruction(
-                        instruction,
-                        requiredRegisters -> {
-                          assert false;
-                          return 0;
-                        },
-                        localStackHeight -> {
-                          assert false;
-                        },
-                        CfInstructionDesugaringEventConsumer.createForDesugaredCode(),
-                        context,
-                        methodProcessingContext,
-                        appView.dexItemFactory())
-                    != null)
-        == null;
+      Iterator<CfInstructionDesugaring> iterator,
+      CfInstructionDesugaring appliedDesugaring) {
+    iterator.forEachRemaining(
+        desugaring -> {
+          boolean alsoApplicable = desugaring.needsDesugaring(instruction, context);
+          // TODO(b/187913003): As part of precise interface desugaring, make sure the
+          //  identification is explicitly non-overlapping and remove the exceptions below.
+          assert !alsoApplicable
+                  || (appliedDesugaring instanceof InterfaceMethodRewriter
+                      && (desugaring instanceof InvokeToPrivateRewriter
+                          || desugaring instanceof D8NestBasedAccessDesugaring))
+                  || (appliedDesugaring instanceof TwrCloseResourceInstructionDesugaring
+                      && desugaring instanceof InterfaceMethodRewriter)
+              : "Desugaring of "
+                  + instruction
+                  + " has multiple matches: "
+                  + appliedDesugaring.getClass().getName()
+                  + " and "
+                  + desugaring.getClass().getName();
+        });
     return true;
   }
 
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 1b5e6aa..2e71826 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
@@ -305,15 +305,8 @@
   @Override
   public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
     assert !instruction.isInitClass();
-    // TODO(b/179146128): This is a temporary work-around to test desugaring of records
-    // without rewriting the record invoke-custom. This should be removed when the record support
-    // is complete.
-    if (instruction.isInvokeDynamic()
-        && context.getHolder().superType == factory.recordType
-        && (context.getName() == factory.toStringMethodName
-            || context.getName() == factory.hashCodeMethodName
-            || context.getName() == factory.equalsMethodName)) {
-      return true;
+    if (instruction.isInvokeDynamic()) {
+      return needsDesugaring(instruction.asInvokeDynamic(), context);
     }
     if (instruction.isInvoke()) {
       CfInvoke cfInvoke = instruction.asInvoke();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
new file mode 100644
index 0000000..031e142
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
@@ -0,0 +1,16 @@
+// 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.ir.desugar.itf;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface InterfaceMethodDesugaringEventConsumer {
+
+  void acceptThrowMethod(ProgramMethod method, ProgramMethod context);
+
+  void acceptInvokeStaticInterfaceOutliningMethod(ProgramMethod method, ProgramMethod context);
+
+  // TODO(b/183998768): Add acceptCompanionClass and acceptEmulatedInterface.
+}
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 b0a4ffa..782ff63 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
@@ -9,23 +9,24 @@
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 import static com.android.tools.r8.ir.code.Invoke.Type.SUPER;
 import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX;
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
 
 import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
@@ -38,6 +39,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -48,17 +50,20 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.code.InvokeCustom;
-import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.InvokeSuper;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.itf.DefaultMethodsHelper.Collection;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
@@ -78,6 +83,9 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -86,6 +94,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 //
@@ -114,7 +124,7 @@
 //       set of default interface methods missing and add them, the created methods
 //       forward the call to an appropriate method in interface companion class.
 //
-public final class InterfaceMethodRewriter {
+public final class InterfaceMethodRewriter implements CfInstructionDesugaring {
 
   // Public for testing.
   public static final String EMULATE_LIBRARY_CLASS_NAME_SUFFIX = "$-EL";
@@ -137,6 +147,10 @@
   private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
 
   private final Predicate<DexType> shouldIgnoreFromReportsPredicate;
+
+  // This is used to filter out double desugaring on backported methods.
+  private final BackportedMethodRewriter backportedMethodRewriter;
+
   /** Defines a minor variation in desugaring. */
   public enum Flavor {
     /** Process all application resources. */
@@ -145,10 +159,24 @@
     ExcludeDexResources
   }
 
+  // Constructor for cf to cf desugaring.
+  public InterfaceMethodRewriter(AppView<?> appView, BackportedMethodRewriter rewriter) {
+    this.appView = appView;
+    this.converter = null;
+    this.backportedMethodRewriter = rewriter;
+    this.options = appView.options();
+    this.factory = appView.dexItemFactory();
+    this.emulatedInterfaces = options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
+    this.shouldIgnoreFromReportsPredicate = getShouldIgnoreFromReportsPredicate(appView);
+    initializeEmulatedInterfaceVariables();
+  }
+
+  // Constructor for IR desugaring.
   public InterfaceMethodRewriter(AppView<?> appView, IRConverter converter) {
     assert converter != null;
     this.appView = appView;
     this.converter = converter;
+    this.backportedMethodRewriter = null;
     this.options = appView.options();
     this.factory = appView.dexItemFactory();
     this.emulatedInterfaces = options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
@@ -232,6 +260,8 @@
   }
 
   private boolean invokeNeedsRewriting(DexMethod method, Type invokeType) {
+    // TODO(b/187913003): Refactor the implementation of needsDesugaring and desugarInstruction so
+    //  that the identification is shared and thus guaranteed to be equivalent.
     if (invokeType == SUPER || invokeType == STATIC || invokeType == DIRECT) {
       DexClass clazz = appView.appInfo().definitionFor(method.getHolderType());
       if (clazz != null && clazz.isInterface()) {
@@ -240,11 +270,257 @@
       return emulatedMethods.contains(method.getName());
     }
     if (invokeType == VIRTUAL || invokeType == INTERFACE) {
-      return defaultMethodForEmulatedDispatchOrNull(method, invokeType) != null;
+      // A virtual dispatch can target a private method, on self or on a nest mate.
+      AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
+      SingleResolutionResult resolution =
+          appInfoForDesugaring.resolveMethod(method, invokeType == INTERFACE).asSingleResolution();
+      if (resolution != null && resolution.getResolvedMethod().isPrivateMethod()) {
+        return true;
+      }
+      return defaultMethodForEmulatedDispatchOrNull(method, invokeType == INTERFACE) != null;
     }
     return true;
   }
 
+  @Override
+  public boolean hasPreciseNeedsDesugaring() {
+    return false;
+  }
+
+  /**
+   * If the method is not required to be desugared, scanning is used to upgrade when required the
+   * class file version, as well as reporting missing type.
+   */
+  @Override
+  public void scan(ProgramMethod context, CfInstructionDesugaringEventConsumer eventConsumer) {
+    if (isSyntheticMethodThatShouldNotBeDoubleProcessed(context)) {
+      leavingStaticInvokeToInterface(context);
+      return;
+    }
+    CfCode code = context.getDefinition().getCode().asCfCode();
+    for (CfInstruction instruction : code.getInstructions()) {
+      if (instruction.isInvokeDynamic()
+          && !LambdaInstructionDesugaring.isLambdaInvoke(instruction, context, appView)
+          && !StringConcatInstructionDesugaring.isStringConcatInvoke(
+              instruction, appView.dexItemFactory())) {
+        reportInterfaceMethodHandleCallSite(instruction.asInvokeDynamic().getCallSite(), context);
+        continue;
+      }
+      if (instruction.isInvoke()) {
+        CfInvoke cfInvoke = instruction.asInvoke();
+        if (backportedMethodRewriter.methodIsBackport(cfInvoke.getMethod())) {
+          continue;
+        }
+        if (cfInvoke.isInvokeStatic()) {
+          scanInvokeStatic(cfInvoke, context);
+        } else if (cfInvoke.isInvokeSpecial()) {
+          scanInvokeDirectOrSuper(cfInvoke, context);
+        }
+      }
+    }
+  }
+
+  private void scanInvokeDirectOrSuper(CfInvoke cfInvoke, ProgramMethod context) {
+    if (cfInvoke.isInvokeConstructor(factory)) {
+      return;
+    }
+    DexMethod invokedMethod = cfInvoke.getMethod();
+    DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
+    if (clazz == null) {
+      // NOTE: For invoke-super, this leaves unchanged those calls to undefined targets.
+      // This may lead to runtime exception but we can not report it as error since it can also be
+      // the intended behavior.
+      // For invoke-direct, this reports the missing class since we don't know if it is an
+      // interface.
+      warnMissingType(context, invokedMethod.holder);
+    }
+  }
+
+  private void scanInvokeStatic(CfInvoke cfInvoke, ProgramMethod context) {
+    DexMethod invokedMethod = cfInvoke.getMethod();
+    DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
+    if (clazz == null) {
+      // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
+      // exception but we can not report it as error since it can also be the intended
+      // behavior.
+      if (cfInvoke.isInterface()) {
+        leavingStaticInvokeToInterface(context);
+      }
+      warnMissingType(context, invokedMethod.holder);
+      return;
+    }
+
+    if (!clazz.isInterface()) {
+      if (cfInvoke.isInterface()) {
+        leavingStaticInvokeToInterface(context);
+      }
+      return;
+    }
+
+    if (isNonDesugaredLibraryClass(clazz)) {
+      // NOTE: we intentionally don't desugar static calls into static interface
+      // methods coming from android.jar since it is only possible in case v24+
+      // version of android.jar is provided.
+      //
+      // We assume such calls are properly guarded by if-checks like
+      //    'if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.XYZ) { ... }'
+      //
+      // WARNING: This may result in incorrect code on older platforms!
+      // Retarget call to an appropriate method of companion class.
+
+      if (options.canLeaveStaticInterfaceMethodInvokes()) {
+        // When leaving static interface method invokes upgrade the class file version.
+        leavingStaticInvokeToInterface(context);
+      }
+    }
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    if (instruction.isInvoke()) {
+      CfInvoke cfInvoke = instruction.asInvoke();
+      return needsRewriting(cfInvoke.getMethod(), cfInvoke.getInvokeType(context), context);
+    }
+    return false;
+  }
+
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
+    if (!instruction.isInvoke() || isSyntheticMethodThatShouldNotBeDoubleProcessed(context)) {
+      return null;
+    }
+    CfInvoke invoke = instruction.asInvoke();
+    if (backportedMethodRewriter.methodIsBackport(invoke.getMethod())) {
+      return null;
+    }
+
+    Function<DexMethod, Collection<CfInstruction>> rewriteInvoke =
+        (newTarget) ->
+            Collections.singletonList(
+                new CfInvoke(org.objectweb.asm.Opcodes.INVOKESTATIC, newTarget, false));
+
+    Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow =
+        (resolutionResult) ->
+            rewriteInvokeToThrowCf(
+                invoke, resolutionResult, eventConsumer, context, methodProcessingContext);
+
+    if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
+      AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
+      SingleResolutionResult resolution =
+          appInfoForDesugaring
+              .resolveMethod(invoke.getMethod(), invoke.isInterface())
+              .asSingleResolution();
+      if (resolution != null
+          && resolution.getResolvedMethod().isPrivateMethod()
+          && resolution.isAccessibleFrom(context, appInfoForDesugaring).isTrue()) {
+        return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke);
+      }
+      return rewriteInvokeInterfaceOrInvokeVirtual(
+          invoke.getMethod(), invoke.isInterface(), rewriteInvoke);
+    }
+    if (invoke.isInvokeStatic()) {
+      Consumer<ProgramMethod> staticOutliningMethodConsumer =
+          staticOutliningMethod -> {
+            synthesizedMethods.add(staticOutliningMethod);
+            eventConsumer.acceptInvokeStaticInterfaceOutliningMethod(
+                staticOutliningMethod, context);
+          };
+      return rewriteInvokeStatic(
+          invoke.getMethod(),
+          invoke.isInterface(),
+          methodProcessingContext,
+          context,
+          staticOutliningMethodConsumer,
+          rewriteInvoke,
+          rewriteToThrow);
+    }
+    assert invoke.isInvokeSpecial();
+    if (invoke.isInvokeSuper(context.getHolderType())) {
+      return rewriteInvokeSuper(invoke.getMethod(), context, rewriteInvoke, rewriteToThrow);
+    }
+    return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke);
+  }
+
+  private Collection<CfInstruction> rewriteInvokeToThrowCf(
+      CfInvoke invoke,
+      SingleResolutionResult resolutionResult,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    if (backportedMethodRewriter != null
+        && backportedMethodRewriter.methodIsBackport(invoke.getMethod())) {
+      // In Cf to Cf it is not allowed to desugar twice the same instruction, if the backported
+      // method rewriter already desugars the instruction, it takes precedence and nothing has
+      // to be done here.
+      return null;
+    }
+
+    MethodSynthesizerConsumer methodSynthesizerConsumer;
+    if (resolutionResult == null) {
+      methodSynthesizerConsumer =
+          UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+    } else if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
+      methodSynthesizerConsumer =
+          UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
+    } else {
+      assert false;
+      return null;
+    }
+
+    assert needsDesugaring(invoke, context);
+
+    // Replace the entire effect of the invoke by by call to the throwing helper:
+    //   ...
+    //   invoke <method> [receiver] args*
+    // =>
+    //   ...
+    //   (pop arg)*
+    //   [pop receiver]
+    //   invoke <throwing-method>
+    //   pop exception result
+    //   [push fake result for <method>]
+    UtilityMethodForCodeOptimizations throwMethod =
+        methodSynthesizerConsumer.synthesizeMethod(appView, methodProcessingContext);
+    ProgramMethod throwProgramMethod = throwMethod.uncheckedGetMethod();
+    eventConsumer.acceptThrowMethod(throwProgramMethod, context);
+
+    ArrayList<CfInstruction> replacement = new ArrayList<>();
+    DexTypeList parameters = invoke.getMethod().getParameters();
+    for (int i = parameters.values.length - 1; i >= 0; i--) {
+      replacement.add(
+          new CfStackInstruction(
+              parameters.get(i).isWideType()
+                  ? CfStackInstruction.Opcode.Pop2
+                  : CfStackInstruction.Opcode.Pop));
+    }
+    if (!invoke.isInvokeStatic()) {
+      replacement.add(new CfStackInstruction(CfStackInstruction.Opcode.Pop));
+    }
+
+    CfInvoke throwInvoke =
+        new CfInvoke(
+            org.objectweb.asm.Opcodes.INVOKESTATIC, throwProgramMethod.getReference(), false);
+    assert throwInvoke.getMethod().getReturnType().isClassType();
+    replacement.add(throwInvoke);
+    replacement.add(new CfStackInstruction(CfStackInstruction.Opcode.Pop));
+
+    DexType returnType = invoke.getMethod().getReturnType();
+    if (returnType != factory.voidType) {
+      replacement.add(
+          returnType.isPrimitiveType()
+              ? new CfConstNumber(0, ValueType.fromDexType(returnType))
+              : new CfConstNull());
+    }
+    return replacement;
+  }
+
   DexType getEmulatedInterface(DexType itf) {
     return emulatedInterfaces.get(itf);
   }
@@ -290,44 +566,16 @@
       InstructionListIterator instructions = block.listIterator(code);
       while (instructions.hasNext()) {
         Instruction instruction = instructions.next();
-        switch (instruction.opcode()) {
-          case INVOKE_CUSTOM:
-            rewriteInvokeCustom(instruction.asInvokeCustom(), context);
-            break;
-          case INVOKE_DIRECT:
-            rewriteInvokeDirect(instruction.asInvokeDirect(), instructions, context);
-            break;
-          case INVOKE_STATIC:
-            rewriteInvokeStatic(
-                instruction.asInvokeStatic(),
-                code,
-                blocks,
-                instructions,
-                affectedValues,
-                blocksToRemove,
-                methodProcessor,
-                methodProcessingContext);
-            break;
-          case INVOKE_SUPER:
-            rewriteInvokeSuper(
-                instruction.asInvokeSuper(),
-                code,
-                blocks,
-                instructions,
-                affectedValues,
-                blocksToRemove,
-                methodProcessor,
-                methodProcessingContext);
-            break;
-          case INVOKE_INTERFACE:
-          case INVOKE_VIRTUAL:
-            rewriteInvokeInterfaceOrInvokeVirtual(
-                instruction.asInvokeMethodWithReceiver(), instructions);
-            break;
-          default:
-            // Intentionally empty.
-            break;
-        }
+        rewriteMethodReferences(
+            code,
+            methodProcessor,
+            methodProcessingContext,
+            context,
+            affectedValues,
+            blocksToRemove,
+            blocks,
+            instructions,
+            instruction);
       }
     }
 
@@ -340,14 +588,71 @@
     assert code.isConsistentSSA();
   }
 
+  private void rewriteMethodReferences(
+      IRCode code,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext,
+      ProgramMethod context,
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove,
+      ListIterator<BasicBlock> blocks,
+      InstructionListIterator instructions,
+      Instruction instruction) {
+    if (instruction.isInvokeCustom()) {
+      reportInterfaceMethodHandleCallSite(instruction.asInvokeCustom().getCallSite(), context);
+      return;
+    }
+    if (!instruction.isInvokeMethod()) {
+      return;
+    }
+    InvokeMethod invoke = instruction.asInvokeMethod();
+    Function<DexMethod, Collection<CfInstruction>> rewriteInvoke =
+        (newTarget) -> {
+          instructions.replaceCurrentInstruction(
+              new InvokeStatic(newTarget, invoke.outValue(), invoke.arguments()));
+          return null;
+        };
+    if (instruction.isInvokeDirect()) {
+      rewriteInvokeDirect(invoke.getInvokedMethod(), context, rewriteInvoke);
+    } else if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
+      rewriteInvokeInterfaceOrInvokeVirtual(
+          invoke.getInvokedMethod(), invoke.getInterfaceBit(), rewriteInvoke);
+    } else {
+      Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow =
+          (resolutionResult) ->
+              rewriteInvokeToThrowIR(
+                  invoke,
+                  resolutionResult,
+                  code,
+                  blocks,
+                  instructions,
+                  affectedValues,
+                  blocksToRemove,
+                  methodProcessor,
+                  methodProcessingContext);
+      if (instruction.isInvokeStatic()) {
+        rewriteInvokeStatic(
+            invoke.getInvokedMethod(),
+            invoke.getInterfaceBit(),
+            methodProcessingContext,
+            context,
+            synthesizedMethods::add,
+            rewriteInvoke,
+            rewriteToThrow);
+      } else {
+        assert instruction.isInvokeSuper();
+        rewriteInvokeSuper(invoke.getInvokedMethod(), context, rewriteInvoke, rewriteToThrow);
+      }
+    }
+  }
+
   private boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
     return appView.getSyntheticItems().isSyntheticMethodThatShouldNotBeDoubleProcessed(method);
   }
 
-  private void rewriteInvokeCustom(InvokeCustom invoke, ProgramMethod context) {
+  private void reportInterfaceMethodHandleCallSite(DexCallSite callSite, ProgramMethod context) {
     // Check that static interface methods are not referenced from invoke-custom instructions via
     // method handles.
-    DexCallSite callSite = invoke.getCallSite();
     reportStaticInterfaceMethodHandle(context, callSite.bootstrapMethod);
     for (DexValue arg : callSite.bootstrapArgs) {
       if (arg.isDexValueMethodHandle()) {
@@ -356,22 +661,23 @@
     }
   }
 
-  private void rewriteInvokeDirect(
-      InvokeDirect invoke, InstructionListIterator instructions, ProgramMethod context) {
-    DexMethod method = invoke.getInvokedMethod();
-    if (factory.isConstructor(method)) {
-      return;
+  private Collection<CfInstruction> rewriteInvokeDirect(
+      DexMethod invokedMethod,
+      ProgramMethod context,
+      Function<DexMethod, Collection<CfInstruction>> rewriteInvoke) {
+    if (factory.isConstructor(invokedMethod)) {
+      return null;
     }
 
-    DexClass clazz = appView.definitionForHolder(method, context);
+    DexClass clazz = appView.definitionForHolder(invokedMethod, context);
     if (clazz == null) {
       // Report missing class since we don't know if it is an interface.
-      warnMissingType(context, method.holder);
-      return;
+      warnMissingType(context, invokedMethod.holder);
+      return null;
     }
 
     if (!clazz.isInterface()) {
-      return;
+      return null;
     }
 
     if (clazz.isLibraryClass()) {
@@ -382,72 +688,63 @@
           getMethodOrigin(context.getReference()));
     }
 
-    DexClassAndMethod directTarget = clazz.lookupClassMethod(method);
+    DexClassAndMethod directTarget = clazz.lookupClassMethod(invokedMethod);
     if (directTarget != null) {
       // This can be a private instance method call. Note that the referenced
       // method is expected to be in the current class since it is private, but desugaring
       // may move some methods or their code into other classes.
-      assert invokeNeedsRewriting(method, DIRECT);
-      instructions.replaceCurrentInstruction(
-          new InvokeStatic(
-              directTarget.getDefinition().isPrivateMethod()
-                  ? privateAsMethodOfCompanionClass(directTarget)
-                  : defaultAsMethodOfCompanionClass(directTarget),
-              invoke.outValue(),
-              invoke.arguments()));
+      assert invokeNeedsRewriting(invokedMethod, DIRECT);
+      return rewriteInvoke.apply(
+          directTarget.getDefinition().isPrivateMethod()
+              ? privateAsMethodOfCompanionClass(directTarget)
+              : defaultAsMethodOfCompanionClass(directTarget));
     } else {
       // The method can be a default method in the interface hierarchy.
       DexClassAndMethod virtualTarget =
-          appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, method);
+          appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, invokedMethod);
       if (virtualTarget != null) {
         // This is a invoke-direct call to a virtual method.
-        assert invokeNeedsRewriting(method, DIRECT);
-        instructions.replaceCurrentInstruction(
-            new InvokeStatic(
-                defaultAsMethodOfCompanionClass(virtualTarget),
-                invoke.outValue(),
-                invoke.arguments()));
+        assert invokeNeedsRewriting(invokedMethod, DIRECT);
+        return rewriteInvoke.apply(defaultAsMethodOfCompanionClass(virtualTarget));
       } else {
         // The below assert is here because a well-type program should have a target, but we
         // cannot throw a compilation error, since we have no knowledge about the input.
         assert false;
       }
     }
+    return null;
   }
 
-  private void rewriteInvokeStatic(
-      InvokeStatic invoke,
-      IRCode code,
-      ListIterator<BasicBlock> blockIterator,
-      InstructionListIterator instructions,
-      Set<Value> affectedValues,
-      Set<BasicBlock> blocksToRemove,
-      MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext) {
-    DexMethod invokedMethod = invoke.getInvokedMethod();
+  private Collection<CfInstruction> rewriteInvokeStatic(
+      DexMethod invokedMethod,
+      boolean interfaceBit,
+      MethodProcessingContext methodProcessingContext,
+      ProgramMethod context,
+      Consumer<ProgramMethod> staticOutliningMethodConsumer,
+      Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
+      Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow) {
     if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
       // We did not create this code yet, but it will not require rewriting.
-      return;
+      return null;
     }
 
-    ProgramMethod context = code.context();
     DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
     if (clazz == null) {
       // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
       // exception but we can not report it as error since it can also be the intended
       // behavior.
-      if (invoke.getInterfaceBit()) {
+      if (interfaceBit) {
         leavingStaticInvokeToInterface(context);
       }
       warnMissingType(context, invokedMethod.holder);
-      return;
+      return null;
     }
 
     if (!clazz.isInterface()) {
-      if (invoke.getInterfaceBit()) {
+      if (interfaceBit) {
         leavingStaticInvokeToInterface(context);
       }
-      return;
+      return null;
     }
 
     if (isNonDesugaredLibraryClass(clazz)) {
@@ -467,6 +764,19 @@
         // so the user class is not rejected because it make this call directly.
         // TODO(b/166247515): If this an incorrect invoke-static without the interface bit
         //  we end up "fixing" the code and remove and ICCE error.
+        if (synthesizedMethods.contains(context)) {
+          // When reprocessing the method generated below, the desugaring asserts this method
+          // does not need any new desugaring, while the interface method rewriter tries
+          // to outline again the invoke-static. Just do nothing instead.
+          return null;
+        }
+        if (backportedMethodRewriter != null
+            && backportedMethodRewriter.methodIsBackport(invokedMethod)) {
+          // In Cf to Cf it is not allowed to desugar twice the same instruction, if the backported
+          // method rewriter already desugars the instruction, it takes precedence and nothing has
+          // to be done here.
+          return null;
+        }
         ProgramMethod newProgramMethod =
             appView
                 .getSyntheticItems()
@@ -484,19 +794,17 @@
                                         .setStaticTarget(invokedMethod, true)
                                         .setStaticSource(m)
                                         .build()));
+        staticOutliningMethodConsumer.accept(newProgramMethod);
         assert invokeNeedsRewriting(invokedMethod, STATIC);
-        instructions.replaceCurrentInstruction(
-            new InvokeStatic(
-                newProgramMethod.getReference(), invoke.outValue(), invoke.arguments()));
         // The synthetic dispatch class has static interface method invokes, so set
         // the class file version accordingly.
-        newProgramMethod.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
-        synthesizedMethods.add(newProgramMethod);
+        leavingStaticInvokeToInterface(newProgramMethod);
+        return rewriteInvoke.apply(newProgramMethod.getReference());
       } else {
         // When leaving static interface method invokes upgrade the class file version.
-        context.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
+        leavingStaticInvokeToInterface(context);
       }
-      return;
+      return null;
     }
 
     SingleResolutionResult resolutionResult =
@@ -504,67 +812,38 @@
             .appInfoForDesugaring()
             .resolveMethodOnInterface(clazz, invokedMethod)
             .asSingleResolution();
-    if (clazz.isInterface()
-        && rewriteInvokeToThrow(
-            invoke,
-            resolutionResult,
-            code,
-            blockIterator,
-            instructions,
-            affectedValues,
-            blocksToRemove,
-            methodProcessor,
-            methodProcessingContext)) {
-      assert invokeNeedsRewriting(invoke.getInvokedMethod(), STATIC);
-      return;
+    if (clazz.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, true)) {
+      assert invokeNeedsRewriting(invokedMethod, STATIC);
+      return rewriteToThrow.apply(resolutionResult);
     }
 
     assert resolutionResult != null;
     assert resolutionResult.getResolvedMethod().isStatic();
     assert invokeNeedsRewriting(invokedMethod, STATIC);
 
-    instructions.replaceCurrentInstruction(
-        new InvokeStatic(
-            staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()),
-            invoke.outValue(),
-            invoke.arguments()));
+    return rewriteInvoke.apply(
+        staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()));
   }
 
-  private void rewriteInvokeSuper(
-      InvokeSuper invoke,
-      IRCode code,
-      ListIterator<BasicBlock> blockIterator,
-      InstructionListIterator instructions,
-      Set<Value> affectedValues,
-      Set<BasicBlock> blocksToRemove,
-      MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext) {
-    ProgramMethod context = code.context();
-    DexMethod invokedMethod = invoke.getInvokedMethod();
+  private Collection<CfInstruction> rewriteInvokeSuper(
+      DexMethod invokedMethod,
+      ProgramMethod context,
+      Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
+      Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow) {
     DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
     if (clazz == null) {
       // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
       // exception but we can not report it as error since it can also be the intended
       // behavior.
       warnMissingType(context, invokedMethod.holder);
-      return;
+      return null;
     }
 
     SingleResolutionResult resolutionResult =
         appView.appInfoForDesugaring().resolveMethodOn(clazz, invokedMethod).asSingleResolution();
-    if (clazz.isInterface()
-        && rewriteInvokeToThrow(
-            invoke,
-            resolutionResult,
-            code,
-            blockIterator,
-            instructions,
-            affectedValues,
-            blocksToRemove,
-            methodProcessor,
-            methodProcessingContext)) {
-      assert invokeNeedsRewriting(invoke.getInvokedMethod(), SUPER);
-      return;
+    if (clazz.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, false)) {
+      assert invokeNeedsRewriting(invokedMethod, SUPER);
+      return rewriteToThrow.apply(resolutionResult);
     }
 
     if (clazz.isInterface() && !clazz.isLibraryClass()) {
@@ -578,81 +857,83 @@
       // WARNING: This may result in incorrect code on older platforms!
       // Retarget call to an appropriate method of companion class.
       assert invokeNeedsRewriting(invokedMethod, SUPER);
-      DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
-      instructions.replaceCurrentInstruction(
-          new InvokeStatic(
-              defaultAsMethodOfCompanionClass(amendedMethod, appView.dexItemFactory()),
-              invoke.outValue(),
-              invoke.arguments()));
-    } else {
-      DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
-      if (emulatedItf == null) {
-        if (clazz.isInterface() && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
-          DexClassAndMethod target =
-              appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
-          if (target != null && target.getDefinition().isDefaultMethod()) {
-            DexClass holder = target.getHolder();
-            if (holder.isLibraryClass() && holder.isInterface()) {
-              assert invokeNeedsRewriting(invokedMethod, SUPER);
-              instructions.replaceCurrentInstruction(
-                  new InvokeStatic(
-                      defaultAsMethodOfCompanionClass(target),
-                      invoke.outValue(),
-                      invoke.arguments()));
-            }
-          }
+      if (resolutionResult.getResolvedMethod().isPrivateMethod()) {
+        if (resolutionResult.isAccessibleFrom(context, appView.appInfoForDesugaring()).isFalse()) {
+          // TODO(b/145775365): This should throw IAE.
+          return rewriteToThrow.apply(null);
         }
+        return rewriteInvoke.apply(
+            privateAsMethodOfCompanionClass(resolutionResult.getResolutionPair()));
       } else {
-        // That invoke super may not resolve since the super method may not be present
-        // since it's in the emulated interface. We need to force resolution. If it resolves
-        // to a library method, then it needs to be rewritten.
-        // If it resolves to a program overrides, the invoke-super can remain.
-        DexClassAndMethod superTarget =
-            appView.appInfoForDesugaring().lookupSuperTarget(invoke.getInvokedMethod(), context);
-        if (superTarget != null && superTarget.isLibraryMethod()) {
-          // Rewriting is required because the super invoke resolves into a missing
-          // method (method is on desugared library). Find out if it needs to be
-          // retarget or if it just calls a companion class method and rewrite.
-          DexMethod retargetMethod =
-              options.desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
-          if (retargetMethod == null) {
-            DexMethod originalCompanionMethod = defaultAsMethodOfCompanionClass(superTarget);
-            DexMethod companionMethod =
-                factory.createMethod(
-                    getCompanionClassType(emulatedItf),
-                    factory.protoWithDifferentFirstParameter(
-                        originalCompanionMethod.proto, emulatedItf),
-                    originalCompanionMethod.name);
+        DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
+        return rewriteInvoke.apply(
+            defaultAsMethodOfCompanionClass(amendedMethod, appView.dexItemFactory()));
+      }
+    }
+
+    DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
+    if (emulatedItf == null) {
+      if (clazz.isInterface() && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
+        DexClassAndMethod target =
+            appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
+        if (target != null && target.getDefinition().isDefaultMethod()) {
+          DexClass holder = target.getHolder();
+          if (holder.isLibraryClass() && holder.isInterface()) {
             assert invokeNeedsRewriting(invokedMethod, SUPER);
-            instructions.replaceCurrentInstruction(
-                new InvokeStatic(companionMethod, invoke.outValue(), invoke.arguments()));
-          } else {
-            assert invokeNeedsRewriting(invokedMethod, SUPER);
-            instructions.replaceCurrentInstruction(
-                new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
+            return rewriteInvoke.apply(defaultAsMethodOfCompanionClass(target));
           }
         }
       }
+      return null;
     }
+    // That invoke super may not resolve since the super method may not be present
+    // since it's in the emulated interface. We need to force resolution. If it resolves
+    // to a library method, then it needs to be rewritten.
+    // If it resolves to a program overrides, the invoke-super can remain.
+    DexClassAndMethod superTarget =
+        appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
+    if (superTarget != null && superTarget.isLibraryMethod()) {
+      // Rewriting is required because the super invoke resolves into a missing
+      // method (method is on desugared library). Find out if it needs to be
+      // retarget or if it just calls a companion class method and rewrite.
+      DexMethod retargetMethod =
+          options.desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
+      if (retargetMethod == null) {
+        DexMethod originalCompanionMethod = defaultAsMethodOfCompanionClass(superTarget);
+        DexMethod companionMethod =
+            factory.createMethod(
+                getCompanionClassType(emulatedItf),
+                factory.protoWithDifferentFirstParameter(
+                    originalCompanionMethod.proto, emulatedItf),
+                originalCompanionMethod.name);
+        assert invokeNeedsRewriting(invokedMethod, SUPER);
+        return rewriteInvoke.apply(companionMethod);
+      } else {
+        assert invokeNeedsRewriting(invokedMethod, SUPER);
+        return rewriteInvoke.apply(retargetMethod);
+      }
+    }
+    return null;
   }
 
   private DexClassAndMethod defaultMethodForEmulatedDispatchOrNull(
-      DexMethod method, Type invokeType) {
-    assert invokeType == VIRTUAL || invokeType == INTERFACE;
-    boolean interfaceBit = invokeType.isInterface();
-    DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(method);
+      DexMethod invokedMethod, boolean interfaceBit) {
+    DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
     if (emulatedItf == null) {
       return null;
     }
     // The call potentially ends up in a library class, in which case we need to rewrite, since the
     // code may be in the desugared library.
     SingleResolutionResult resolution =
-        appView.appInfoForDesugaring().resolveMethod(method, interfaceBit).asSingleResolution();
+        appView
+            .appInfoForDesugaring()
+            .resolveMethod(invokedMethod, interfaceBit)
+            .asSingleResolution();
     if (resolution != null
         && (resolution.getResolvedHolder().isLibraryClass()
             || appView.options().isDesugaredLibraryCompilation())) {
       DexClassAndMethod defaultMethod =
-          appView.definitionFor(emulatedItf).lookupClassMethod(method);
+          appView.definitionFor(emulatedItf).lookupClassMethod(invokedMethod);
       if (defaultMethod != null && !dontRewrite(defaultMethod)) {
         assert !defaultMethod.getAccessFlags().isAbstract();
         return defaultMethod;
@@ -661,20 +942,25 @@
     return null;
   }
 
-  private void rewriteInvokeInterfaceOrInvokeVirtual(
-      InvokeMethodWithReceiver invoke, InstructionListIterator instructions) {
+  private Collection<CfInstruction> rewriteInvokeInterfaceOrInvokeVirtual(
+      DexMethod invokedMethod,
+      boolean interfaceBit,
+      Function<DexMethod, Collection<CfInstruction>> rewriteInvoke) {
     DexClassAndMethod defaultMethod =
-        defaultMethodForEmulatedDispatchOrNull(invoke.getInvokedMethod(), invoke.getType());
+        defaultMethodForEmulatedDispatchOrNull(invokedMethod, interfaceBit);
     if (defaultMethod != null) {
-      instructions.replaceCurrentInstruction(
-          new InvokeStatic(
-              emulateInterfaceLibraryMethod(defaultMethod, factory),
-              invoke.outValue(),
-              invoke.arguments()));
+      return rewriteInvoke.apply(emulateInterfaceLibraryMethod(defaultMethod, factory));
     }
+    return null;
   }
 
-  private boolean rewriteInvokeToThrow(
+  private boolean shouldRewriteToInvokeToThrow(
+      SingleResolutionResult resolutionResult, boolean isInvokeStatic) {
+    return resolutionResult == null
+        || resolutionResult.getResolvedMethod().isStatic() != isInvokeStatic;
+  }
+
+  private Collection<CfInstruction> rewriteInvokeToThrowIR(
       InvokeMethod invoke,
       SingleResolutionResult resolutionResult,
       IRCode code,
@@ -692,7 +978,8 @@
       methodSynthesizerConsumer =
           UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
     } else {
-      return false;
+      assert false;
+      return null;
     }
 
     // Replace by throw new SomeException.
@@ -725,7 +1012,7 @@
     throwBlockIterator.next();
     throwBlockIterator.replaceCurrentInstructionWithThrow(
         appView, code, blockIterator, throwInvoke.outValue(), blocksToRemove, affectedValues);
-    return true;
+    return null;
   }
 
   private DexType maximallySpecificEmulatedInterfaceOrNull(DexMethod invokedMethod) {
@@ -1100,7 +1387,7 @@
       return collection;
     }
     collection = createInterfaceInfo(classToDesugar, implementing, iface);
-    Collection existing = cache.putIfAbsent(iface, collection);
+    DefaultMethodsHelper.Collection existing = cache.putIfAbsent(iface, collection);
     return existing != null ? existing : collection;
   }
 
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 ad1cacb..71010c3 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
@@ -158,6 +158,11 @@
 
   @Override
   public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    return isLambdaInvoke(instruction, context, appView);
+  }
+
+  public static boolean isLambdaInvoke(
+      CfInstruction instruction, ProgramMethod context, AppView<?> appView) {
     return instruction.isInvokeDynamic()
         && LambdaDescriptor.tryInfer(
                 instruction.asInvokeDynamic().getCallSite(),
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 4f4d963..607b088 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
@@ -216,13 +216,17 @@
 
   @Override
   public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
-    return instruction.isInvokeDynamic()
-        && needsDesugaring(instruction.asInvokeDynamic().getCallSite());
+    return isStringConcatInvoke(instruction, factory);
   }
 
-  private boolean needsDesugaring(DexCallSite callSite) {
+  public static boolean isStringConcatInvoke(CfInstruction instruction, DexItemFactory factory) {
+    CfInvokeDynamic invoke = instruction.asInvokeDynamic();
+    if (invoke == null) {
+      return false;
+    }
     // We are interested in bootstrap methods StringConcatFactory::makeConcat
     // and StringConcatFactory::makeConcatWithConstants, both are static.
+    DexCallSite callSite = invoke.getCallSite();
     if (callSite.bootstrapMethod.type.isInvokeStatic()) {
       DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
       return bootstrapMethod == factory.stringConcatFactoryMembers.makeConcat
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index 953a78c..2b15739 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -177,6 +177,10 @@
       return method;
     }
 
+    public ProgramMethod uncheckedGetMethod() {
+      return method;
+    }
+
     public void optimize(MethodProcessor methodProcessor) {
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
       optimized = true;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index ce1647b..20f0ab4 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -207,6 +207,11 @@
         createDescriptor(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, kind, context.getBinaryName(), id));
   }
 
+  public static boolean isInternalStaticInterfaceCall(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(
+        reference, Phase.INTERNAL, SyntheticKind.STATIC_INTERFACE_CALL);
+  }
+
   static boolean isSynthetic(ClassReference clazz, Phase phase, SyntheticKind kind) {
     String typeName = clazz.getTypeName();
     if (kind.isFixedSuffixSynthetic) {
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
index b54a7d5..35e52b4 100644
--- a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
@@ -15,12 +15,12 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -149,8 +149,8 @@
     return box.get();
   }
 
-  private Set<MethodReference> getSyntheticMethods(CodeInspector inspector) {
-    Set<MethodReference> methods = new HashSet<>();
+  private Set<FoundMethodSubject> getSyntheticMethods(CodeInspector inspector) {
+    Set<FoundMethodSubject> methods = new HashSet<>();
     assert inspector.allClasses().stream()
         .allMatch(
             c ->
@@ -159,10 +159,7 @@
                         c.getFinalReference()));
     inspector.allClasses().stream()
         .filter(c -> SyntheticItemsTestUtils.isExternalStaticInterfaceCall(c.getFinalReference()))
-        .forEach(
-            c ->
-                c.allMethods(m -> !m.isInstanceInitializer())
-                    .forEach(m -> methods.add(m.asMethodReference())));
+        .forEach(c -> methods.addAll(c.allMethods(m -> !m.isInstanceInitializer())));
     return methods;
   }