Merge commit '74b2bd6f9854f668928607bab99fdc59d264783a' into dev-release
diff --git a/build.gradle b/build.gradle
index 33dfc26..d7c83e7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -294,6 +294,7 @@
 def r8LibTestPath = "$buildDir/classes/r8libtest"
 def java11ClassFiles = "$buildDir/classes/java/mainJava11"
 def r8RetracePath = "$buildDir/libs/r8retrace.jar"
+def r8RetraceExludeDepsPath = "$buildDir/libs/r8retrace-exclude-deps.jar"
 
 def osString = OperatingSystem.current().isLinux() ? "linux" :
         OperatingSystem.current().isMacOsX() ? "mac" : "windows"
@@ -1082,6 +1083,19 @@
     outputs.file r8RetracePath
 }
 
+task R8RetraceNoDeps {
+    dependsOn R8LibNoDeps
+    dependsOn r8LibCreateTask(
+            "RetraceNoDeps",
+            ["src/main/keep_retrace.txt"],
+            R8LibNoDeps,
+            r8RetraceExludeDepsPath,
+            "--release",
+            repackageDepsNew.outputs.files
+    ).dependsOn(R8LibNoDeps)
+    outputs.file r8RetraceExludeDepsPath
+}
+
 task sourceJar(type: Jar, dependsOn: classes) {
     classifier = 'src'
     from sourceSets.main.allSource
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index 559d2f2..187402a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -6,8 +6,8 @@
 
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.utils.MapUtils;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableList;
@@ -26,8 +26,8 @@
  */
 public final class AppliedGraphLens extends NonIdentityGraphLens {
 
-  private final MutableBidirectionalManyToOneMap<DexType, DexType> renamedTypeNames =
-      new BidirectionalManyToOneHashMap<>();
+  private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> renamedTypeNames =
+      new BidirectionalManyToOneRepresentativeHashMap<>();
   private final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
   private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
 
@@ -39,6 +39,12 @@
   public AppliedGraphLens(AppView<? extends AppInfoWithClassHierarchy> appView) {
     super(appView.dexItemFactory(), GraphLens.getIdentityLens());
     for (DexProgramClass clazz : appView.appInfo().classes()) {
+      // TODO(b/169395592): If merged classes were removed from the application this would not be
+      //  necessary.
+      if (appView.graphLens().lookupType(clazz.getType()) != clazz.getType()) {
+        continue;
+      }
+
       // Record original type names.
       recordOriginalTypeNames(clazz, appView);
 
@@ -83,7 +89,12 @@
     List<DexType> originalTypes = Lists.newArrayList(appView.graphLens().getOriginalTypes(type));
     boolean isIdentity = originalTypes.size() == 1 && originalTypes.get(0) == type;
     if (!isIdentity) {
-      originalTypes.forEach(originalType -> renamedTypeNames.put(originalType, type));
+      originalTypes.forEach(
+          originalType -> {
+            assert !renamedTypeNames.containsKey(originalType);
+            renamedTypeNames.put(originalType, type);
+          });
+      renamedTypeNames.setRepresentative(type, appView.graphLens().getOriginalType(type));
     }
   }
 
@@ -94,11 +105,7 @@
 
   @Override
   public DexType getOriginalType(DexType type) {
-    Set<DexType> originalTypeNames = renamedTypeNames.getKeys(type);
-    if (!originalTypeNames.isEmpty()) {
-      return originalTypeNames.iterator().next();
-    }
-    return type;
+    return renamedTypeNames.getRepresentativeKeyOrDefault(type, type);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 70bad80..22a830e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import static com.google.common.base.Predicates.alwaysFalse;
+import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
@@ -121,6 +122,16 @@
     }
   }
 
+  public void forEachClassMethod(Consumer<? super DexClassAndMethod> consumer) {
+    forEachClassMethodMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachClassMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<? super DexClassAndMethod> consumer) {
+    methodCollection.forEachMethodMatching(
+        predicate, method -> consumer.accept(DexClassAndMethod.create(this, method)));
+  }
+
   @Override
   public ClassAccessFlags getAccessFlags() {
     return accessFlags;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 0a17134..0446e09 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -27,11 +27,23 @@
     }
   }
 
+  public boolean isDefaultMethod() {
+    return getHolder().isInterface() && getDefinition().isDefaultMethod();
+  }
+
+  public boolean isStructurallyEqualTo(DexClassAndMethod other) {
+    return getDefinition() == other.getDefinition() && getHolder() == other.getHolder();
+  }
+
   @Override
   public MethodAccessFlags getAccessFlags() {
     return getDefinition().getAccessFlags();
   }
 
+  public DexProto getProto() {
+    return getReference().getProto();
+  }
+
   @Override
   public boolean isMethodTarget() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 4fcd92b..91203c8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1383,13 +1383,13 @@
   }
 
   public static DexEncodedMethod createDesugaringForwardingMethod(
-      DexEncodedMethod target, DexClass clazz, DexMethod forwardMethod, DexItemFactory factory) {
-    DexMethod method = target.method;
+      DexClassAndMethod target, DexClass clazz, DexMethod forwardMethod, DexItemFactory factory) {
+    DexMethod method = target.getReference();
     assert forwardMethod != null;
     // New method will have the same name, proto, and also all the flags of the
     // default method, including bridge flag.
     DexMethod newMethod = factory.createMethod(clazz.type, method.proto, method.name);
-    MethodAccessFlags newFlags = target.accessFlags.copy();
+    MethodAccessFlags newFlags = target.getAccessFlags().copy();
     // Some debuggers (like IntelliJ) automatically skip synthetic methods on single step.
     newFlags.setSynthetic();
     newFlags.unsetAbstract();
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 385fb58..e6d824a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -682,14 +682,18 @@
   // Library methods listed here are based on their original implementations. That is, we assume
   // these cannot be overridden.
   public final Set<DexMethod> libraryMethodsReturningNonNull =
-      ImmutableSet.of(
-          classMethods.getName,
-          classMethods.getSimpleName,
-          classMethods.forName,
-          objectsMethods.requireNonNull,
-          objectsMethods.requireNonNullWithMessage,
-          objectsMethods.requireNonNullWithMessageSupplier,
-          stringMembers.valueOf);
+      ImmutableSet.<DexMethod>builder()
+          .add(
+              classMethods.getName,
+              classMethods.getSimpleName,
+              classMethods.forName,
+              objectsMethods.requireNonNull,
+              objectsMethods.requireNonNullWithMessage,
+              objectsMethods.requireNonNullWithMessageSupplier,
+              stringMembers.valueOf)
+          .addAll(stringBufferMethods.appendMethods)
+          .addAll(stringBuilderMethods.appendMethods)
+          .build();
 
   // TODO(b/119596718): More idempotent methods? Any singleton accessors? E.g.,
   // java.util.Calendar#getInstance(...) // 4 variants
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index e00c1fe..d35b1a0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -261,6 +261,10 @@
     return factory.isConstructor(this);
   }
 
+  public boolean mustBeInlinedIntoInstanceInitializer(DexItemFactory dexItemFactory) {
+    return getName().startsWith(dexItemFactory.temporaryConstructorMethodPrefix);
+  }
+
   public DexMethod withExtraArgumentPrepended(DexType type, DexItemFactory dexItemFactory) {
     return dexItemFactory.createMethod(
         holder, dexItemFactory.prependTypeToProto(type, proto), name);
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 376abd6..27284ea 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -7,7 +7,6 @@
 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 static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
@@ -42,7 +41,7 @@
   // Bundletool is merging classes that may originate from a build with an old version of R8.
   // Allow merging of classes that use names from older versions of R8.
   private static List<String> OLD_SYNTHESIZED_NAMES =
-      ImmutableList.of("$r8$backportedMethods$utility", "$r8$java8methods$utility");
+      ImmutableList.of("$r8$backportedMethods$utility", "$r8$java8methods$utility", "$-DC");
 
   public final DexString descriptor;
   private String toStringCache = null;
@@ -313,9 +312,11 @@
     String name = toSourceString();
     // The synthesized classes listed here must always be unique to a program context and thus
     // never duplicated for distinct inputs.
-    return
-    // Hygienic suffix.
-    name.contains(COMPANION_CLASS_NAME_SUFFIX)
+    return false
+        // Hygienic suffix.
+        || name.contains(COMPANION_CLASS_NAME_SUFFIX)
+        || name.contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
+        || name.contains(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX)
         // New and hygienic synthesis infrastructure.
         || name.contains(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)
         || name.contains(SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR)
@@ -336,11 +337,8 @@
   private static boolean isSynthesizedTypeThatCouldBeDuplicated(String name) {
     // Any entry that is removed from here must be added to OLD_SYNTHESIZED_NAMES to ensure that
     // newer releases can be used to merge previous builds.
-    return name.contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX) // Shared among enums.
-        || name.contains(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX)
-        || name.contains(LAMBDA_CLASS_NAME_PREFIX) // Could collide.
+    return name.contains(LAMBDA_CLASS_NAME_PREFIX) // Could collide.
         || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
-        || name.contains(DISPATCH_CLASS_NAME_SUFFIX) // Shared on reference.
         || name.contains(OutlineOptions.CLASS_NAME) // Global singleton.
         || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME) // Global singleton.
         || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME) // Global singleton.
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 33063ba..3dc807c 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -52,10 +52,6 @@
     definition.parameterAnnotationsList.collectIndexedItems(indexedItems);
   }
 
-  public boolean isStructurallyEqualTo(ProgramMethod other) {
-    return getDefinition() == other.getDefinition() && getHolder() == other.getHolder();
-  }
-
   public void registerCodeReferences(UseRegistry registry) {
     Code code = getDefinition().getCode();
     if (code != null) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 495be54..dd0486a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -155,7 +155,7 @@
         new AllInstantiatedOrUninstantiated(appView),
         new SameParentClass(),
         new SameNestHost(),
-        new PreserveMethodCharacteristics(),
+        new PreserveMethodCharacteristics(appView),
         new SameFeatureSplit(appView),
         new RespectPackageBoundaries(appView),
         new DontMergeSynchronizedClasses(appView),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index 431fdb9..3adb35c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -4,12 +4,14 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
@@ -28,9 +30,10 @@
   static class MethodCharacteristics {
 
     private final MethodAccessFlags accessFlags;
+    private final boolean isAssumeNoSideEffectsMethod;
     private final OptionalBool isLibraryMethodOverride;
 
-    private MethodCharacteristics(DexEncodedMethod method) {
+    private MethodCharacteristics(boolean isAssumeNoSideEffectsMethod, DexEncodedMethod method) {
       this.accessFlags =
           MethodAccessFlags.builder()
               .setPrivate(method.getAccessFlags().isPrivate())
@@ -39,9 +42,16 @@
               .setStrict(method.getAccessFlags().isStrict())
               .setSynchronized(method.getAccessFlags().isSynchronized())
               .build();
+      this.isAssumeNoSideEffectsMethod = isAssumeNoSideEffectsMethod;
       this.isLibraryMethodOverride = method.isLibraryMethodOverride();
     }
 
+    static MethodCharacteristics create(
+        AppView<AppInfoWithLiveness> appView, DexEncodedMethod method) {
+      return new MethodCharacteristics(
+          appView.appInfo().isAssumeNoSideEffectsMethod(method.getReference()), method);
+    }
+
     @Override
     public int hashCode() {
       return (accessFlags.hashCode() << 2) | isLibraryMethodOverride.ordinal();
@@ -56,12 +66,17 @@
         return false;
       }
       MethodCharacteristics characteristics = (MethodCharacteristics) obj;
-      return isLibraryMethodOverride == characteristics.isLibraryMethodOverride
-          && accessFlags.equals(characteristics.accessFlags);
+      return accessFlags.equals(characteristics.accessFlags)
+          && isAssumeNoSideEffectsMethod == characteristics.isAssumeNoSideEffectsMethod
+          && isLibraryMethodOverride == characteristics.isLibraryMethodOverride;
     }
   }
 
-  public PreserveMethodCharacteristics() {}
+  private final AppView<AppInfoWithLiveness> appView;
+
+  public PreserveMethodCharacteristics(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
 
   public static class TargetGroup {
 
@@ -72,12 +87,12 @@
       return group;
     }
 
-    public boolean tryAdd(DexProgramClass clazz) {
+    public boolean tryAdd(AppView<AppInfoWithLiveness> appView, DexProgramClass clazz) {
       Map<DexMethodSignature, MethodCharacteristics> newMethods = new HashMap<>();
       for (DexEncodedMethod method : clazz.methods()) {
         DexMethodSignature signature = method.getSignature();
         MethodCharacteristics existingCharacteristics = methodMap.get(signature);
-        MethodCharacteristics methodCharacteristics = new MethodCharacteristics(method);
+        MethodCharacteristics methodCharacteristics = MethodCharacteristics.create(appView, method);
         if (existingCharacteristics == null) {
           newMethods.put(signature, methodCharacteristics);
           continue;
@@ -97,10 +112,10 @@
     List<TargetGroup> groups = new ArrayList<>();
 
     for (DexProgramClass clazz : group) {
-      boolean added = Iterables.any(groups, targetGroup -> targetGroup.tryAdd(clazz));
+      boolean added = Iterables.any(groups, targetGroup -> targetGroup.tryAdd(appView, clazz));
       if (!added) {
         TargetGroup newGroup = new TargetGroup();
-        added = newGroup.tryAdd(clazz);
+        added = newGroup.tryAdd(appView, clazz);
         assert added;
         groups.add(newGroup);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index ee770e8..a1c4e7b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
@@ -12,6 +13,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+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.ProgramMethod;
@@ -19,6 +23,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.optimize.NestUtils;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.google.common.collect.ImmutableList;
@@ -269,6 +274,76 @@
   }
 
   @Override
+  public boolean replaceCurrentInstructionByNullCheckIfPossible(
+      AppView<?> appView, ProgramMethod context) {
+    Instruction toBeReplaced = current;
+    assert toBeReplaced != null;
+    assert toBeReplaced.isInstanceFieldInstruction() || toBeReplaced.isInvokeMethodWithReceiver();
+    if (toBeReplaced.hasUsedOutValue()) {
+      return false;
+    }
+    if (toBeReplaced.isInvokeDirect()) {
+      DexItemFactory dexItemFactory = appView.dexItemFactory();
+      DexMethod invokedMethod = toBeReplaced.asInvokeDirect().getInvokedMethod();
+      if (invokedMethod.isInstanceInitializer(dexItemFactory)
+          || invokedMethod.mustBeInlinedIntoInstanceInitializer(dexItemFactory)) {
+        return false;
+      }
+    }
+    if (toBeReplaced.instructionMayHaveSideEffects(
+        appView, context, Instruction.SideEffectAssumption.RECEIVER_NOT_NULL)) {
+      return false;
+    }
+    Value receiver =
+        toBeReplaced.isInstanceFieldInstruction()
+            ? toBeReplaced.asInstanceFieldInstruction().object()
+            : toBeReplaced.asInvokeMethodWithReceiver().getReceiver();
+    if (receiver.isNeverNull()) {
+      removeOrReplaceByDebugLocalRead();
+      return true;
+    }
+    InvokeMethod replacement;
+    if (appView.options().canUseRequireNonNull()) {
+      DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
+      replacement = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver));
+    } else {
+      DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
+      replacement = new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver));
+    }
+    replaceCurrentInstruction(replacement);
+    return true;
+  }
+
+  @Override
+  public boolean replaceCurrentInstructionByInitClassIfPossible(
+      AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+    Instruction toBeReplaced = current;
+    assert toBeReplaced != null;
+    assert toBeReplaced.isStaticFieldInstruction() || toBeReplaced.isInvokeStatic();
+    if (toBeReplaced.hasUsedOutValue()) {
+      return false;
+    }
+    ProgramMethod context = code.context();
+    if (toBeReplaced.instructionMayHaveSideEffects(
+        appView, context, Instruction.SideEffectAssumption.CLASS_ALREADY_INITIALIZED)) {
+      return false;
+    }
+    if (!type.classInitializationMayHaveSideEffectsInContext(appView, context)) {
+      removeOrReplaceByDebugLocalRead();
+      return true;
+    }
+    if (!appView.canUseInitClass()) {
+      return false;
+    }
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+    if (clazz != null) {
+      Value dest = code.createValue(TypeElement.getInt());
+      replaceCurrentInstruction(new InitClass(dest, clazz.type));
+    }
+    return true;
+  }
+
+  @Override
   public void replaceCurrentInstructionWithConstClass(
       AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
     if (current == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 763d520..b849f75 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -47,7 +47,10 @@
   }
 
   public static ConstNumber asConstNumberOrNull(Instruction instruction) {
-    return (ConstNumber) instruction;
+    if (instruction == null) {
+      return null;
+    }
+    return instruction.asConstNumber();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index a96760f..757a581 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -11,7 +11,9 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.ListIterator;
 import java.util.NoSuchElementException;
@@ -42,6 +44,18 @@
   }
 
   @Override
+  public boolean replaceCurrentInstructionByNullCheckIfPossible(
+      AppView<?> appView, ProgramMethod context) {
+    return instructionIterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
+  }
+
+  @Override
+  public boolean replaceCurrentInstructionByInitClassIfPossible(
+      AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+    return instructionIterator.replaceCurrentInstructionByInitClassIfPossible(appView, code, type);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithConstClass(
       AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
     instructionIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index e8ba6bd..689514f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -130,7 +130,11 @@
   }
 
   public boolean hasUnusedOutValue() {
-    return !hasOutValue() || !outValue().hasAnyUsers();
+    return !hasUsedOutValue();
+  }
+
+  public boolean hasUsedOutValue() {
+    return hasOutValue() && outValue().hasAnyUsers();
   }
 
   public Value outValue() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index c4cceea..6b17a04 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -11,7 +11,9 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
@@ -97,6 +99,11 @@
 
   Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value);
 
+  boolean replaceCurrentInstructionByNullCheckIfPossible(AppView<?> appView, ProgramMethod context);
+
+  boolean replaceCurrentInstructionByInitClassIfPossible(
+      AppView<AppInfoWithLiveness> appView, IRCode code, DexType type);
+
   void replaceCurrentInstructionWithConstClass(
       AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo);
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 31fd8f4..5793ec1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -144,6 +144,10 @@
 
   abstract public DexType getReturnType();
 
+  public boolean hasArguments() {
+    return !arguments().isEmpty();
+  }
+
   public boolean hasReturnTypeVoid(DexItemFactory factory) {
     return getReturnType() == factory.voidType;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 0fc2768..bbb6d27 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -10,7 +10,9 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
@@ -62,6 +64,18 @@
   }
 
   @Override
+  public boolean replaceCurrentInstructionByNullCheckIfPossible(
+      AppView<?> appView, ProgramMethod context) {
+    return currentBlockIterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
+  }
+
+  @Override
+  public boolean replaceCurrentInstructionByInitClassIfPossible(
+      AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+    return currentBlockIterator.replaceCurrentInstructionByInitClassIfPossible(appView, code, type);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithConstClass(
       AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
     currentBlockIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
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 e2b0e47..d08eb3e 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
@@ -442,8 +442,7 @@
   }
 
   private void synthesizeTwrCloseResourceUtilityClass(
-      Builder<?> builder, ExecutorService executorService)
-      throws ExecutionException {
+      Builder<?> builder, ExecutorService executorService) throws ExecutionException {
     if (twrCloseResourceRewriter != null) {
       twrCloseResourceRewriter.synthesizeUtilityClass(builder, executorService, options);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index f6ad5f3..f348214 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -56,14 +56,14 @@
   public void registerInvokeVirtual(DexMethod method) {
     registerBackportedMethodRewriting(method);
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method);
+    registerInterfaceMethodRewriting(method, false);
     registerDesugaredLibraryAPIConverter(method);
   }
 
   @Override
   public void registerInvokeDirect(DexMethod method) {
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method);
+    registerInterfaceMethodRewriting(method, false);
     registerDesugaredLibraryAPIConverter(method);
   }
 
@@ -73,10 +73,11 @@
     }
   }
 
-  private void registerInterfaceMethodRewriting(DexMethod method) {
+  private void registerInterfaceMethodRewriting(DexMethod method, boolean isInvokeSuper) {
     if (!needsDesugarging) {
       needsDesugarging =
-          interfaceMethodRewriter != null && interfaceMethodRewriter.needsRewriting(method);
+          interfaceMethodRewriter != null
+              && interfaceMethodRewriter.needsRewriting(method, isInvokeSuper, appView);
     }
   }
 
@@ -103,14 +104,14 @@
     }
     registerBackportedMethodRewriting(method);
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method);
+    registerInterfaceMethodRewriting(method, false);
     registerDesugaredLibraryAPIConverter(method);
   }
 
   @Override
   public void registerInvokeInterface(DexMethod method) {
     registerLibraryRetargeting(method, true);
-    registerInterfaceMethodRewriting(method);
+    registerInterfaceMethodRewriting(method, false);
     registerDesugaredLibraryAPIConverter(method);
   }
 
@@ -131,7 +132,7 @@
   @Override
   public void registerInvokeSuper(DexMethod method) {
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method);
+    registerInterfaceMethodRewriting(method, true);
     registerDesugaredLibraryAPIConverter(method);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 1e3b81f..6200603 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GenericSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -25,6 +27,7 @@
 import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -39,7 +42,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import org.objectweb.asm.Opcodes;
 
@@ -105,14 +107,14 @@
     // List of methods that are known to be forwarded to by a forwarding method at this point in the
     // class hierarchy. This set consists of the default interface methods, i.e., the targets of the
     // forwarding methods, *not* the forwarding methods themselves.
-    final ImmutableList<DexEncodedMethod> forwardedMethodTargets;
+    final ImmutableList<DexClassAndMethod> forwardedMethodTargets;
     // If the forwarding methods for the emulated interface methods have not been added yet,
     // this contains the information to add it in the subclasses.
     final EmulatedInterfaceInfo emulatedInterfaceInfo;
 
     ClassInfo(
         ClassInfo parent,
-        ImmutableList<DexEncodedMethod> forwardedMethodTargets,
+        ImmutableList<DexClassAndMethod> forwardedMethodTargets,
         EmulatedInterfaceInfo emulatedInterfaceInfo) {
       this.parent = parent;
       this.forwardedMethodTargets = forwardedMethodTargets;
@@ -121,7 +123,7 @@
 
     static ClassInfo create(
         ClassInfo parent,
-        ImmutableList<DexEncodedMethod> forwardedMethodTargets,
+        ImmutableList<DexClassAndMethod> forwardedMethodTargets,
         EmulatedInterfaceInfo emulatedInterfaceInfo) {
       return forwardedMethodTargets.isEmpty()
           ? parent
@@ -132,8 +134,11 @@
       return this == EMPTY;
     }
 
-    boolean isTargetedByForwards(DexEncodedMethod method) {
-      return forwardedMethodTargets.contains(method)
+    boolean isTargetedByForwards(DexClassAndMethod method) {
+      return IterableUtils.any(
+              forwardedMethodTargets,
+              DexClassAndMember::getDefinition,
+              definition -> definition == method.getDefinition())
           || (parent != null && parent.isTargetedByForwards(method));
     }
   }
@@ -394,7 +399,7 @@
   // The computation of a class information and the insertions of forwarding methods.
   private ClassInfo computeClassInfo(
       DexClass clazz, ClassInfo superInfo, SignaturesInfo signatureInfo) {
-    Builder<DexEncodedMethod> additionalForwards = ImmutableList.builder();
+    ImmutableList.Builder<DexClassAndMethod> additionalForwards = ImmutableList.builder();
     // First we deal with non-emulated interface desugaring.
     resolveForwardingMethods(clazz, superInfo, signatureInfo.signatures, additionalForwards);
     // Second we deal with emulated interface, if one method has override in the current class,
@@ -507,7 +512,7 @@
       DexClass clazz,
       ClassInfo superInfo,
       MethodSignatures signatures,
-      Builder<DexEncodedMethod> additionalForwards) {
+      Builder<DexClassAndMethod> additionalForwards) {
     if (clazz.isProgramClass() && appView.isAlreadyLibraryDesugared(clazz.asProgramClass())) {
       return;
     }
@@ -515,10 +520,10 @@
       resolveForwardForSignature(
           clazz,
           wrapper.get(),
-          (targetHolder, target) -> {
+          target -> {
             if (!superInfo.isTargetedByForwards(target)) {
               additionalForwards.add(target);
-              addForwardingMethod(targetHolder, target, clazz);
+              addForwardingMethod(target, clazz);
             }
           });
     }
@@ -527,7 +532,7 @@
   // Looks up a method signature from the point of 'clazz', if it can dispatch to a default method
   // the 'addForward' call-back is called with the target of the forward.
   private void resolveForwardForSignature(
-      DexClass clazz, DexMethod method, BiConsumer<DexClass, DexEncodedMethod> addForward) {
+      DexClass clazz, DexMethod method, Consumer<DexClassAndMethod> addForward) {
     // Resolve the default method with base type as the symbolic holder as call sites are not known.
     // The dispatch target is then looked up from the possible "instance" class.
     // Doing so can cause an invalid invoke to become valid (at runtime resolution at a subtype
@@ -550,54 +555,52 @@
       return;
     }
 
-    DexEncodedMethod target = virtualDispatchTarget.getDefinition();
-    DexClass targetHolder = virtualDispatchTarget.getHolder();
     // Don't forward if the target is explicitly marked as 'dont-rewrite'
-    if (dontRewrite(targetHolder, target)) {
+    if (dontRewrite(virtualDispatchTarget)) {
       return;
     }
 
     // If resolution targets a default interface method, forward it.
-    if (targetHolder.isInterface() && target.isDefaultMethod()) {
-      addForward.accept(targetHolder, target);
+    if (virtualDispatchTarget.isDefaultMethod()) {
+      addForward.accept(virtualDispatchTarget);
       return;
     }
 
     // Remaining edge cases only pertain to desugaring of library methods.
-    DexLibraryClass libraryHolder = targetHolder.asLibraryClass();
-    if (libraryHolder == null || ignoreLibraryInfo()) {
+    if (!virtualDispatchTarget.isLibraryMethod() || ignoreLibraryInfo()) {
       return;
     }
 
-    if (isRetargetMethod(libraryHolder, target)) {
-      addForward.accept(targetHolder, target);
+    LibraryMethod libraryMethod = virtualDispatchTarget.asLibraryMethod();
+    if (isRetargetMethod(libraryMethod)) {
+      addForward.accept(virtualDispatchTarget);
       return;
     }
 
     // If target is a non-interface library class it may be an emulated interface,
     // except on a rewritten type, where L8 has already dealt with the desugaring.
-    if (!libraryHolder.isInterface()
-        && !appView.rewritePrefix.hasRewrittenType(libraryHolder.type, appView)) {
+    if (!libraryMethod.getHolder().isInterface()
+        && !appView.rewritePrefix.hasRewrittenType(libraryMethod.getHolderType(), appView)) {
       // Here we use step-3 of resolution to find a maximally specific default interface method.
-      DexClassAndMethod result = appInfo.lookupMaximallySpecificMethod(libraryHolder, method);
-      if (result != null && rewriter.isEmulatedInterface(result.getHolder().type)) {
-        addForward.accept(result.getHolder(), result.getDefinition());
+      DexClassAndMethod result =
+          appInfo.lookupMaximallySpecificMethod(libraryMethod.getHolder(), method);
+      if (result != null && rewriter.isEmulatedInterface(result.getHolderType())) {
+        addForward.accept(result);
       }
     }
   }
 
-  private boolean isRetargetMethod(DexLibraryClass holder, DexEncodedMethod method) {
+  private boolean isRetargetMethod(LibraryMethod method) {
     assert needsLibraryInfo();
-    assert holder.type == method.getHolderType();
-    assert method.isNonPrivateVirtualMethod();
-    if (method.isFinal()) {
-      return false;
-    }
-    return appView.options().desugaredLibraryConfiguration.retargetMethod(method, appView) != null;
+    assert method.getDefinition().isNonPrivateVirtualMethod();
+    return !method.getAccessFlags().isFinal()
+        && appView.options().desugaredLibraryConfiguration.retargetMethod(method, appView) != null;
   }
 
-  private boolean dontRewrite(DexClass clazz, DexEncodedMethod method) {
-    return needsLibraryInfo() && clazz.isLibraryClass() && rewriter.dontRewrite(method.method);
+  private boolean dontRewrite(DexClassAndMethod method) {
+    return needsLibraryInfo()
+        && method.getHolder().isLibraryClass()
+        && rewriter.dontRewrite(method);
   }
 
   // Construction of actual forwarding methods.
@@ -630,13 +633,12 @@
 
   // Note: The parameter 'target' may be a public method on a class in case of desugared
   // library retargeting (See below target.isInterface check).
-  private void addForwardingMethod(DexClass targetHolder, DexEncodedMethod target, DexClass clazz) {
-    assert targetHolder != null;
+  private void addForwardingMethod(DexClassAndMethod target, DexClass clazz) {
     if (!clazz.isProgramClass()) {
       return;
     }
 
-    DexEncodedMethod methodOnSelf = clazz.lookupMethod(target.method);
+    DexEncodedMethod methodOnSelf = clazz.lookupMethod(target.getReference());
     if (methodOnSelf != null) {
       throw new CompilationError(
           "Attempt to add forwarding method that conflicts with existing method.",
@@ -645,13 +647,12 @@
           new MethodPosition(methodOnSelf.method.asMethodReference()));
     }
 
-    DexMethod method = target.method;
     // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
     // even if this results in invalid code, these classes are never desugared.
     // In desugared library, emulated interface methods can be overridden by retarget lib members.
     DexMethod forwardMethod =
-        targetHolder.isInterface()
-            ? rewriter.defaultAsMethodOfCompanionClass(method)
+        target.getHolder().isInterface()
+            ? rewriter.defaultAsMethodOfCompanionClass(target)
             : appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
     DexEncodedMethod desugaringForwardingMethod =
         DexEncodedMethod.createDesugaringForwardingMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index c2b151c..3dc74ac 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -44,9 +44,10 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -73,7 +74,7 @@
   // the invoke needs to be rewritten.
   private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites = new IdentityHashMap<>();
   // Non final virtual library methods requiring generation of emulated dispatch.
-  private final Set<DexEncodedMethod> emulatedDispatchMethods = Sets.newIdentityHashSet();
+  private final DexClassAndMethodSet emulatedDispatchMethods = DexClassAndMethodSet.create();
 
   public DesugaredLibraryRetargeter(AppView<?> appView) {
     this.appView = appView;
@@ -316,9 +317,6 @@
       DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
       assert singleTarget != null;
       retarget = getRetargetLibraryMember(singleTarget.method);
-      if (retarget == null) {
-        return null;
-      }
     }
     return retarget;
   }
@@ -356,52 +354,80 @@
           DexClass typeClass = appView.definitionFor(inType);
           if (typeClass != null) {
             DexType newHolder = retargetCoreLibMember.get(methodName).get(inType);
-            List<DexEncodedMethod> found = findDexEncodedMethodsWithName(methodName, typeClass);
-            for (DexEncodedMethod encodedMethod : found) {
+            List<DexClassAndMethod> found = findMethodsWithName(methodName, typeClass);
+            for (DexClassAndMethod method : found) {
+              DexMethod methodReference = method.getReference();
               if (!typeClass.isFinal()) {
-                nonFinalHolderRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
-                nonFinalHolderRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
-                if (!encodedMethod.isStatic()) {
-                  if (InterfaceMethodRewriter.isEmulatedInterfaceDispatch(appView, encodedMethod)) {
+                nonFinalHolderRewrites.putIfAbsent(method.getName(), new ArrayList<>());
+                nonFinalHolderRewrites.get(method.getName()).add(methodReference);
+                if (!method.getAccessFlags().isStatic()) {
+                  if (isEmulatedInterfaceDispatch(method)) {
                     // In this case interface method rewriter takes care of it.
                     continue;
-                  } else if (!encodedMethod.isFinal()) {
+                  } else if (!method.getAccessFlags().isFinal()) {
                     // Virtual rewrites require emulated dispatch for inheritance.
                     // The call is rewritten to the dispatch holder class instead.
-                    handleEmulateDispatch(appView, encodedMethod);
-                    newHolder = dispatchHolderTypeFor(encodedMethod);
+                    handleEmulateDispatch(appView, method);
+                    newHolder = dispatchHolderTypeFor(method);
                   }
                 }
               }
-              DexProto proto = encodedMethod.method.proto;
-              DexMethod method = appView.dexItemFactory().createMethod(inType, proto, methodName);
-              retargetLibraryMember.put(
-                  method, computeRetargetMethod(method, encodedMethod.isStatic(), newHolder));
+              retargetLibraryMember.put(methodReference, computeRetargetMethod(method, newHolder));
             }
           }
         }
       }
     }
 
-    private DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
-      DexItemFactory factory = appView.dexItemFactory();
-      DexProto newProto = isStatic ? method.proto : factory.prependHolderToProto(method);
-      return factory.createMethod(newHolder, newProto, method.name);
+    private boolean isEmulatedInterfaceDispatch(DexClassAndMethod method) {
+      // Answers true if this method is already managed through emulated interface dispatch.
+      Map<DexType, DexType> emulateLibraryInterface =
+          appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
+      if (emulateLibraryInterface.isEmpty()) {
+        return false;
+      }
+      DexMethod methodToFind = method.getReference();
+
+      // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
+      // the method, answers true.
+      WorkList<DexClass> worklist = WorkList.newIdentityWorkList(method.getHolder());
+      while (worklist.hasNext()) {
+        DexClass clazz = worklist.next();
+        if (clazz.isInterface()
+            && emulateLibraryInterface.containsKey(clazz.getType())
+            && clazz.lookupMethod(methodToFind) != null) {
+          return true;
+        }
+        // All super types are library class, or we are doing L8 compilation.
+        clazz.forEachImmediateSupertype(
+            superType -> {
+              DexClass superClass = appView.definitionFor(superType);
+              if (superClass != null) {
+                worklist.addIfNotSeen(superClass);
+              }
+            });
+      }
+      return false;
     }
 
-    private List<DexEncodedMethod> findDexEncodedMethodsWithName(
-        DexString methodName, DexClass clazz) {
-      List<DexEncodedMethod> found = new ArrayList<>();
-      for (DexEncodedMethod encodedMethod : clazz.methods()) {
-        if (encodedMethod.method.name == methodName) {
-          found.add(encodedMethod);
-        }
-      }
-      assert found.size() > 0 : "Should have found a method (library specifications).";
+    private DexMethod computeRetargetMethod(DexClassAndMethod method, DexType newHolder) {
+      DexItemFactory factory = appView.dexItemFactory();
+      DexProto newProto =
+          method.getAccessFlags().isStatic()
+              ? method.getProto()
+              : factory.prependHolderToProto(method.getReference());
+      return factory.createMethod(newHolder, newProto, method.getName());
+    }
+
+    private List<DexClassAndMethod> findMethodsWithName(DexString methodName, DexClass clazz) {
+      List<DexClassAndMethod> found = new ArrayList<>();
+      clazz.forEachClassMethodMatching(
+          definition -> definition.getName() == methodName, found::add);
+      assert !found.isEmpty() : "Should have found a method (library specifications).";
       return found;
     }
 
-    private void handleEmulateDispatch(AppView<?> appView, DexEncodedMethod method) {
+    private void handleEmulateDispatch(AppView<?> appView, DexClassAndMethod method) {
       emulatedDispatchMethods.add(method);
       if (!appView.options().isDesugaredLibraryCompilation()) {
         // Add rewrite rules so keeps rules are correctly generated in the program.
@@ -437,8 +463,8 @@
     private void addInterfacesAndForwardingMethods(
         ExecutorService executorService, IRConverter converter) throws ExecutionException {
       assert !appView.options().isDesugaredLibraryCompilation();
-      Map<DexType, List<DexEncodedMethod>> map = Maps.newIdentityHashMap();
-      for (DexEncodedMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+      Map<DexType, List<DexClassAndMethod>> map = Maps.newIdentityHashMap();
+      for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
         map.putIfAbsent(emulatedDispatchMethod.getHolderType(), new ArrayList<>(1));
         map.get(emulatedDispatchMethod.getHolderType()).add(emulatedDispatchMethod);
       }
@@ -470,7 +496,7 @@
     }
 
     private boolean inherit(
-        DexLibraryClass clazz, DexType typeToInherit, Set<DexEncodedMethod> retarget) {
+        DexLibraryClass clazz, DexType typeToInherit, DexClassAndMethodSet retarget) {
       DexLibraryClass current = clazz;
       while (current.type != appView.dexItemFactory().objectType) {
         if (current.type == typeToInherit) {
@@ -491,13 +517,13 @@
 
     private void addInterfacesAndForwardingMethods(
         DexProgramClass clazz,
-        List<DexEncodedMethod> methods,
+        List<DexClassAndMethod> methods,
         Consumer<DexEncodedMethod> newForwardingMethodsConsumer) {
       // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
       // methods.
       // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
       // applies up to 24.
-      for (DexEncodedMethod method : methods) {
+      for (DexClassAndMethod method : methods) {
         clazz.addExtraInterfaces(
             Collections.singletonList(new ClassTypeSignature(dispatchInterfaceTypeFor(method))));
         if (clazz.lookupVirtualMethod(method.getReference()) == null) {
@@ -508,7 +534,7 @@
       }
     }
 
-    private DexEncodedMethod createForwardingMethod(DexEncodedMethod target, DexClass clazz) {
+    private DexEncodedMethod createForwardingMethod(DexClassAndMethod target, DexClass clazz) {
       // NOTE: Never add a forwarding method to methods of classes unknown or coming from
       // android.jar
       // even if this results in invalid code, these classes are never desugared.
@@ -533,7 +559,7 @@
                   | Constants.ACC_INTERFACE);
       ClassAccessFlags holderAccessFlags =
           ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
-      for (DexEncodedMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+      for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
         // Dispatch interface.
         DexType interfaceType = dispatchInterfaceTypeFor(emulatedDispatchMethod);
         DexEncodedMethod itfMethod =
@@ -560,7 +586,7 @@
     }
 
     private DexEncodedMethod generateInterfaceDispatchMethod(
-        DexEncodedMethod emulatedDispatchMethod, DexType interfaceType) {
+        DexClassAndMethod emulatedDispatchMethod, DexType interfaceType) {
       MethodAccessFlags flags =
           MethodAccessFlags.fromSharedAccessFlags(
               Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
@@ -582,7 +608,7 @@
     }
 
     private DexEncodedMethod generateHolderDispatchMethod(
-        DexEncodedMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
+        DexClassAndMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
       // The method should look like:
       // static foo(rcvr, arg0, arg1) {
       //    if (rcvr instanceof interfaceType) {
@@ -613,7 +639,7 @@
   }
 
   private void reportInvalidLibrarySupertype(
-      DexLibraryClass libraryClass, Set<DexEncodedMethod> retarget) {
+      DexLibraryClass libraryClass, DexClassAndMethodSet retarget) {
     DexClass dexClass = appView.definitionFor(libraryClass.superType);
     String message;
     if (dexClass == null) {
@@ -634,15 +660,15 @@
             retarget);
   }
 
-  private DexType dispatchInterfaceTypeFor(DexEncodedMethod method) {
+  private DexType dispatchInterfaceTypeFor(DexClassAndMethod method) {
     return dispatchTypeFor(method, "dispatchInterface");
   }
 
-  private DexType dispatchHolderTypeFor(DexEncodedMethod method) {
+  private DexType dispatchHolderTypeFor(DexClassAndMethod method) {
     return dispatchTypeFor(method, "dispatchHolder");
   }
 
-  private DexType dispatchTypeFor(DexEncodedMethod method, String suffix) {
+  private DexType dispatchTypeFor(DexClassAndMethod method, String suffix) {
     String descriptor =
         "L"
             + appView
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 47793a8..1529d2f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -4,6 +4,13 @@
 
 package com.android.tools.r8.ir.desugar;
 
+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 com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Constants;
@@ -36,8 +43,10 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+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.conversion.IRConverter;
@@ -102,7 +111,6 @@
 
   // Public for testing.
   public static final String EMULATE_LIBRARY_CLASS_NAME_SUFFIX = "$-EL";
-  public static final String DISPATCH_CLASS_NAME_SUFFIX = "$-DC";
   public static final String COMPANION_CLASS_NAME_SUFFIX = "$-CC";
   public static final String DEFAULT_METHOD_PREFIX = "$default$";
   public static final String PRIVATE_METHOD_PREFIX = "$private$";
@@ -208,7 +216,16 @@
     return emulatedInterfaces.containsKey(itf);
   }
 
-  public boolean needsRewriting(DexMethod method) {
+  public boolean needsRewriting(DexMethod method, boolean isInvokeSuper, AppView<?> appView) {
+    if (isInvokeSuper) {
+      DexClass clazz = appView.appInfo().definitionFor(method.getHolderType());
+      if (clazz != null
+          && clazz.isLibraryClass()
+          && clazz.isInterface()
+          && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
+        return true;
+      }
+    }
     return emulatedMethods.contains(method.getName());
   }
 
@@ -216,18 +233,19 @@
     return emulatedInterfaces.get(itf);
   }
 
-  private void leavingStaticInvokeToInterface(
-      DexProgramClass holder, DexEncodedMethod encodedMethod) {
+  private void leavingStaticInvokeToInterface(ProgramMethod method) {
     // When leaving static interface method invokes possibly upgrade the class file
     // version, but don't go above the initial class file version. If the input was
     // 1.7 or below, this will make a VerificationError on the input a VerificationError
     // on the output. If the input was 1.8 or above the runtime behaviour (potential ICCE)
     // will remain the same.
-    if (holder.hasClassFileVersion()) {
-      encodedMethod.upgradeClassFileVersion(
-          Ordered.min(CfVersion.V1_8, holder.getInitialClassFileVersion()));
+    if (method.getHolder().hasClassFileVersion()) {
+      method
+          .getDefinition()
+          .upgradeClassFileVersion(
+              Ordered.min(CfVersion.V1_8, method.getHolder().getInitialClassFileVersion()));
     } else {
-      encodedMethod.upgradeClassFileVersion(CfVersion.V1_8);
+      method.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
     }
   }
 
@@ -235,279 +253,300 @@
   // NOTE: can be called for different methods concurrently.
   public void rewriteMethodReferences(IRCode code) {
     ProgramMethod context = code.context();
-    DexEncodedMethod encodedMethod = context.getDefinition();
-    if (synthesizedMethods.contains(encodedMethod)) {
+    if (synthesizedMethods.contains(context)) {
       return;
     }
 
     ListIterator<BasicBlock> blocks = code.listIterator();
-    AppInfo appInfo = appView.appInfo();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
       InstructionListIterator instructions = block.listIterator(code);
       while (instructions.hasNext()) {
         Instruction instruction = instructions.next();
-
-        if (instruction.isInvokeCustom()) {
-          // Check that static interface methods are not referenced
-          // from invoke-custom instructions via method handles.
-          DexCallSite callSite = instruction.asInvokeCustom().getCallSite();
-          reportStaticInterfaceMethodHandle(encodedMethod.method, callSite.bootstrapMethod);
-          for (DexValue arg : callSite.bootstrapArgs) {
-            if (arg.isDexValueMethodHandle()) {
-              reportStaticInterfaceMethodHandle(
-                  encodedMethod.method, arg.asDexValueMethodHandle().value);
-            }
-          }
-          continue;
+        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(), instructions, context);
+            break;
+          case INVOKE_SUPER:
+            rewriteInvokeSuper(instruction.asInvokeSuper(), instructions, context);
+            break;
+          case INVOKE_INTERFACE:
+          case INVOKE_VIRTUAL:
+            rewriteInvokeInterfaceOrInvokeVirtual(
+                instruction.asInvokeMethodWithReceiver(), instructions);
+            break;
+          default:
+            // Intentionally empty.
+            break;
         }
+      }
+    }
+  }
 
-        if (instruction.isInvokeStatic()) {
-          InvokeStatic invokeStatic = instruction.asInvokeStatic();
-          DexMethod method = invokeStatic.getInvokedMethod();
-          if (appView.getSyntheticItems().isPendingSynthetic(method.holder)) {
-            // We did not create this code yet, but it will not require rewriting.
-            continue;
-          }
-          DexClass clazz = appInfo.definitionFor(method.holder);
-          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 (invokeStatic.getInterfaceBit()) {
-              leavingStaticInvokeToInterface(context.getHolder(), encodedMethod);
-            }
-            warnMissingType(encodedMethod.method, method.holder);
-          } else if (clazz.isInterface()) {
-            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.
+  private void rewriteInvokeCustom(InvokeCustom invoke, 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()) {
+        reportStaticInterfaceMethodHandle(context, arg.asDexValueMethodHandle().value);
+      }
+    }
+  }
 
-              if (!options.canLeaveStaticInterfaceMethodInvokes()) {
-                // On pre-L devices static calls to interface methods result in verifier
-                // rejecting the whole class. We have to create special dispatch classes,
-                // 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.
-                ProgramMethod newProgramMethod =
-                    appView
-                        .getSyntheticItems()
-                        .createMethod(
-                            context.getHolder(),
-                            factory,
-                            syntheticMethodBuilder -> {
-                              syntheticMethodBuilder
-                                  .setProto(method.proto)
-                                  .setAccessFlags(
-                                      MethodAccessFlags.fromSharedAccessFlags(
-                                          Constants.ACC_PUBLIC
-                                              | Constants.ACC_STATIC
-                                              | Constants.ACC_SYNTHETIC,
-                                          false))
-                                  .setCode(
-                                      m ->
-                                          ForwardMethodBuilder.builder(factory)
-                                              .setStaticTarget(method, true)
-                                              .setStaticSource(m)
-                                              .build());
-                            });
-                instructions.replaceCurrentInstruction(
-                    new InvokeStatic(
-                        newProgramMethod.getReference(),
-                        invokeStatic.outValue(),
-                        invokeStatic.arguments()));
-                synchronized (synthesizedMethods) {
-                  // 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);
-                }
-              } else {
-                // When leaving static interface method invokes upgrade the class file version.
-                encodedMethod.upgradeClassFileVersion(CfVersion.V1_8);
-              }
-            } else {
+  private void rewriteInvokeDirect(
+      InvokeDirect invoke, InstructionListIterator instructions, ProgramMethod context) {
+    DexMethod method = invoke.getInvokedMethod();
+    if (factory.isConstructor(method)) {
+      return;
+    }
+
+    DexClass clazz = appView.definitionForHolder(method, context);
+    if (clazz == null) {
+      // Report missing class since we don't know if it is an interface.
+      warnMissingType(context, method.holder);
+      return;
+    }
+
+    if (!clazz.isInterface()) {
+      return;
+    }
+
+    if (clazz.isLibraryClass()) {
+      throw new CompilationError(
+          "Unexpected call to a private method "
+              + "defined in library class "
+              + clazz.toSourceString(),
+          getMethodOrigin(context.getReference()));
+    }
+
+    DexEncodedMethod directTarget = clazz.lookupMethod(method);
+    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.
+      instructions.replaceCurrentInstruction(
+          new InvokeStatic(
+              directTarget.isPrivateMethod()
+                  ? privateAsMethodOfCompanionClass(method)
+                  : defaultAsMethodOfCompanionClass(method),
+              invoke.outValue(),
+              invoke.arguments()));
+    } else {
+      // The method can be a default method in the interface hierarchy.
+      DexClassAndMethod virtualTarget =
+          appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, method);
+      if (virtualTarget != null) {
+        // This is a invoke-direct call to a virtual method.
+        instructions.replaceCurrentInstruction(
+            new InvokeStatic(
+                defaultAsMethodOfCompanionClass(virtualTarget.getDefinition().method),
+                invoke.outValue(),
+                invoke.arguments()));
+      } 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;
+      }
+    }
+  }
+
+  private void rewriteInvokeStatic(
+      InvokeStatic invoke, InstructionListIterator instructions, ProgramMethod context) {
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
+      // We did not create this code yet, but it will not require rewriting.
+      return;
+    }
+
+    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()) {
+        leavingStaticInvokeToInterface(context);
+      }
+      warnMissingType(context, invokedMethod.holder);
+      return;
+    }
+
+    if (!clazz.isInterface()) {
+      if (invoke.getInterfaceBit()) {
+        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()) {
+        // On pre-L devices static calls to interface methods result in verifier
+        // rejecting the whole class. We have to create special dispatch classes,
+        // 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.
+        ProgramMethod newProgramMethod =
+            appView
+                .getSyntheticItems()
+                .createMethod(
+                    context.getHolder(),
+                    factory,
+                    syntheticMethodBuilder ->
+                        syntheticMethodBuilder
+                            .setProto(invokedMethod.proto)
+                            .setAccessFlags(
+                                MethodAccessFlags.fromSharedAccessFlags(
+                                    Constants.ACC_PUBLIC
+                                        | Constants.ACC_STATIC
+                                        | Constants.ACC_SYNTHETIC,
+                                    false))
+                            .setCode(
+                                m ->
+                                    ForwardMethodBuilder.builder(factory)
+                                        .setStaticTarget(invokedMethod, true)
+                                        .setStaticSource(m)
+                                        .build()));
+        instructions.replaceCurrentInstruction(
+            new InvokeStatic(
+                newProgramMethod.getReference(), invoke.outValue(), invoke.arguments()));
+        synchronized (synthesizedMethods) {
+          // 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);
+        }
+      } else {
+        // When leaving static interface method invokes upgrade the class file version.
+        context.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
+      }
+    } else {
+      instructions.replaceCurrentInstruction(
+          new InvokeStatic(
+              staticAsMethodOfCompanionClass(invokedMethod),
+              invoke.outValue(),
+              invoke.arguments()));
+    }
+  }
+
+  private void rewriteInvokeSuper(
+      InvokeSuper invoke, InstructionListIterator instructions, ProgramMethod context) {
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    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;
+    }
+
+    if (clazz.isInterface() && !clazz.isLibraryClass()) {
+      // NOTE: we intentionally don't desugar super calls into 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.
+      DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
+      instructions.replaceCurrentInstruction(
+          new InvokeStatic(
+              defaultAsMethodOfCompanionClass(amendedMethod),
+              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()) {
               instructions.replaceCurrentInstruction(
-                  new InvokeStatic(staticAsMethodOfCompanionClass(method),
-                      invokeStatic.outValue(), invokeStatic.arguments()));
-            }
-          } else {
-            assert !clazz.isInterface();
-            if (invokeStatic.getInterfaceBit()) {
-              leavingStaticInvokeToInterface(context.getHolder(), encodedMethod);
+                  new InvokeStatic(
+                      defaultAsMethodOfCompanionClass(target.getReference(), factory),
+                      invoke.outValue(),
+                      invoke.arguments()));
             }
           }
-          continue;
         }
-
-        if (instruction.isInvokeSuper()) {
-          InvokeSuper invokeSuper = instruction.asInvokeSuper();
-          DexMethod invokedMethod = invokeSuper.getInvokedMethod();
-          DexClass clazz = appInfo.definitionFor(invokedMethod.holder);
-          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(encodedMethod.method, invokedMethod.holder);
-          } else if (clazz.isInterface() && !clazz.isLibraryClass()) {
-            // NOTE: we intentionally don't desugar super calls into 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.
-            DexMethod amendedMethod =
-                amendDefaultMethod(
-                    appInfo.definitionFor(encodedMethod.getHolderType()), invokedMethod);
+      } 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 =
+                instanceAsMethodOfCompanionClass(
+                    superTarget.getReference(), DEFAULT_METHOD_PREFIX, factory);
+            DexMethod companionMethod =
+                factory.createMethod(
+                    getCompanionClassType(emulatedItf),
+                    factory.protoWithDifferentFirstParameter(
+                        originalCompanionMethod.proto, emulatedItf),
+                    originalCompanionMethod.name);
             instructions.replaceCurrentInstruction(
-                new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
-                    invokeSuper.outValue(), invokeSuper.arguments()));
+                new InvokeStatic(companionMethod, invoke.outValue(), invoke.arguments()));
           } else {
-            DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
-            if (dexType == null) {
-              if (clazz.isInterface()
-                  && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
-                DexClassAndMethod target =
-                    appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, code.context());
-                if (target != null && target.getDefinition().isDefaultMethod()) {
-                  DexClass holder = target.getHolder();
-                  if (holder.isLibraryClass() && holder.isInterface()) {
-                    instructions.replaceCurrentInstruction(
-                        new InvokeStatic(
-                            defaultAsMethodOfCompanionClass(target.getReference(), factory),
-                            invokeSuper.outValue(),
-                            invokeSuper.arguments()));
-                  }
-                }
-              }
-            } 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(invokeSuper.getInvokedMethod(), code.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 =
-                      instanceAsMethodOfCompanionClass(
-                          superTarget.getReference(), DEFAULT_METHOD_PREFIX, factory);
-                  DexMethod companionMethod =
-                      factory.createMethod(
-                          getCompanionClassType(dexType),
-                          factory.protoWithDifferentFirstParameter(
-                              originalCompanionMethod.proto, dexType),
-                          originalCompanionMethod.name);
-                  instructions.replaceCurrentInstruction(
-                      new InvokeStatic(
-                          companionMethod, invokeSuper.outValue(), invokeSuper.arguments()));
-                } else {
-                  instructions.replaceCurrentInstruction(
-                      new InvokeStatic(
-                          retargetMethod, invokeSuper.outValue(), invokeSuper.arguments()));
-                }
-              }
-            }
-          }
-          continue;
-        }
-
-        if (instruction.isInvokeDirect()) {
-          InvokeDirect invokeDirect = instruction.asInvokeDirect();
-          DexMethod method = invokeDirect.getInvokedMethod();
-          if (factory.isConstructor(method)) {
-            continue;
-          }
-
-          DexClass clazz = appInfo.definitionForHolder(method);
-          if (clazz == null) {
-            // Report missing class since we don't know if it is an interface.
-            warnMissingType(encodedMethod.method, method.holder);
-          } else if (clazz.isInterface()) {
-            if (clazz.isLibraryClass()) {
-              throw new CompilationError("Unexpected call to a private method " +
-                  "defined in library class " + clazz.toSourceString(),
-                  getMethodOrigin(encodedMethod.method));
-            }
-            DexEncodedMethod directTarget = clazz.lookupMethod(method);
-            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.
-              if (directTarget.isPrivateMethod()) {
-                instructions.replaceCurrentInstruction(
-                    new InvokeStatic(
-                        privateAsMethodOfCompanionClass(method),
-                        invokeDirect.outValue(),
-                        invokeDirect.arguments()));
-              } else {
-                instructions.replaceCurrentInstruction(
-                    new InvokeStatic(
-                        defaultAsMethodOfCompanionClass(method),
-                        invokeDirect.outValue(),
-                        invokeDirect.arguments()));
-              }
-            } else {
-              // The method can be a default method in the interface hierarchy.
-              DexClassAndMethod virtualTarget =
-                  appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, method);
-              if (virtualTarget != null) {
-                // This is a invoke-direct call to a virtual method.
-                instructions.replaceCurrentInstruction(
-                    new InvokeStatic(
-                        defaultAsMethodOfCompanionClass(virtualTarget.getDefinition().method),
-                        invokeDirect.outValue(),
-                        invokeDirect.arguments()));
-              } 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;
-              }
-            }
-          }
-        }
-
-        if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
-          InvokeMethod invokeMethod = instruction.asInvokeMethod();
-          DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-          DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
-          if (dexType != 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(invokedMethod, invokeMethod.getInterfaceBit())
-                    .asSingleResolution();
-            if (resolution != null
-                && (resolution.getResolvedHolder().isLibraryClass()
-                    || appView.options().isDesugaredLibraryCompilation())) {
-              rewriteCurrentInstructionToEmulatedInterfaceCall(
-                  dexType, invokedMethod, invokeMethod, instructions);
-            }
+            instructions.replaceCurrentInstruction(
+                new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
           }
         }
       }
     }
   }
 
+  private void rewriteInvokeInterfaceOrInvokeVirtual(
+      InvokeMethodWithReceiver invoke, InstructionListIterator instructions) {
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
+    if (emulatedItf == null) {
+      return;
+    }
+
+    // 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(invokedMethod, invoke.getInterfaceBit())
+            .asSingleResolution();
+    if (resolution != null
+        && (resolution.getResolvedHolder().isLibraryClass()
+            || appView.options().isDesugaredLibraryCompilation())) {
+      rewriteCurrentInstructionToEmulatedInterfaceCall(
+          emulatedItf, invokedMethod, invoke, instructions);
+    }
+  }
+
   private DexType maximallySpecificEmulatedInterfaceOrNull(DexMethod invokedMethod) {
     // Here we try to avoid doing the expensive look-up on all invokes.
     if (!emulatedMethods.contains(invokedMethod.name)) {
@@ -547,12 +586,13 @@
       DexMethod invokedMethod,
       InvokeMethod invokeMethod,
       InstructionListIterator instructions) {
-    DexEncodedMethod defaultMethod = appView.definitionFor(emulatedItf).lookupMethod(invokedMethod);
-    if (defaultMethod != null && !dontRewrite(defaultMethod.method)) {
-      assert !defaultMethod.isAbstract();
+    DexClassAndMethod defaultMethod =
+        appView.definitionFor(emulatedItf).lookupClassMethod(invokedMethod);
+    if (defaultMethod != null && !dontRewrite(defaultMethod)) {
+      assert !defaultMethod.getAccessFlags().isAbstract();
       instructions.replaceCurrentInstruction(
           new InvokeStatic(
-              emulateInterfaceLibraryMethod(invokedMethod, emulatedItf, factory),
+              emulateInterfaceLibraryMethod(defaultMethod),
               invokeMethod.outValue(),
               invokeMethod.arguments()));
     }
@@ -570,10 +610,11 @@
     return appView.rewritePrefix.hasRewrittenType(clazz.type, appView);
   }
 
-  boolean dontRewrite(DexMethod method) {
+  boolean dontRewrite(DexClassAndMethod method) {
     for (Pair<DexType, DexString> dontRewrite :
         options.desugaredLibraryConfiguration.getDontRewriteInvocation()) {
-      if (method.holder == dontRewrite.getFirst() && method.name == dontRewrite.getSecond()) {
+      if (method.getHolderType() == dontRewrite.getFirst()
+          && method.getName() == dontRewrite.getSecond()) {
         return true;
       }
     }
@@ -666,87 +707,82 @@
     return false;
   }
 
-  private static DexMethod emulateInterfaceLibraryMethod(
-      DexMethod method, DexType holder, DexItemFactory factory) {
+  private DexMethod emulateInterfaceLibraryMethod(DexClassAndMethod method) {
     return factory.createMethod(
-        getEmulateLibraryInterfaceClassType(holder, factory),
-        factory.prependTypeToProto(holder, method.proto),
-        factory.createString(method.name.toString()));
+        getEmulateLibraryInterfaceClassType(method.getHolderType(), factory),
+        factory.prependTypeToProto(method.getHolderType(), method.getProto()),
+        method.getName());
   }
 
   private DexProgramClass synthesizeEmulateInterfaceLibraryClass(
       DexProgramClass theInterface, Map<DexType, List<DexType>> emulatedInterfacesHierarchy) {
     List<DexEncodedMethod> emulationMethods = new ArrayList<>();
-    for (DexEncodedMethod method : theInterface.methods()) {
-      if (method.isDefaultMethod()) {
-        DexMethod libraryMethod =
-            factory.createMethod(
-                emulatedInterfaces.get(theInterface.type), method.method.proto, method.method.name);
-        DexMethod originalCompanionMethod =
-            method.isStatic()
-                ? staticAsMethodOfCompanionClass(method.method)
-                : instanceAsMethodOfCompanionClass(method.method, DEFAULT_METHOD_PREFIX, factory);
-        DexMethod companionMethod =
-            factory.createMethod(
-                getCompanionClassType(theInterface.type),
-                originalCompanionMethod.proto,
-                originalCompanionMethod.name);
+    theInterface.forEachProgramMethodMatching(
+        DexEncodedMethod::isDefaultMethod,
+        method -> {
+          DexMethod libraryMethod =
+              method.getReference().withHolder(emulatedInterfaces.get(theInterface.type), factory);
+          DexMethod companionMethod =
+              method.getAccessFlags().isStatic()
+                  ? staticAsMethodOfCompanionClass(method)
+                  : defaultAsMethodOfCompanionClass(method);
 
-        // To properly emulate the library interface call, we need to compute the interfaces
-        // inheriting from the interface and manually implement the dispatch with instance of.
-        // The list guarantees that an interface is always after interfaces it extends,
-        // hence reverse iteration.
-        List<DexType> subInterfaces = emulatedInterfacesHierarchy.get(theInterface.type);
-        List<Pair<DexType, DexMethod>> extraDispatchCases = new ArrayList<>();
-        // In practice, there is usually a single case (except for tests),
-        // so we do not bother to make the following loop more clever.
-        Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-            options.desugaredLibraryConfiguration.getRetargetCoreLibMember();
-        for (DexString methodName : retargetCoreLibMember.keySet()) {
-          if (method.method.name == methodName) {
-            for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
-              DexClass inClass = appView.definitionFor(inType);
-              if (inClass != null && implementsInterface(inClass, theInterface.type)) {
-                extraDispatchCases.add(
-                    new Pair<>(
-                        inType,
-                        factory.createMethod(
-                            retargetCoreLibMember.get(methodName).get(inType),
-                            factory.protoWithDifferentFirstParameter(
-                                originalCompanionMethod.proto, inType),
-                            method.method.name)));
+          // To properly emulate the library interface call, we need to compute the interfaces
+          // inheriting from the interface and manually implement the dispatch with instance of.
+          // The list guarantees that an interface is always after interfaces it extends,
+          // hence reverse iteration.
+          List<DexType> subInterfaces = emulatedInterfacesHierarchy.get(theInterface.type);
+          List<Pair<DexType, DexMethod>> extraDispatchCases = new ArrayList<>();
+          // In practice, there is usually a single case (except for tests),
+          // so we do not bother to make the following loop more clever.
+          Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+              options.desugaredLibraryConfiguration.getRetargetCoreLibMember();
+          for (DexString methodName : retargetCoreLibMember.keySet()) {
+            if (method.getName() == methodName) {
+              for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
+                DexClass inClass = appView.definitionFor(inType);
+                if (inClass != null && implementsInterface(inClass, theInterface.type)) {
+                  extraDispatchCases.add(
+                      new Pair<>(
+                          inType,
+                          factory.createMethod(
+                              retargetCoreLibMember.get(methodName).get(inType),
+                              factory.protoWithDifferentFirstParameter(
+                                  companionMethod.proto, inType),
+                              method.getName())));
+                }
               }
             }
           }
-        }
-        if (subInterfaces != null) {
-          for (int i = subInterfaces.size() - 1; i >= 0; i--) {
-            DexClass subInterfaceClass = appView.definitionFor(subInterfaces.get(i));
-            assert subInterfaceClass != null; // Else computation of subInterface would have failed.
-            // if the method is implemented, extra dispatch is required.
-            DexEncodedMethod result = subInterfaceClass.lookupVirtualMethod(method.method);
-            if (result != null && !result.isAbstract()) {
-              extraDispatchCases.add(
-                  new Pair<>(
-                      subInterfaceClass.type,
-                      factory.createMethod(
-                          getCompanionClassType(subInterfaceClass.type),
-                          factory.protoWithDifferentFirstParameter(
-                              originalCompanionMethod.proto, subInterfaceClass.type),
-                          originalCompanionMethod.name)));
+          if (subInterfaces != null) {
+            for (int i = subInterfaces.size() - 1; i >= 0; i--) {
+              DexClass subInterfaceClass = appView.definitionFor(subInterfaces.get(i));
+              assert subInterfaceClass
+                  != null; // Else computation of subInterface would have failed.
+              // if the method is implemented, extra dispatch is required.
+              DexEncodedMethod result =
+                  subInterfaceClass.lookupVirtualMethod(method.getReference());
+              if (result != null && !result.isAbstract()) {
+                extraDispatchCases.add(
+                    new Pair<>(
+                        subInterfaceClass.type,
+                        factory.createMethod(
+                            getCompanionClassType(subInterfaceClass.type),
+                            factory.protoWithDifferentFirstParameter(
+                                companionMethod.proto, subInterfaceClass.type),
+                            companionMethod.name)));
+              }
             }
           }
-        }
-        emulationMethods.add(
-            DexEncodedMethod.toEmulateDispatchLibraryMethod(
-                method.getHolderType(),
-                emulateInterfaceLibraryMethod(method.method, method.getHolderType(), factory),
-                companionMethod,
-                libraryMethod,
-                extraDispatchCases,
-                appView));
-      }
-    }
+          emulationMethods.add(
+              DexEncodedMethod.toEmulateDispatchLibraryMethod(
+                  method.getHolderType(),
+                  emulateInterfaceLibraryMethod(method),
+                  companionMethod,
+                  libraryMethod,
+                  extraDispatchCases,
+                  appView));
+        });
     if (emulationMethods.isEmpty()) {
       return null;
     }
@@ -799,17 +835,17 @@
     return factory.createSynthesizedType(elTypeDescriptor);
   }
 
-  private void reportStaticInterfaceMethodHandle(DexMethod referencedFrom, DexMethodHandle handle) {
+  private void reportStaticInterfaceMethodHandle(ProgramMethod context, DexMethodHandle handle) {
     if (handle.type.isInvokeStatic()) {
       DexClass holderClass = appView.definitionFor(handle.asMethod().holder);
       // NOTE: If the class definition is missing we can't check. Let it be handled as any other
       // missing call target.
       if (holderClass == null) {
-        warnMissingType(referencedFrom, handle.asMethod().holder);
+        warnMissingType(context, handle.asMethod().holder);
       } else if (holderClass.isInterface()) {
         throw new Unimplemented(
             "Desugaring of static interface method handle in `"
-                + referencedFrom.toSourceString()
+                + context.toSourceString()
                 + "` is not yet supported.");
       }
     }
@@ -831,15 +867,6 @@
     return getCompanionClassType(type, factory);
   }
 
-  // Gets the forwarding class for the interface `type`.
-  final DexType getDispatchClassType(DexType type) {
-    assert type.isClassType();
-    String descriptor = type.descriptor.toString();
-    String dcTypeDescriptor = descriptor.substring(0, descriptor.length() - 1)
-        + DISPATCH_CLASS_NAME_SUFFIX + ";";
-    return factory.createType(dcTypeDescriptor);
-  }
-
   // Checks if `type` is a companion class.
   public static boolean isCompanionClassType(DexType type) {
     return type.descriptor.toString().endsWith(COMPANION_CLASS_NAME_SUFFIX + ";");
@@ -864,21 +891,15 @@
   }
 
   // Represent a static interface method as a method of companion class.
+  final DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) {
+    return staticAsMethodOfCompanionClass(method.getReference());
+  }
+
   final DexMethod staticAsMethodOfCompanionClass(DexMethod method) {
     // No changes for static methods.
     return factory.createMethod(getCompanionClassType(method.holder), method.proto, method.name);
   }
 
-  // Represent a static interface method as a method of dispatch class.
-  final DexMethod staticAsMethodOfDispatchClass(DexMethod method) {
-    return factory.createMethod(getDispatchClassType(method.holder), method.proto, method.name);
-  }
-
-  // Checks if the type ends with dispatch class suffix.
-  public static boolean hasDispatchClassSuffix(DexType clazz) {
-    return clazz.getName().endsWith(DISPATCH_CLASS_NAME_SUFFIX);
-  }
-
   private static DexMethod instanceAsMethodOfCompanionClass(
       DexMethod method, String prefix, DexItemFactory factory) {
     // Add an implicit argument to represent the receiver.
@@ -913,6 +934,10 @@
     return defaultAsMethodOfCompanionClass(method, factory);
   }
 
+  DexMethod defaultAsMethodOfCompanionClass(DexClassAndMethod method) {
+    return defaultAsMethodOfCompanionClass(method.getReference(), factory);
+  }
+
   // Represent a private instance interface method as a method of companion class.
   static DexMethod privateAsMethodOfCompanionClass(DexMethod method, DexItemFactory factory) {
     // Add an implicit argument to represent the receiver.
@@ -1135,37 +1160,6 @@
     return true;
   }
 
-  public static boolean isEmulatedInterfaceDispatch(AppView<?> appView, DexEncodedMethod method) {
-    // Answers true if this method is already managed through emulated interface dispatch.
-    Map<DexType, DexType> emulateLibraryInterface =
-        appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
-    if (emulateLibraryInterface.isEmpty()) {
-      return false;
-    }
-    DexMethod methodToFind = method.method;
-
-    // Look-up all superclass and interfaces, if an emulated interface is found, and it implements
-    // the method, answers true.
-    LinkedList<DexType> workList = new LinkedList<>();
-    workList.add(methodToFind.holder);
-    while (!workList.isEmpty()) {
-      DexType dexType = workList.removeFirst();
-      DexClass dexClass = appView.definitionFor(dexType);
-      assert dexClass != null; // It is a library class, or we are doing L8 compilation.
-      if (dexClass.isInterface() && emulateLibraryInterface.containsKey(dexType)) {
-        DexEncodedMethod dexEncodedMethod = dexClass.lookupMethod(methodToFind);
-        if (dexEncodedMethod != null) {
-          return true;
-        }
-      }
-      Collections.addAll(workList, dexClass.interfaces.values);
-      if (dexClass.superType != appView.dexItemFactory().objectType) {
-        workList.add(dexClass.superType);
-      }
-    }
-    return false;
-  }
-
   private boolean shouldIgnoreFromReports(DexType missing) {
     return appView.rewritePrefix.hasRewrittenType(missing, appView)
         || missing.isD8R8SynthesizedClassType()
@@ -1187,13 +1181,13 @@
     options.warningMissingInterfaceForDesugar(classToDesugar, implementing, missing);
   }
 
-  private void warnMissingType(DexMethod referencedFrom, DexType missing) {
+  private void warnMissingType(ProgramMethod context, DexType missing) {
     // Companion/Emulated interface/Conversion classes for desugared library won't be missing,
     // they are in the desugared library.
     if (shouldIgnoreFromReports(missing)) {
       return;
     }
-    DexMethod method = appView.graphLens().getOriginalMethodSignature(referencedFrom);
+    DexMethod method = appView.graphLens().getOriginalMethodSignature(context.getReference());
     Origin origin = getMethodOrigin(method);
     MethodPosition position = new MethodPosition(method.asMethodReference());
     options.warningMissingTypeForDesugar(origin, position, missing, method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 42787b7..c2db31a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InvokeSuper;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
@@ -27,7 +26,6 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
@@ -45,7 +43,6 @@
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
@@ -362,85 +359,6 @@
     return c -> 7 * checksum;
   }
 
-  DexProgramClass process(DexLibraryClass iface, Set<DexProgramClass> callers) {
-    assert iface.isInterface();
-
-    // The list of methods to be created in dispatch class.
-    // NOTE: we do NOT check static methods being actually called on the interface against
-    //       static methods actually existing in that interface. It is essential for supporting
-    //       D8 desugaring when each class may be dexed separately since it allows us to assume
-    //       that all synthesized dispatch classes have the same set of methods so we don't
-    //       need to merge them.
-    List<DexEncodedMethod> dispatchMethods = new ArrayList<>();
-
-    // Process public static methods, for each of them create a method dispatching the call to it.
-    for (DexEncodedMethod direct : iface.directMethods()) {
-      MethodAccessFlags originalAccessFlags = direct.accessFlags;
-      if (!originalAccessFlags.isStatic() || !originalAccessFlags.isPublic()) {
-        // We assume only public static methods of library interfaces can be called
-        // from program classes, since there should not be protected or package private
-        // static methods on interfaces.
-        assert !originalAccessFlags.isStatic() || originalAccessFlags.isPrivate();
-        continue;
-      }
-
-      assert !rewriter.factory.isClassConstructor(direct.method);
-
-      DexMethod origMethod = direct.method;
-      DexMethod newMethod = rewriter.staticAsMethodOfDispatchClass(origMethod);
-      // Create a forwarding method to the library static interface method. The method is added
-      // to the dispatch class, however, the targeted method is still on the interface, so the
-      // interface bit should be set to true.
-      ForwardMethodBuilder forwardMethodBuilder =
-          ForwardMethodBuilder.builder(appView.dexItemFactory())
-              .setStaticSource(newMethod)
-              .setStaticTarget(origMethod, true);
-      DexEncodedMethod newEncodedMethod =
-          new DexEncodedMethod(
-              newMethod,
-              MethodAccessFlags.fromSharedAccessFlags(
-                  Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false),
-              MethodTypeSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              ParameterAnnotationsList.empty(),
-              forwardMethodBuilder.build(),
-              true);
-      newEncodedMethod.getMutableOptimizationInfo().markNeverInline();
-      dispatchMethods.add(newEncodedMethod);
-    }
-
-    ClassAccessFlags dispatchClassFlags =
-        ClassAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
-
-    // Create dispatch class.
-    DexType dispatchClassType = rewriter.getDispatchClassType(iface.type);
-    DexProgramClass dispatchClass =
-        new DexProgramClass(
-            dispatchClassType,
-            null,
-            new SynthesizedOrigin("interface dispatch", getClass()),
-            dispatchClassFlags,
-            rewriter.factory.objectType,
-            DexTypeList.empty(),
-            iface.sourceFile,
-            null,
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            ClassSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY,
-            DexEncodedField.EMPTY_ARRAY,
-            dispatchMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
-            DexEncodedMethod.EMPTY_ARRAY,
-            rewriter.factory.getSkipNameValidationForTesting(),
-            DexProgramClass::checksumFromType,
-            callers);
-    syntheticClasses.put(iface, dispatchClass);
-    return dispatchClass;
-  }
-
   private boolean canMoveToCompanionClass(DexEncodedMethod method) {
     Code code = method.getCode();
     assert code != null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index d41fd32..f7be74d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -132,10 +132,10 @@
 
     // The only encoded method.
     CfCode code =
-        BackportedMethods.CloseResourceMethod_closeResourceImpl(
-            options, twrCloseResourceMethod);
-    MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(
-        Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
+        BackportedMethods.CloseResourceMethod_closeResourceImpl(options, twrCloseResourceMethod);
+    MethodAccessFlags flags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
     DexEncodedMethod method =
         new DexEncodedMethod(
             twrCloseResourceMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 8f510fb..1cce76e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -985,6 +985,16 @@
             continue;
           }
 
+          if (invoke.isInvokeMethodWithReceiver()) {
+            if (iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context)) {
+              continue;
+            }
+          } else if (invoke.isInvokeStatic()
+              && iterator.replaceCurrentInstructionByInitClassIfPossible(
+                  appView, code, resolutionResult.getResolvedHolder().getType())) {
+            continue;
+          }
+
           // TODO(b/156853206): Should not duplicate resolution.
           ProgramMethod singleTarget = oracle.lookupSingleTarget(invoke, context);
           if (singleTarget == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 39ce729..2950d4c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.google.common.base.Predicates.alwaysTrue;
 
@@ -14,7 +13,6 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -26,13 +24,10 @@
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRMetadata;
-import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
@@ -44,7 +39,6 @@
 import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
 import java.util.Set;
@@ -216,8 +210,8 @@
       }
       if (current.isStaticGet()) {
         StaticGet staticGet = current.asStaticGet();
-        replaceInstructionByInitClassIfPossible(
-            staticGet, staticGet.getField().holder, code, iterator, code.context());
+        iterator.replaceCurrentInstructionByInitClassIfPossible(
+            appView, code, staticGet.getField().holder);
       }
       replacement.setPosition(position);
       if (block.hasCatchHandlers()) {
@@ -236,16 +230,12 @@
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
       InvokeMethod invoke) {
+    if (invoke.hasUnusedOutValue()) {
+      return;
+    }
+
     DexMethod invokedMethod = invoke.getInvokedMethod();
-    if (invokedMethod.proto.returnType.isVoidType()) {
-      return;
-    }
-
-    if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
-      return;
-    }
-
-    DexType invokedHolder = invokedMethod.holder;
+    DexType invokedHolder = invokedMethod.getHolderType();
     if (!invokedHolder.isClassType()) {
       return;
     }
@@ -302,10 +292,10 @@
         invoke.setOutValue(null);
 
         if (invoke.isInvokeMethodWithReceiver()) {
-          replaceInstructionByNullCheckIfPossible(invoke, iterator, context);
+          iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
         } else if (invoke.isInvokeStatic()) {
-          replaceInstructionByInitClassIfPossible(
-              invoke, singleTarget.getHolderType(), code, iterator, context);
+          iterator.replaceCurrentInstructionByInitClassIfPossible(
+              appView, code, singleTarget.getHolderType());
         }
 
         // Insert the definition of the replacement.
@@ -404,11 +394,11 @@
         // To preserve side effects, original field-get is replaced by an explicit null-check, if
         // the field-get instruction may only fail with an NPE, or the field-get remains as-is.
         if (current.isInstanceGet()) {
-          replaceInstructionByNullCheckIfPossible(current, iterator, context);
+          iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
         } else {
           assert current.isStaticGet();
-          replaceInstructionByInitClassIfPossible(
-              current, target.getHolderType(), code, iterator, context);
+          iterator.replaceCurrentInstructionByInitClassIfPossible(
+              appView, code, target.getHolderType());
         }
 
         // Insert the definition of the replacement.
@@ -426,58 +416,6 @@
     }
   }
 
-  private void replaceInstructionByNullCheckIfPossible(
-      Instruction instruction, InstructionListIterator iterator, ProgramMethod context) {
-    assert instruction.isInstanceFieldInstruction() || instruction.isInvokeMethodWithReceiver();
-    assert !instruction.hasOutValue() || !instruction.outValue().hasAnyUsers();
-    if (instruction.instructionMayHaveSideEffects(
-        appView, context, Instruction.SideEffectAssumption.RECEIVER_NOT_NULL)) {
-      return;
-    }
-    Value receiver =
-        instruction.isInstanceFieldInstruction()
-            ? instruction.asInstanceFieldInstruction().object()
-            : instruction.asInvokeMethodWithReceiver().getReceiver();
-    if (receiver.isNeverNull()) {
-      iterator.removeOrReplaceByDebugLocalRead();
-      return;
-    }
-    InvokeMethod replacement;
-    if (appView.options().canUseRequireNonNull()) {
-      DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
-      replacement = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver));
-    } else {
-      DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
-      replacement = new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver));
-    }
-    iterator.replaceCurrentInstruction(replacement);
-  }
-
-  private void replaceInstructionByInitClassIfPossible(
-      Instruction instruction,
-      DexType holder,
-      IRCode code,
-      InstructionListIterator iterator,
-      ProgramMethod context) {
-    assert instruction.isStaticFieldInstruction() || instruction.isInvokeStatic();
-    if (instruction.instructionMayHaveSideEffects(
-        appView, context, Instruction.SideEffectAssumption.CLASS_ALREADY_INITIALIZED)) {
-      return;
-    }
-    if (!holder.classInitializationMayHaveSideEffectsInContext(appView, context)) {
-      iterator.removeOrReplaceByDebugLocalRead();
-      return;
-    }
-    if (!appView.canUseInitClass()) {
-      return;
-    }
-    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder));
-    if (clazz != null) {
-      Value dest = code.createValue(TypeElement.getInt());
-      iterator.replaceCurrentInstruction(new InitClass(dest, clazz.type));
-    }
-  }
-
   private void replaceInstancePutByNullCheckIfNeverRead(
       IRCode code, InstructionListIterator iterator, InstancePut current) {
     DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField();
@@ -492,7 +430,7 @@
       return;
     }
 
-    replaceInstructionByNullCheckIfPossible(current, iterator, code.context());
+    iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
   }
 
   private void replaceStaticPutByInitClassIfNeverRead(
@@ -509,8 +447,7 @@
       return;
     }
 
-    replaceInstructionByInitClassIfPossible(
-        current, field.getHolderType(), code, iterator, code.context());
+    iterator.replaceCurrentInstructionByInitClassIfPossible(appView, code, field.getHolderType());
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 508e0f4..f673395 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
+import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.Binop;
@@ -782,7 +783,7 @@
     // Process instruction. Returns true if an outline candidate was found.
     private void processInstruction(Instruction instruction) {
       // Figure out whether to include the instruction.
-      boolean include = false;
+      boolean include;
       int instructionIncrement = 1;
       if (instruction.isConstInstruction()) {
         if (index == start) {
@@ -811,13 +812,13 @@
         includeInstruction(instruction);
         // Check if this instruction ends the outline.
         if (actualInstructions >= appView.options().outline.maxSize) {
-          candidate(start, index + 1, actualInstructions);
+          candidate(start, index + 1);
         } else {
           index++;
         }
       } else if (index > start) {
         // Do not add this instruction, candidate ends with previous instruction.
-        candidate(start, index, actualInstructions);
+        candidate(start, index);
       } else {
         // Restart search from next instruction.
         reset(index + 1);
@@ -830,7 +831,7 @@
       int returnValueUsersLeftIfIncluded = returnValueUsersLeft;
       if (returnValue != null) {
         for (Value value : instruction.inValues()) {
-          if (value == returnValue) {
+          if (value.getAliasedValue() == returnValue) {
             returnValueUsersLeftIfIncluded--;
           }
         }
@@ -870,10 +871,9 @@
       }
       // Find the number of in-going arguments, if adding this instruction.
       int newArgumentRegisters = argumentRegisters;
-      if (instruction.inValues().size() > 0) {
-        List<Value> inValues = orderedInValues(instruction, returnValue);
-        for (int i = 0; i < inValues.size(); i++) {
-          Value value = inValues.get(i);
+      if (invoke.hasArguments()) {
+        for (int i = 0; i < invoke.arguments().size(); i++) {
+          Value value = invoke.getArgument(i).getAliasedValue();
           if (value == returnValue) {
             continue;
           }
@@ -884,7 +884,8 @@
             newArgumentRegisters += value.requiredRegisters();
           } else {
             // For virtual calls only re-use the receiver argument.
-            if (i > 0 || !arguments.contains(value)) {
+            Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver().getAliasedValue();
+            if (value != receiver || !arguments.contains(value)) {
               newArgumentRegisters += value.requiredRegisters();
             }
           }
@@ -907,7 +908,11 @@
           offset++;
           previous = instructions.get(index - offset);
         } while (previous.isConstInstruction());
-        if (!previous.isNewInstance() || previous.outValue() != returnValue) {
+        if (!previous.isNewInstance()) {
+          return false;
+        }
+        if (returnValue == null || returnValue != previous.outValue()) {
+          assert false;
           return false;
         }
         // Clear pending new instance flag as the last thing, now the matching constructor is known
@@ -996,6 +1001,10 @@
     // Add the current instruction to the outline.
     private void includeInstruction(Instruction instruction) {
       if (instruction.isAssume()) {
+        Assume assume = instruction.asAssume();
+        if (returnValue != null && assume.src().getAliasedValue() == returnValue) {
+          adjustReturnValueUsersLeft(assume.outValue().numberOfAllUsers() - 1);
+        }
         return;
       }
 
@@ -1005,12 +1014,7 @@
       if (returnValue != null) {
         for (Value value : inValues) {
           if (value.getAliasedValue() == returnValue) {
-            assert returnValueUsersLeft > 0;
-            returnValueUsersLeft--;
-          }
-          if (returnValueUsersLeft == 0) {
-            returnValue = null;
-            returnType = appView.dexItemFactory().voidType;
+            adjustReturnValueUsersLeft(-1);
           }
         }
       }
@@ -1080,9 +1084,18 @@
       }
     }
 
+    private void adjustReturnValueUsersLeft(int change) {
+      returnValueUsersLeft += change;
+      assert returnValueUsersLeft >= 0;
+      if (returnValueUsersLeft == 0) {
+        returnValue = null;
+        returnType = appView.dexItemFactory().voidType;
+      }
+    }
+
     protected abstract void handle(int start, int end, Outline outline);
 
-    private void candidate(int start, int index, int actualInstructions) {
+    private void candidate(int start, int index) {
       List<Instruction> instructions = getInstructionArray();
       assert !instructions.get(start).isConstInstruction();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 143d776..0ce08a5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -584,10 +584,13 @@
       currentUsers = indirectOutValueUsers;
     }
 
-    assert !methodCallsOnInstance.isEmpty();
-
-    inliner.performForcedInlining(
-        method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty());
+    if (!methodCallsOnInstance.isEmpty()) {
+      inliner.performForcedInlining(
+          method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty());
+    } else {
+      assert indirectMethodCallsOnInstance.stream()
+          .noneMatch(method -> method.getDefinition().getOptimizationInfo().mayHaveSideEffects());
+    }
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index b1890af..ae91ac8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -38,7 +38,10 @@
         ImmutableMap.<DexMethod, BiPredicate<DexMethod, List<Value>>>builder()
             .put(
                 dexItemFactory.stringMembers.constructor,
-                (method, arguments) -> arguments.get(1).isNeverNull());
+                (method, arguments) -> arguments.get(1).isNeverNull())
+            .put(
+                dexItemFactory.stringMembers.valueOf,
+                (method, arguments) -> arguments.get(0).isNeverNull());
     putAll(
         builder,
         dexItemFactory.stringBufferMethods.constructorMethods,
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 25688ce..ef724ec 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.CodeSizeOverflowDiagnostic;
 import com.android.tools.r8.errors.ConstantPoolOverflowDiagnostic;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -126,35 +125,29 @@
     LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView);
     for (DexProgramClass clazz : application.classes()) {
       assert SyntheticItems.verifyNotInternalSynthetic(clazz.getType());
-      if (clazz.getSynthesizedFrom().isEmpty()
-          || options.isDesugaredLibraryCompilation()
-          || options.cfToCfDesugar) {
-        try {
-          writeClass(clazz, consumer, rewriter, markerString);
-        } catch (ClassTooLargeException e) {
-          throw appView
-              .options()
-              .reporter
-              .fatalError(
-                  new ConstantPoolOverflowDiagnostic(
-                      clazz.getOrigin(),
-                      Reference.classFromBinaryName(e.getClassName()),
-                      e.getConstantPoolCount()));
-        } catch (MethodTooLargeException e) {
-          throw appView
-              .options()
-              .reporter
-              .fatalError(
-                  new CodeSizeOverflowDiagnostic(
-                      clazz.getOrigin(),
-                      Reference.methodFromDescriptor(
-                          Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
-                          e.getMethodName(),
-                          e.getDescriptor()),
-                      e.getCodeSize()));
-        }
-      } else {
-        throw new Unimplemented("No support for synthetics in the Java bytecode backend.");
+      try {
+        writeClass(clazz, consumer, rewriter, markerString);
+      } catch (ClassTooLargeException e) {
+        throw appView
+            .options()
+            .reporter
+            .fatalError(
+                new ConstantPoolOverflowDiagnostic(
+                    clazz.getOrigin(),
+                    Reference.classFromBinaryName(e.getClassName()),
+                    e.getConstantPoolCount()));
+      } catch (MethodTooLargeException e) {
+        throw appView
+            .options()
+            .reporter
+            .fatalError(
+                new CodeSizeOverflowDiagnostic(
+                    clazz.getOrigin(),
+                    Reference.methodFromDescriptor(
+                        Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
+                        e.getMethodName(),
+                        e.getDescriptor()),
+                    e.getCodeSize()));
       }
     }
     ApplicationWriter.supplyAdditionalConsumers(
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index b374e58..6b316f7 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -469,7 +469,6 @@
             || getMissingClasses().contains(type)
             // TODO(b/150693139): Remove these exceptions once fixed.
             || InterfaceMethodRewriter.isCompanionClassType(type)
-            || InterfaceMethodRewriter.hasDispatchClassSuffix(type)
             || InterfaceMethodRewriter.isEmulatedLibraryClassType(type)
             || type.toDescriptorString().startsWith("Lj$/$r8$retargetLibraryMember$")
             || TwrCloseResourceRewriter.isUtilityClassDescriptor(type)
@@ -577,6 +576,10 @@
     return neverInlineDueToSingleCaller.contains(method.getReference());
   }
 
+  public boolean isAssumeNoSideEffectsMethod(DexMethod method) {
+    return noSideEffects.containsKey(method);
+  }
+
   public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
     return whyAreYouNotInlining.contains(method);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 985090a..db914a4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -63,12 +63,15 @@
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.IROrdering.IdentityIROrdering;
 import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.io.PrintStream;
@@ -990,7 +993,7 @@
       DexType libraryType,
       DexType invalidSuperType,
       String message,
-      Set<DexEncodedMethod> retarget) {
+      DexClassAndMethodSet retarget) {
     if (invalidLibraryClasses.add(invalidSuperType)) {
       reporter.warning(
           new InvalidLibrarySuperclassDiagnostic(
@@ -998,7 +1001,9 @@
               Reference.classFromDescriptor(libraryType.toDescriptorString()),
               Reference.classFromDescriptor(invalidSuperType.toDescriptorString()),
               message,
-              ListUtils.map(retarget, method -> method.getReference().asMethodReference())));
+              Lists.newArrayList(
+                  Iterables.transform(
+                      retarget, method -> method.getReference().asMethodReference()))));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index ebbef95..cd1c0a0 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -15,6 +15,16 @@
 
 public class IterableUtils {
 
+  public static <S, T> boolean any(
+      Iterable<S> iterable, Function<S, T> transform, Predicate<T> predicate) {
+    for (S element : iterable) {
+      if (predicate.test(transform.apply(element))) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public static <T> Iterable<T> append(Iterable<T> iterable, T element) {
     return Iterables.concat(iterable, singleton(element));
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSet.java
new file mode 100644
index 0000000..ad7c3fe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSet.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, 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.utils.collections;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.google.common.collect.ImmutableMap;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+public class DexClassAndMethodSet extends DexClassAndMethodSetBase<DexClassAndMethod> {
+
+  private static final DexClassAndMethodSet EMPTY = new DexClassAndMethodSet(ImmutableMap::of);
+
+  protected DexClassAndMethodSet(
+      Supplier<? extends Map<DexMethod, DexClassAndMethod>> backingFactory) {
+    super(backingFactory);
+  }
+
+  protected DexClassAndMethodSet(
+      Supplier<? extends Map<DexMethod, DexClassAndMethod>> backingFactory,
+      Map<DexMethod, DexClassAndMethod> backing) {
+    super(backingFactory, backing);
+  }
+
+  public static DexClassAndMethodSet create() {
+    return new DexClassAndMethodSet(IdentityHashMap::new);
+  }
+
+  public static DexClassAndMethodSet create(int capacity) {
+    return new DexClassAndMethodSet(IdentityHashMap::new, new IdentityHashMap<>(capacity));
+  }
+
+  public static DexClassAndMethodSet create(DexClassAndMethod element) {
+    DexClassAndMethodSet result = create();
+    result.add(element);
+    return result;
+  }
+
+  public static DexClassAndMethodSet create(DexClassAndMethodSet methodSet) {
+    DexClassAndMethodSet newMethodSet = create();
+    newMethodSet.addAll(methodSet);
+    return newMethodSet;
+  }
+
+  public static DexClassAndMethodSet createConcurrent() {
+    return new DexClassAndMethodSet(ConcurrentHashMap::new);
+  }
+
+  public static DexClassAndMethodSet createLinked() {
+    return new DexClassAndMethodSet(LinkedHashMap::new);
+  }
+
+  public static DexClassAndMethodSet empty() {
+    return EMPTY;
+  }
+
+  public void addAll(DexClassAndMethodSet methods) {
+    backing.putAll(methods.backing);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
new file mode 100644
index 0000000..797d8b6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2020, 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.utils.collections;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+public abstract class DexClassAndMethodSetBase<T extends DexClassAndMethod> implements Iterable<T> {
+
+  protected final Map<DexMethod, T> backing;
+  protected final Supplier<? extends Map<DexMethod, T>> backingFactory;
+
+  protected DexClassAndMethodSetBase(Supplier<? extends Map<DexMethod, T>> backingFactory) {
+    this(backingFactory, backingFactory.get());
+  }
+
+  protected DexClassAndMethodSetBase(
+      Supplier<? extends Map<DexMethod, T>> backingFactory, Map<DexMethod, T> backing) {
+    this.backing = backing;
+    this.backingFactory = backingFactory;
+  }
+
+  public boolean add(T method) {
+    T existing = backing.put(method.getReference(), method);
+    assert existing == null || existing.isStructurallyEqualTo(method);
+    return existing == null;
+  }
+
+  public void addAll(Iterable<T> methods) {
+    methods.forEach(this::add);
+  }
+
+  public boolean contains(DexEncodedMethod method) {
+    return backing.containsKey(method.getReference());
+  }
+
+  public boolean contains(T method) {
+    return backing.containsKey(method.getReference());
+  }
+
+  public void clear() {
+    backing.clear();
+  }
+
+  public boolean isEmpty() {
+    return backing.isEmpty();
+  }
+
+  @Override
+  public Iterator<T> iterator() {
+    return backing.values().iterator();
+  }
+
+  public boolean remove(DexMethod method) {
+    T existing = backing.remove(method);
+    return existing != null;
+  }
+
+  public boolean remove(DexEncodedMethod method) {
+    return remove(method.getReference());
+  }
+
+  public int size() {
+    return backing.size();
+  }
+
+  public Stream<T> stream() {
+    return backing.values().stream();
+  }
+
+  public Set<DexEncodedMethod> toDefinitionSet() {
+    assert backing instanceof IdentityHashMap;
+    Set<DexEncodedMethod> definitions = Sets.newIdentityHashSet();
+    forEach(method -> definitions.add(method.getDefinition()));
+    return definitions;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
index f058bf1..d36d207 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
@@ -11,32 +11,24 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
-import java.util.stream.Stream;
 
-public class ProgramMethodSet implements Iterable<ProgramMethod> {
+public class ProgramMethodSet extends DexClassAndMethodSetBase<ProgramMethod> {
 
   private static final ProgramMethodSet EMPTY = new ProgramMethodSet(ImmutableMap::of);
 
-  private final Map<DexMethod, ProgramMethod> backing;
-  private final Supplier<? extends Map<DexMethod, ProgramMethod>> backingFactory;
-
   protected ProgramMethodSet(Supplier<? extends Map<DexMethod, ProgramMethod>> backingFactory) {
-    this(backingFactory, backingFactory.get());
+    super(backingFactory);
   }
 
   protected ProgramMethodSet(
       Supplier<? extends Map<DexMethod, ProgramMethod>> backingFactory,
       Map<DexMethod, ProgramMethod> backing) {
-    this.backing = backing;
-    this.backingFactory = backingFactory;
+    super(backingFactory, backing);
   }
 
   public static ProgramMethodSet create() {
@@ -71,16 +63,6 @@
     return EMPTY;
   }
 
-  public boolean add(ProgramMethod method) {
-    ProgramMethod existing = backing.put(method.getReference(), method);
-    assert existing == null || existing.isStructurallyEqualTo(method);
-    return existing == null;
-  }
-
-  public void addAll(Iterable<ProgramMethod> methods) {
-    methods.forEach(this::add);
-  }
-
   public void addAll(ProgramMethodSet methods) {
     backing.putAll(methods.backing);
   }
@@ -89,36 +71,6 @@
     return add(new ProgramMethod(clazz, definition));
   }
 
-  public boolean contains(DexEncodedMethod method) {
-    return backing.containsKey(method.getReference());
-  }
-
-  public boolean contains(ProgramMethod method) {
-    return backing.containsKey(method.getReference());
-  }
-
-  public void clear() {
-    backing.clear();
-  }
-
-  public boolean isEmpty() {
-    return backing.isEmpty();
-  }
-
-  @Override
-  public Iterator<ProgramMethod> iterator() {
-    return backing.values().iterator();
-  }
-
-  public boolean remove(DexMethod method) {
-    ProgramMethod existing = backing.remove(method);
-    return existing != null;
-  }
-
-  public boolean remove(DexEncodedMethod method) {
-    return remove(method.getReference());
-  }
-
   public ProgramMethodSet rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) {
     ProgramMethodSet rewritten = new ProgramMethodSet(backingFactory);
     forEach(
@@ -130,19 +82,4 @@
         });
     return rewritten;
   }
-
-  public int size() {
-    return backing.size();
-  }
-
-  public Stream<ProgramMethod> stream() {
-    return backing.values().stream();
-  }
-
-  public Set<DexEncodedMethod> toDefinitionSet() {
-    assert backing instanceof IdentityHashMap;
-    Set<DexEncodedMethod> definitions = Sets.newIdentityHashSet();
-    forEach(method -> definitions.add(method.getDefinition()));
-    return definitions;
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 726136f..d25c465 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -96,7 +96,6 @@
           Assert.assertTrue(
               descriptor.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
                   || descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
-                  || descriptor.endsWith(InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX + ";")
                   || descriptor.equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR)
                   || descriptor.contains(SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR)
                   || descriptor.equals(mainClassDescriptor));
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
index f6e0d0a..f998b89 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
@@ -4,16 +4,23 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 import org.jetbrains.annotations.NotNull;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -25,12 +32,13 @@
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
   private static final String EXPECTED_OUTPUT =
-      StringUtils.lines("1", "2", "3", "4", "5", "Count: 4");
+      StringUtils.lines("1", "2", "3", "4", "5", "Count: 4", "1", "2", "3", "4", "5");
 
   @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withAllRuntimes().withAllApiLevels().build());
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
   }
 
   public IterableTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
@@ -38,6 +46,54 @@
     this.parameters = parameters;
   }
 
+  private void inspect(CodeInspector inspector) {
+    if (parameters
+        .getApiLevel()
+        .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport())) {
+      assertThat(
+          inspector.clazz(MyIterableSub.class).uniqueMethodWithFinalName("myForEach"),
+          CodeMatchers.invokesMethod(null, MyIterable.class.getTypeName(), "forEach", null));
+    } else {
+      assertThat(
+          inspector.clazz(MyIterableSub.class).uniqueMethodWithFinalName("myForEach"),
+          CodeMatchers.invokesMethod(null, "j$.lang.Iterable$-CC", "$default$forEach", null));
+    }
+  }
+
+  @Test
+  public void testIterableD8Cf() throws Exception {
+    // Only test without shrinking desugared library.
+    Assume.assumeFalse(shrinkDesugaredLibrary);
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addInnerClasses(IterableTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+
+    if (parameters.getRuntime().isDex()) {
+      // Convert to DEX without desugaring and run.
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addRunClasspathFiles(buildDesugaredLibrary(parameters.getApiLevel()))
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    } else {
+      // Run on the JVM with desugared library on classpath.
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(buildDesugaredLibraryClassFile(parameters.getApiLevel()))
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    }
+  }
+
   @Test
   public void testIterable() throws Exception {
     if (parameters.isCfRuntime()) {
@@ -69,6 +125,8 @@
       iterable.forEach(System.out::println);
       Stream<Integer> stream = StreamSupport.stream(iterable.spliterator(), false);
       System.out.println("Count: " + stream.filter(x -> x != 3).count());
+      MyIterableSub<Integer> iterableSub = new MyIterableSub<>(Arrays.asList(1, 2, 3, 4, 5));
+      iterableSub.myForEach(System.out::println);
     }
   }
 
@@ -86,4 +144,15 @@
       return collection.iterator();
     }
   }
+
+  static class MyIterableSub<E> extends MyIterable<E> {
+
+    public MyIterableSub(Collection<E> collection) {
+      super(collection);
+    }
+
+    public void myForEach(Consumer<E> consumer) {
+      super.forEach(consumer);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index 3e40117..31bbcbd 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -5,10 +5,13 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.DexType;
@@ -19,6 +22,7 @@
 import com.android.tools.r8.utils.codeinspector.CheckCastInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -26,6 +30,12 @@
 import com.android.tools.r8.utils.codeinspector.TypeSubject;
 import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
+import java.time.DateTimeException;
+import java.time.ZoneId;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalQueries;
+import java.time.temporal.TemporalQuery;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -48,6 +58,8 @@
           "1970-01-02T10:17:36.789Z",
           "GMT",
           "GMT",
+          "true",
+          "true",
           "Hello, world");
 
   @Parameters(name = "{2}, shrinkDesugaredLibrary: {0}, traceReferencesKeepRules {1}")
@@ -69,7 +81,15 @@
     this.parameters = parameters;
   }
 
-  private void checkRewrittenInvokes(CodeInspector inspector) {
+  private void checkRewrittenInvokesForD8(CodeInspector inspector) {
+    checkRewrittenInvokes(inspector, false);
+  }
+
+  private void checkRewrittenInvokesForR8(CodeInspector inspector) {
+    checkRewrittenInvokes(inspector, true);
+  }
+
+  private void checkRewrittenInvokes(CodeInspector inspector, boolean isR8) {
     Set<String> expectedInvokeHolders;
     Set<String> expectedCatchGuards;
     Set<String> expectedCheckCastType;
@@ -118,6 +138,31 @@
             .map(TypeSubject::toString)
             .collect(Collectors.toSet());
     assertEquals(expectedCatchGuards, foundCatchGuards);
+
+    if (isR8) {
+      assertThat(
+          inspector.clazz(TemporalAccessorImpl.class).uniqueMethodWithFinalName("query"),
+          not(isPresent()));
+    } else {
+      assertThat(
+          inspector.clazz(TemporalAccessorImplSub.class).uniqueMethodWithFinalName("query"),
+          CodeMatchers.invokesMethod(
+              null, TemporalAccessorImpl.class.getTypeName(), "query", null));
+    }
+    if (parameters
+        .getApiLevel()
+        .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport())) {
+      assertThat(
+          inspector.clazz(TemporalAccessorImpl.class).uniqueMethodWithName("query"),
+          not(isPresent()));
+    } else {
+      assertThat(
+          inspector
+              .clazz(isR8 ? TemporalAccessorImplSub.class : TemporalAccessorImpl.class)
+              .uniqueMethodWithFinalName("query"),
+          CodeMatchers.invokesMethod(
+              null, "j$.time.temporal.TemporalAccessor$-CC", "$default$query", null));
+    }
   }
 
   private String desugaredLibraryKeepRules(
@@ -149,7 +194,7 @@
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
-            .inspect(this::checkRewrittenInvokes)
+            .inspect(this::checkRewrittenInvokesForD8)
             .writeToZip();
 
     String desugaredLibraryKeepRules;
@@ -198,7 +243,7 @@
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
-            .inspect(this::checkRewrittenInvokes);
+            .inspect(this::checkRewrittenInvokesForD8);
     result
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
@@ -219,11 +264,12 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(JavaTimeTest.class)
             .addKeepMainRule(TestClass.class)
+            .enableNoVerticalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .enableInliningAnnotations()
             .compile()
-            .inspect(this::checkRewrittenInvokes);
+            .inspect(this::checkRewrittenInvokesForR8);
     result
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
@@ -234,6 +280,31 @@
         .assertSuccessWithOutput(expectedOutput);
   }
 
+  @NoVerticalClassMerging
+  static class TemporalAccessorImpl implements TemporalAccessor {
+    @Override
+    public boolean isSupported(TemporalField field) {
+      return false;
+    }
+
+    @Override
+    public long getLong(TemporalField field) {
+      throw new DateTimeException("Mock");
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class TemporalAccessorImplSub extends TemporalAccessorImpl {
+    @SuppressWarnings("unchecked")
+    @Override
+    public <R> R query(TemporalQuery<R> query) {
+      if (query == TemporalQueries.zoneId()) {
+        return (R) ZoneId.of("GMT");
+      }
+      return super.query(query);
+    }
+  }
+
   static class TestClass {
 
     @NeverInline
@@ -246,6 +317,36 @@
       return System.currentTimeMillis() > 0 ? null : new Object();
     }
 
+    public static void superInvokeOnLibraryDesugaredDefaultMethod() {
+      TemporalAccessor mock =
+          new TemporalAccessor() {
+            @Override
+            public boolean isSupported(TemporalField field) {
+              return false;
+            }
+
+            @Override
+            public long getLong(TemporalField field) {
+              throw new DateTimeException("Mock");
+            }
+
+            @SuppressWarnings("unchecked")
+            @Override
+            public <R> R query(TemporalQuery<R> query) {
+              if (query == TemporalQueries.zoneId()) {
+                return (R) ZoneId.of("GMT");
+              }
+              return TemporalAccessor.super.query(query);
+            }
+          };
+      System.out.println(ZoneId.from(mock).equals(ZoneId.of("GMT")));
+    }
+
+    public static void superInvokeOnLibraryDesugaredDefaultMethodFromSubclass() {
+      TemporalAccessor mock = new TemporalAccessorImplSub();
+      System.out.println(ZoneId.from(mock).equals(ZoneId.of("GMT")));
+    }
+
     public static void main(String[] args) {
       java.time.Clock.systemDefaultZone();
       try {
@@ -266,6 +367,9 @@
       System.out.println(timeZone.getID());
       System.out.println(timeZone.toZoneId().getId());
 
+      superInvokeOnLibraryDesugaredDefaultMethod();
+      superInvokeOnLibraryDesugaredDefaultMethodFromSubclass();
+
       System.out.println("Hello, world");
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
index 294efee..bcdd93f 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -87,7 +87,16 @@
                 // instruction may throw. This holds even if the string-switch instruction is
                 // compiled to a sequence of `if (x.equals("..."))` instructions that do not even
                 // use the hash code.
-                assertNotEquals(0, hashCodeValues.size());
+                assertTrue(
+                    code.collectArguments().get(0).uniqueUsers().stream()
+                        .filter(Instruction::isInvokeMethod)
+                        .map(Instruction::asInvokeMethod)
+                        .map(invoke -> invoke.getInvokedMethod().getName().toSourceString())
+                        .anyMatch(
+                            name ->
+                                name.equals("getClass")
+                                    || name.equals("hashCode")
+                                    || name.equals("requireNonNull")));
               }
             })
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index af047b9..7049089 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -333,7 +335,7 @@
     assertCounters(INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
 
     m = clazz.method("int", "notInlinableDueToMissingNpe", ImmutableList.of("inlining.A"));
-    assertCounters(INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
+    assertCounters(INLINABLE, ALWAYS_INLINABLE, countInvokes(inspector, m));
 
     m = clazz.method("int", "notInlinableDueToSideEffect", ImmutableList.of("inlining.A"));
     assertCounters(INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
@@ -354,7 +356,12 @@
     CodeInspector inspector =
         new CodeInspector(getGeneratedFiles(), getGeneratedProguardMap(), null);
     ClassSubject clazz = inspector.clazz(nullabilityClass);
-    MethodSubject m = clazz.method("int", "conditionalOperator", ImmutableList.of("inlining.A"));
+    assertThat(clazz.uniqueMethodWithName("conditionalOperator"), isAbsent());
+
+    // The enum parameter may get unboxed.
+    MethodSubject m =
+        clazz.uniqueMethodWithName(
+            parameters.isCfRuntime() ? "moreControlFlows" : "moreControlFlows$enumunboxing$");
     assertTrue(m.isPresent());
 
     // Verify that a.b() is resolved to an inline instance-get.
@@ -365,27 +372,6 @@
       InstructionSubject instruction = iterator.next();
       if (instruction.isInstanceGet()) {
         ++instanceGetCount;
-      } else if (instruction.isInvoke()) {
-        ++invokeCount;
-      }
-    }
-    assertEquals(1, instanceGetCount);
-    assertEquals(0, invokeCount);
-
-    // The enum parameter may get unboxed.
-    m =
-        clazz.uniqueMethodWithName(
-            parameters.isCfRuntime() ? "moreControlFlows" : "moreControlFlows$enumunboxing$");
-    assertTrue(m.isPresent());
-
-    // Verify that a.b() is resolved to an inline instance-get.
-    iterator = m.iterateInstructions();
-    instanceGetCount = 0;
-    invokeCount = 0;
-    while (iterator.hasNext()) {
-      InstructionSubject instruction = iterator.next();
-      if (instruction.isInstanceGet()) {
-        ++instanceGetCount;
       } else if (instruction.isInvoke() && !isEnumInvoke(instruction)) {
         ++invokeCount;
       }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index c253c45..8d01a11 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.LinkedList;
 import java.util.List;
@@ -62,6 +64,18 @@
     }
 
     @Override
+    public boolean replaceCurrentInstructionByNullCheckIfPossible(
+        AppView<?> appView, ProgramMethod context) {
+      throw new Unimplemented();
+    }
+
+    @Override
+    public boolean replaceCurrentInstructionByInitClassIfPossible(
+        AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+      throw new Unimplemented();
+    }
+
+    @Override
     public void replaceCurrentInstructionWithConstClass(
         AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
       throw new Unimplemented();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index 447ed7e..7565cf7 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -87,7 +87,7 @@
                     .assertErrorsMatch(
                         diagnosticMessage(
                             containsString(
-                                "--main-dex-list-output require --main-dex-rules and/or"
+                                "--main-dex-list-output requires --main-dex-rules and/or"
                                     + " --main-dex-list"))));
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
index eb29f88..fed4e6d 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.AssumeMayHaveSideEffects;
@@ -71,7 +72,11 @@
     assertThat(
         methodSubject,
         onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("hashCode")));
-    assertThat(methodSubject, onlyIf(maybeNullReceiver, invokesMethodWithName("getClass")));
+    assertThat(
+        methodSubject,
+        onlyIf(
+            maybeNullReceiver,
+            anyOf(invokesMethodWithName("getClass"), invokesMethodWithName("requireNonNull"))));
     assertThat(
         methodSubject,
         onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("toString")));
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
index 1c7f6ac..8130a6f 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
@@ -111,7 +111,7 @@
   private final TestConfig config;
   private final boolean enableHorizontalClassMerging;
 
-  @Parameterized.Parameters(name = "{0} {1}")
+  @Parameterized.Parameters(name = "{0}, config: {1}, horizontal: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index e85f2d8..04ac085 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -252,7 +252,7 @@
             "    move-result-object  v0",
             "    invoke-virtual      { v0, v1 }, " + stringBuilderAppendSignature,
             "    move-result-object  v0",
-            "    invoke-virtual      { v0 }, " + stringBuilderToStringSignature,
+            "    invoke-virtual      { v0, v1 }, " + stringBuilderAppendSignature,
             "    move-result-object  v0",
             "    const               v0, 0",
             "    const               v1, 1",
@@ -818,7 +818,7 @@
         "    move-result-object  v0",
         "    invoke-virtual      { v0, v1 }, " + stringBuilderAppendSignature,
         "    move-result-object  v0",
-        "    invoke-virtual      { v0 }, " + stringBuilderToStringSignature,
+        "    invoke-virtual      { v0, v1 }, " + stringBuilderAppendSignature,
         "    return-void");
 
     String returnType2 = "java.lang.String";
@@ -866,14 +866,14 @@
     ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
     assertTrue(clazz.isPresent());
     assertEquals(3, clazz.getDexProgramClass().getMethodCollection().numberOfDirectMethods());
-    // Collect the return types of the putlines for the body of method1 and method2.
+    // Collect the return types of the outlines for the body of method1 and method2.
     List<DexType> r = new ArrayList<>();
     for (DexEncodedMethod directMethod : clazz.getDexProgramClass().directMethods()) {
       if (directMethod.getCode().asDexCode().instructions[0] instanceof InvokeVirtual) {
         r.add(directMethod.method.proto.returnType);
       }
     }
-    assert r.size() == 2;
+    assertEquals(2, r.size());
     DexType r1 = r.get(0);
     DexType r2 = r.get(1);
     DexItemFactory factory = inspector.getFactory();
@@ -882,7 +882,7 @@
 
     // Run the code.
     String result = runArt(processedApplication);
-    assertEquals("TestTestTestTest", result);
+    assertEquals("TestTestTestTestTest", result);
   }
 
   @Test
diff --git a/tools/archive.py b/tools/archive.py
index b7ca7c0..e92e87d 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -145,6 +145,8 @@
     utils.D8,
     utils.R8LIB,
     utils.R8LIB_NO_DEPS,
+    utils.R8RETRACE,
+    utils.R8RETRACE_NO_DEPS,
     utils.LIBRARY_DESUGAR_CONVERSIONS,
     '-Pno_internal'
   ])
@@ -191,6 +193,8 @@
       utils.R8_FULL_EXCLUDE_DEPS_JAR,
       utils.R8LIB_EXCLUDE_DEPS_JAR,
       utils.R8LIB_EXCLUDE_DEPS_JAR + '.map',
+      utils.R8RETRACE_JAR,
+      utils.R8RETRACE_EXCLUDE_DEPS_JAR,
       utils.MAVEN_ZIP,
       utils.MAVEN_ZIP_LIB,
       utils.DESUGAR_CONFIGURATION,
diff --git a/tools/as_utils.py b/tools/as_utils.py
index fa81ac4..43153c6 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -3,11 +3,18 @@
 # 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.
 
+import utils
+
 from distutils.version import LooseVersion
-from HTMLParser import HTMLParser
 import os
 import shutil
 
+if utils.is_python3():
+  from html.parser import HTMLParser
+else:
+  from HTMLParser import HTMLParser
+
+
 def add_r8_dependency(checkout_dir, temp_dir, minified):
   build_file = os.path.join(checkout_dir, 'build.gradle')
   assert os.path.isfile(build_file), (
diff --git a/tools/r8_release.py b/tools/r8_release.py
index c3aff38..31bb4c6 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -362,6 +362,7 @@
       g4_open('src.jar')
       g4_open('lib.jar')
       g4_open('lib.jar.map')
+      g4_open('retrace.jar')
       g4_open('desugar_jdk_libs_configuration.jar')
       download_file(options.version, 'r8-full-exclude-deps.jar', 'full.jar')
       download_file(options.version, 'r8-src.jar', 'src.jar')
@@ -370,6 +371,7 @@
           options.version, 'r8lib-exclude-deps.jar.map', 'lib.jar.map')
       download_file(options.version, 'desugar_jdk_libs_configuration.jar',
                     'desugar_jdk_libs_configuration.jar')
+      download_file(options.version, 'r8retrace-exclude-deps.jar', 'retrace.jar')
       g4_open('METADATA')
       sed(r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev',
           options.version,
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 4509656..c4de6e3 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -5,6 +5,7 @@
 
 import adb
 import apk_masseur
+import as_utils
 import compiledump
 import gradle
 import optparse
@@ -516,7 +517,7 @@
   result['status'] = 'success'
   result_per_shrinker = build_app_with_shrinkers(
     app, options, temp_dir, app_dir)
-  for shrinker, shrinker_result in result_per_shrinker.iteritems():
+  for shrinker, shrinker_result in result_per_shrinker.items():
     result[shrinker] = shrinker_result
   return result
 
@@ -940,7 +941,8 @@
       assert any(app.name == app_name for app in options.apps)
   if options.shrinker:
     for shrinker in options.shrinker:
-      assert shrinker in SHRINKERS
+      assert shrinker in SHRINKERS, (
+          'Shrinker must be one of %s' % ', '.join(SHRINKERS))
   else:
     options.shrinker = [shrinker for shrinker in SHRINKERS]
 
diff --git a/tools/utils.py b/tools/utils.py
index 351a038..ee49000 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -41,6 +41,8 @@
 R8 = 'r8'
 R8LIB = 'r8lib'
 R8LIB_NO_DEPS = 'r8LibNoDeps'
+R8RETRACE = 'R8Retrace'
+R8RETRACE_NO_DEPS = 'R8RetraceNoDeps'
 R8_SRC = 'sourceJar'
 LIBRARY_DESUGAR_CONVERSIONS = 'buildLibraryDesugarConversions'
 
@@ -52,6 +54,8 @@
 R8_SRC_JAR = os.path.join(LIBS, 'r8-src.jar')
 R8LIB_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8lib-exclude-deps.jar')
 R8_FULL_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8-full-exclude-deps.jar')
+R8RETRACE_JAR = os.path.join(LIBS, 'r8retrace.jar')
+R8RETRACE_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8retrace-exclude-deps.jar')
 MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
 MAVEN_ZIP_LIB = os.path.join(LIBS, 'r8lib.zip')
 LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join(LIBS, 'library_desugar_conversions.zip')
@@ -104,6 +108,9 @@
   version = os.environ.get(ANDROID_TOOLS_VERSION_ENVIRONMENT_NAME, '28.0.3')
   return os.path.join(getAndroidHome(), 'build-tools', version)
 
+def is_python3():
+  return sys.version_info.major == 3
+
 def Print(s, quiet=False):
   if quiet:
     return