Merge commit '8e2f68f2b3881569a1f8fce97661853e72712928' into dev-release
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 41ac1e6..c2cea80 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -54,9 +54,11 @@
     {
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
+        "java.io.DesugarBufferedReader": "j$.io.DesugarBufferedReader",
         "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
         "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
         "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
+        "java.util.Objects": "j$.util.Objects",
         "java.util.Optional": "j$.util.Optional",
         "java.util.PrimitiveIterator": "j$.util.PrimitiveIterator",
         "java.util.Spliterator": "j$.util.Spliterator",
@@ -67,6 +69,9 @@
         "java.util.function.": "j$.util.function.",
         "java.util.stream.": "j$.util.stream."
       },
+      "maintain_prefix": [
+        "java.io.UncheckedIOException"
+      ],
       "emulate_interface": {
         "java.lang.Iterable": "j$.lang.Iterable",
         "java.util.Collection": "j$.util.Collection",
@@ -101,6 +106,7 @@
         "java.util.stream.Stream java.util.Arrays#stream(java.lang.Object[], int, int)": "java.util.DesugarArrays"
       },
       "retarget_method_with_emulated_dispatch": {
+        "java.util.stream.Stream java.io.BufferedReader#lines()": "java.io.DesugarBufferedReader",
         "java.util.Spliterator java.util.LinkedHashSet#spliterator()": "java.util.DesugarLinkedHashSet"
       },
       "wrapper_conversion": [
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
new file mode 100644
index 0000000..73fa330
--- /dev/null
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_minimal.json
@@ -0,0 +1,24 @@
+{
+  "identifier": "com.tools.android:desugar_jdk_libs_minimal:2.0.0",
+  "configuration_format_version": 100,
+  "required_compilation_api_level": 24,
+  "synthesized_library_classes_package_prefix": "j$.",
+  "support_all_callbacks_from_library": false,
+  "common_flags": [
+    {
+      "api_level_below_or_equal": 23,
+      "maintain_prefix": [
+        "java.util.function.",
+        "java.util.Optional"
+      ]
+    }
+  ],
+  "program_flags": [],
+  "library_flags": [],
+  "shrinker_config": [
+    "-keeppackagenames java.**",
+    "-keepattributes Signature",
+    "-keepattributes EnclosingMethod",
+    "-keepattributes InnerClasses"
+  ]
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index c953853..acbd677 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -579,9 +579,7 @@
     HorizontalClassMergerOptions horizontalClassMergerOptions =
         internal.horizontalClassMergerOptions();
     if (internal.isGeneratingDex()) {
-      // TODO(b/227791663): Disable until fixed.
-      horizontalClassMergerOptions.disable();
-      // horizontalClassMergerOptions.setRestrictToSynthetics();
+      horizontalClassMergerOptions.setRestrictToSynthetics();
     } else {
       assert internal.isGeneratingClassFiles();
       horizontalClassMergerOptions.disable();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 1c8ba99..4e89130 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CheckDiscardDiagnostic;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.startup.StartupInstrumentation;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
@@ -443,6 +444,8 @@
       assert appView.appInfo().hasLiveness();
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
 
+      new StartupInstrumentation(appView).instrumentClasses(executorService);
+
       assert verifyNoJarApplicationReaders(appView.appInfo().classes());
       assert appView.checkForTesting(() -> allReferencesAssignedApiLevel(appViewWithLiveness));
       // Build conservative main dex content after first round of tree shaking. This is used
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index f259f26..888b78b 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -945,8 +945,10 @@
 
     internal.featureSplitConfiguration = featureSplitConfiguration;
 
-    internal.startupConfiguration =
-        StartupConfiguration.createStartupConfiguration(getDexItemFactory(), getReporter());
+    internal
+        .getStartupOptions()
+        .setStartupConfiguration(
+            StartupConfiguration.createStartupConfiguration(getDexItemFactory(), getReporter()));
 
     internal.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
 
@@ -983,6 +985,8 @@
 
     internal.setDesugaredLibrarySpecification(desugaredLibrarySpecification);
     internal.synthesizedClassPrefix = synthesizedClassPrefix;
+    // TODO(b/214382176): Enable all the time.
+    internal.loadAllClassDefinitions = !synthesizedClassPrefix.isEmpty();
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
     // Set up the map and source file providers.
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
index 0d466f0..c81abe4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
@@ -174,16 +174,6 @@
       for (CfTryCatch tryCatchRange : tryCatchRanges) {
         if (tryCatchRange.start == label) {
           currentCatchRanges.add(tryCatchRange);
-          // We can have fall-through into this range requiring the current frame being
-          // assignable to the handler frame. This is handled for locals when we see a throwing
-          // instruction, but we can validate here that the stack will be a single element stack
-          // [throwable].
-          CfFrame destinationFrame = stateMap.get(tryCatchRange.start);
-          if (destinationFrame == null) {
-            throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
-          }
-          checkStackIsAssignable(
-              destinationFrame.getStack(), throwStack, factory, isJavaAssignable);
         }
       }
       currentCatchRanges.removeIf(currentRange -> currentRange.end == label);
@@ -191,6 +181,35 @@
     return this;
   }
 
+  public void checkTryCatchRange(CfTryCatch tryCatchRange) {
+    // According to the spec:
+    // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1
+    // saying ` and the handler's target (the initial instruction of the handler code) is type
+    // safe assuming an incoming type state T. The type state T is derived from ExcStackFrame
+    // by replacing the operand stack with a stack whose sole element is the handler's
+    // exception class.
+    tryCatchRange.targets.forEach(
+        target -> {
+          CfFrame destinationFrame = stateMap.get(target);
+          if (destinationFrame == null) {
+            throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
+          }
+          // From the spec: the handler's exception class is assignable to the class Throwable.
+          tryCatchRange.guards.forEach(
+              guard -> {
+                if (!isJavaAssignable.test(guard, factory.throwableType)) {
+                  throw CfCodeStackMapValidatingException.error(
+                      "Could not assign '" + guard.toSourceString() + "' to throwable.");
+                }
+                checkStackIsAssignable(
+                    ImmutableDeque.of(FrameType.initialized(guard)),
+                    destinationFrame.getStack(),
+                    factory,
+                    isJavaAssignable);
+              });
+        });
+  }
+
   private void checkFrameIsSet() {
     if (currentFrame == NO_FRAME) {
       throw CfCodeStackMapValidatingException.error("Unexpected state change");
@@ -218,9 +237,6 @@
         if (destinationFrame == null) {
           throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
         }
-        // We have to check all current handler targets have assignable locals and a 1-element
-        // stack assignable to throwable. It is not required that the the thrown error is
-        // handled.
         checkLocalsIsAssignable(
             currentFrame.getLocals(), destinationFrame.getLocals(), factory, isJavaAssignable);
       }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 9e3b1ee..3df1eba 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -100,6 +100,6 @@
       DexMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // This is a no-op.
+    frameBuilder.seenLabel(this);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 7bfd947..6b77d5b 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -66,17 +67,26 @@
       this.options = options;
     }
 
-    private boolean shouldKeep(DexType type) {
-      return namingLens.prefixRewrittenType(type) != null
-          || options.machineDesugaredLibrarySpecification.getMaintainType().contains(type)
-          || options.machineDesugaredLibrarySpecification.isCustomConversionRewrittenType(type)
-          || options.machineDesugaredLibrarySpecification.isEmulatedInterfaceRewrittenType(type)
+    private boolean shouldKeep(DexType givenType) {
+      if (namingLens.prefixRewrittenType(givenType) != null
+          || options.machineDesugaredLibrarySpecification.isCustomConversionRewrittenType(givenType)
+          || options.machineDesugaredLibrarySpecification.isEmulatedInterfaceRewrittenType(
+              givenType)
           // TODO(b/158632510): This should prefix match on DexString.
-          || type.toDescriptorString()
+          || givenType
+              .toDescriptorString()
               .startsWith(
                   "L"
                       + options.machineDesugaredLibrarySpecification
-                          .getSynthesizedLibraryClassesPackagePrefix());
+                          .getSynthesizedLibraryClassesPackagePrefix())) {
+        return true;
+      }
+      DexType type =
+          InterfaceDesugaringSyntheticHelper.isCompanionClassType(givenType)
+              ? InterfaceDesugaringSyntheticHelper.getInterfaceClassType(
+                  givenType, options.dexItemFactory())
+              : givenType;
+      return options.machineDesugaredLibrarySpecification.getMaintainType().contains(type);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
index 92671f2..9978311 100644
--- a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
+++ b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Maps;
 import java.util.ArrayList;
@@ -279,26 +280,29 @@
   }
 
   private final VirtualFile mainDex;
-  private final List<VirtualFile> dexes;
+  private final List<VirtualFile> files;
+  private final List<VirtualFile> filesForDistribution;
   private final BitSet fullDex = new BitSet();
   private final Set<DexProgramClass> classes;
   private final AppView<?> appView;
-  private final int dexIndexOffset;
+  private final IntBox nextFileId;
   private final NamingLens namingLens;
   private final DirectSubClassesInfo directSubClasses;
 
   public InheritanceClassInDexDistributor(
       VirtualFile mainDex,
-      List<VirtualFile> dexes,
+      List<VirtualFile> files,
+      List<VirtualFile> filesForDistribution,
       Set<DexProgramClass> classes,
-      int dexIndexOffset,
+      IntBox nextFileId,
       NamingLens namingLens,
       AppView<?> appView,
       ExecutorService executorService) {
     this.mainDex = mainDex;
-    this.dexes = dexes;
+    this.files = files;
+    this.filesForDistribution = filesForDistribution;
     this.classes = classes;
-    this.dexIndexOffset = dexIndexOffset;
+    this.nextFileId = nextFileId;
     this.namingLens = namingLens;
     this.appView = appView;
     this.executorService = executorService;
@@ -315,6 +319,8 @@
 
     // Allocate member of groups depending on
     // the main dex members
+    VirtualFileCycler cycler =
+        new VirtualFileCycler(files, filesForDistribution, appView, namingLens, nextFileId);
     for (Iterator<ClassGroup> iter = remainingInheritanceGroups.iterator(); iter.hasNext();) {
       ClassGroup group = iter.next();
       if (group.dependsOnMainDexClasses) {
@@ -344,19 +350,19 @@
             new ClassGroup(groupSplit.mainDexIndependents);
 
         Collection<VirtualFile> mainDexInpendentsDexes =
-            assignGroup(mainDexIndependentGroup, Collections.singletonList(mainDex));
+            assignGroup(mainDexIndependentGroup, cycler, Collections.singletonList(mainDex));
 
         Set<DexProgramClass> classesWithLinkingError =
             new HashSet<>(groupSplit.dependentsOfMainDexIndependents);
         classesWithLinkingError.addAll(classesMissingMainDex);
-        assignClassesWithLinkingError(classesWithLinkingError, mainDexInpendentsDexes);
+        assignClassesWithLinkingError(classesWithLinkingError, cycler, mainDexInpendentsDexes);
       }
     }
 
     // Allocate member of groups independents from the main dex members
     for (ClassGroup group : remainingInheritanceGroups) {
       if (!group.dependsOnMainDexClasses) {
-        assignGroup(group, Collections.emptyList());
+        assignGroup(group, cycler, Collections.emptyList());
       }
     }
   }
@@ -369,11 +375,13 @@
     return groupClassNumber;
   }
 
-  private Collection<VirtualFile> assignGroup(ClassGroup group, List<VirtualFile> exclude) {
-    VirtualFileCycler cycler = new VirtualFileCycler(dexes, appView, namingLens, dexIndexOffset);
+  private Collection<VirtualFile> assignGroup(
+      ClassGroup group, VirtualFileCycler cycler, List<VirtualFile> exclude) {
     if (group.members.isEmpty()) {
       return Collections.emptyList();
-    } else if (group.canFitInOneDex()) {
+    }
+    cycler.reset();
+    if (group.canFitInOneDex()) {
       VirtualFile currentDex;
       while (true) {
         currentDex = cycler.nextOrCreate(dex -> !exclude.contains(dex) && !isDexFull(dex));
@@ -399,7 +407,8 @@
       Collection<VirtualFile> newExclude = new HashSet<>(exclude);
       newExclude.add(dexForLinkingClasses);
 
-      Collection<VirtualFile> usedDex = assignClassesWithLinkingError(remaining, newExclude);
+      Collection<VirtualFile> usedDex =
+          assignClassesWithLinkingError(remaining, cycler, newExclude);
       usedDex.add(dexForLinkingClasses);
       return usedDex;
     }
@@ -412,14 +421,11 @@
    * @param classes set of classes to assign, the set will be destroyed during assignment.
    */
   private Collection<VirtualFile> assignClassesWithLinkingError(
-      Set<DexProgramClass> classes, Collection<VirtualFile> exclude) {
-
+      Set<DexProgramClass> classes, VirtualFileCycler cycler, Collection<VirtualFile> exclude) {
     List<ClassGroup> layers = collectNoDirectInheritanceGroups(classes);
-
     Collections.sort(layers);
 
     Collection<VirtualFile> usedDex = new ArrayList<>();
-    VirtualFileCycler cycler = new VirtualFileCycler(dexes, appView, namingLens, dexIndexOffset);
     // Don't modify exclude. Think about modifying the input collection considering this
     // is private API.
     Set<VirtualFile> currentExclude = new HashSet<>(exclude);
@@ -456,10 +462,8 @@
             dexForLayer.commitTransaction();
             break;
           }
-
         }
       }
-
     }
 
     return usedDex;
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 7f39bfc..d5995f4 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -24,7 +25,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ThrowNullCode;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -34,18 +37,22 @@
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -320,11 +327,7 @@
         if (!combineSyntheticClassesWithPrimaryClass
             || !appView.getSyntheticItems().isSyntheticClass(clazz)) {
           VirtualFile file =
-              new VirtualFile(
-                  virtualFiles.size(),
-                  writer.appView,
-                  writer.namingLens,
-                  clazz);
+              new VirtualFile(virtualFiles.size(), appView, writer.namingLens, clazz);
           virtualFiles.add(file);
           file.addClass(clazz);
           files.put(clazz, file);
@@ -362,7 +365,7 @@
       this.classes = SetUtils.newIdentityHashSet(classes);
 
       // Create the primary dex file. The distribution will add more if needed.
-      mainDexFile = new VirtualFile(0, writer.appView, writer.namingLens);
+      mainDexFile = new VirtualFile(0, appView, writer.namingLens);
       assert virtualFiles.isEmpty();
       virtualFiles.add(mainDexFile);
       addMarkers(mainDexFile);
@@ -435,25 +438,27 @@
       if (featureSplitClasses.isEmpty()) {
         return;
       }
-      List<VirtualFile> filesForDistribution;
       for (Map.Entry<FeatureSplit, Set<DexProgramClass>> featureSplitSetEntry :
           featureSplitClasses.entrySet()) {
         // Add a new virtual file, start from index 0 again
+        IntBox nextFileId = new IntBox();
         VirtualFile featureFile =
             new VirtualFile(
-                0,
-                writer.appView,
+                nextFileId.getAndIncrement(),
+                appView,
                 writer.namingLens,
                 featureSplitSetEntry.getKey());
         virtualFiles.add(featureFile);
         addMarkers(featureFile);
-        filesForDistribution = virtualFiles.subList(virtualFiles.size() - 1, virtualFiles.size());
+        List<VirtualFile> files = virtualFiles;
+        List<VirtualFile> filesForDistribution = ImmutableList.of(featureFile);
         new PackageSplitPopulator(
+                files,
                 filesForDistribution,
                 appView,
                 featureSplitSetEntry.getValue(),
                 originalNames,
-                0,
+                nextFileId,
                 writer.namingLens)
             .run();
       }
@@ -474,6 +479,9 @@
 
     @Override
     public List<VirtualFile> run() throws IOException {
+      assert virtualFiles.size() == 1;
+      assert virtualFiles.get(0).isEmpty();
+
       int totalClassNumber = classes.size();
       // First fill required classes into the main dex file.
       fillForMainDexList(classes);
@@ -483,37 +491,37 @@
       }
 
       List<VirtualFile> filesForDistribution = virtualFiles;
-      int fileIndexOffset = 0;
       boolean multidexLegacy = !mainDexFile.isEmpty();
       if (options.minimalMainDex && multidexLegacy) {
-        assert !virtualFiles.get(0).isEmpty();
         assert virtualFiles.size() == 1;
-        // The main dex file is filtered out, so ensure at least one file for the remaining classes.
-        virtualFiles.add(new VirtualFile(1, writer.appView, writer.namingLens));
-        filesForDistribution = virtualFiles.subList(1, virtualFiles.size());
-        fileIndexOffset = 1;
+        assert !virtualFiles.get(0).isEmpty();
+        // Don't consider the main dex for distribution.
+        filesForDistribution = Collections.emptyList();
       }
 
       Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses =
           removeFeatureSplitClassesGetMapping();
 
+      IntBox nextFileId = new IntBox(1);
       if (multidexLegacy && options.enableInheritanceClassInDexDistributor) {
         new InheritanceClassInDexDistributor(
                 mainDexFile,
+                virtualFiles,
                 filesForDistribution,
                 classes,
-                fileIndexOffset,
+                nextFileId,
                 writer.namingLens,
-                writer.appView,
+                appView,
                 executorService)
             .distribute();
       } else {
         new PackageSplitPopulator(
+                virtualFiles,
                 filesForDistribution,
                 appView,
                 classes,
                 originalNames,
-                fileIndexOffset,
+                nextFileId,
                 writer.namingLens)
             .run();
       }
@@ -820,33 +828,41 @@
   static class VirtualFileCycler {
 
     private final List<VirtualFile> files;
+    private final List<VirtualFile> filesForDistribution;
     private final AppView<?> appView;
     private final NamingLens namingLens;
 
-    private int nextFileId;
+    private final IntBox nextFileId;
     private Iterator<VirtualFile> allFilesCyclic;
     private Iterator<VirtualFile> activeFiles;
-    private FeatureSplit featuresplit;
+    private FeatureSplit featureSplit;
 
     VirtualFileCycler(
         List<VirtualFile> files,
+        List<VirtualFile> filesForDistribution,
         AppView<?> appView,
         NamingLens namingLens,
-        int fileIndexOffset) {
+        IntBox nextFileId) {
       this.files = files;
+      this.filesForDistribution = new ArrayList<>(filesForDistribution);
       this.appView = appView;
       this.namingLens = namingLens;
+      this.nextFileId = nextFileId;
 
-      nextFileId = files.size() + fileIndexOffset;
-      if (files.size() > 0) {
-        featuresplit = files.get(0).getFeatureSplit();
+      if (filesForDistribution.size() > 0) {
+        featureSplit = filesForDistribution.get(0).getFeatureSplit();
       }
 
       reset();
     }
 
+    void clearFilesForDistribution() {
+      filesForDistribution.clear();
+      reset();
+    }
+
     void reset() {
-      allFilesCyclic = Iterators.cycle(files);
+      allFilesCyclic = Iterators.cycle(filesForDistribution);
       restart();
     }
 
@@ -863,11 +879,10 @@
      */
     VirtualFile nextOrCreate() {
       if (hasNext()) {
-        return activeFiles.next();
+        return next();
       } else {
-        VirtualFile newFile = new VirtualFile(nextFileId++, appView, namingLens, featuresplit);
-        files.add(newFile);
-        allFilesCyclic = Iterators.cycle(files);
+        VirtualFile newFile = internalAddFile();
+        allFilesCyclic = Iterators.cycle(filesForDistribution);
         return newFile;
       }
     }
@@ -892,16 +907,29 @@
 
     // Start a new iteration over all files, starting at the current one.
     void restart() {
-      activeFiles = Iterators.limit(allFilesCyclic, files.size());
+      activeFiles = Iterators.limit(allFilesCyclic, filesForDistribution.size());
     }
 
     VirtualFile addFile() {
-      VirtualFile newFile = new VirtualFile(nextFileId++, appView, namingLens, featuresplit);
-      files.add(newFile);
-
+      VirtualFile newFile = internalAddFile();
       reset();
       return newFile;
     }
+
+    private VirtualFile internalAddFile() {
+      VirtualFile newFile =
+          new VirtualFile(nextFileId.getAndIncrement(), appView, namingLens, featureSplit);
+      files.add(newFile);
+      filesForDistribution.add(newFile);
+      return newFile;
+    }
+
+    VirtualFileCycler ensureFile() {
+      if (filesForDistribution.isEmpty()) {
+        addFile();
+      }
+      return this;
+    }
   }
 
   /**
@@ -1029,16 +1057,18 @@
 
     PackageSplitPopulator(
         List<VirtualFile> files,
+        List<VirtualFile> filesForDistribution,
         AppView<?> appView,
         Collection<DexProgramClass> classes,
         Map<DexProgramClass, String> originalNames,
-        int fileIndexOffset,
+        IntBox nextFileId,
         NamingLens namingLens) {
       this.classPartioning = PackageSplitClassPartioning.create(classes, appView, originalNames);
       this.originalNames = originalNames;
       this.dexItemFactory = appView.dexItemFactory();
       this.options = appView.options();
-      this.cycler = new VirtualFileCycler(files, appView, namingLens, fileIndexOffset);
+      this.cycler =
+          new VirtualFileCycler(files, filesForDistribution, appView, namingLens, nextFileId);
     }
 
     static boolean coveredByPrefix(String originalName, String currentPrefix) {
@@ -1059,11 +1089,19 @@
 
     public void run() {
       addStartupClasses();
+      enableStartupCompletenessCheckForTesting();
       List<DexProgramClass> nonPackageClasses = addNonStartupClasses();
       addNonPackageClasses(cycler, nonPackageClasses);
     }
 
     private void addStartupClasses() {
+      List<DexProgramClass> startupClasses = classPartioning.getStartupClasses();
+      if (startupClasses.isEmpty()) {
+        return;
+      }
+
+      assert options.getStartupOptions().hasStartupConfiguration();
+
       // In practice, all startup classes should fit in a single dex file, so optimistically try to
       // commit the startup classes using a single transaction.
       VirtualFile virtualFile = cycler.next();
@@ -1091,7 +1129,36 @@
         }
       }
 
-      cycler.restart();
+      if (options.getStartupOptions().isMinimalStartupDexEnabled()) {
+        cycler.clearFilesForDistribution();
+      } else {
+        cycler.restart();
+      }
+    }
+
+    /**
+     * Replaces the code of each method of a non-startup class by {@code throw null}. If the
+     * application fails on launch with this enabled this points to the startup configuration being
+     * incomplete.
+     */
+    private void enableStartupCompletenessCheckForTesting() {
+      if (!options.getStartupOptions().isStartupCompletenessCheckForTesting()) {
+        return;
+      }
+      for (DexProgramClass clazz : classPartioning.getNonStartupClasses()) {
+        clazz.forEachProgramMethodMatching(
+            DexEncodedMethod::hasCode,
+            method ->
+                method.getDefinition().setCode(ThrowNullCode.get(), Int2ReferenceMaps.emptyMap()));
+        if (!clazz.hasClassInitializer()) {
+          clazz.addDirectMethod(
+              DexEncodedMethod.syntheticBuilder()
+                  .setAccessFlags(MethodAccessFlags.createForClassInitializer())
+                  .setCode(ThrowNullCode.get())
+                  .setMethod(dexItemFactory.createClassInitializer(clazz.getType()))
+                  .build());
+        }
+      }
     }
 
     private List<DexProgramClass> addNonStartupClasses() {
@@ -1099,7 +1166,7 @@
       int transactionStartIndex = 0;
       String currentPrefix = null;
       Object2IntMap<String> packageAssignments = new Object2IntOpenHashMap<>();
-      VirtualFile current = cycler.next();
+      VirtualFile current = cycler.ensureFile().next();
       List<DexProgramClass> classes = classPartioning.getNonStartupClasses();
       List<DexProgramClass> nonPackageClasses = new ArrayList<>();
       for (int classIndex = 0; classIndex < classes.size(); classIndex++) {
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
index 0f9ad9c..0c1425c 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
@@ -45,11 +45,13 @@
    */
   public static StartupConfiguration createStartupConfiguration(
       DexItemFactory dexItemFactory, Reporter reporter) {
-    String propertyValue = System.getProperty("com.android.tools.r8.startupclassdescriptors");
+    String propertyValue = System.getProperty("com.android.tools.r8.startup.config");
     if (propertyValue == null) {
       return null;
     }
 
+    reporter.warning("Use of startupconfig is experimental");
+
     List<String> startupDescriptors;
     try {
       startupDescriptors = FileUtils.readAllLines(Paths.get(propertyValue));
@@ -115,10 +117,10 @@
 
     String protoWithNameDescriptor = startupMethodDescriptor.substring(methodNameStartIndex);
     int methodNameEndIndex = protoWithNameDescriptor.indexOf('(');
-    if (methodNameEndIndex <= 1) {
+    if (methodNameEndIndex <= 0) {
       return null;
     }
-    String methodName = protoWithNameDescriptor.substring(methodNameEndIndex);
+    String methodName = protoWithNameDescriptor.substring(0, methodNameEndIndex);
 
     String protoDescriptor = protoWithNameDescriptor.substring(methodNameEndIndex);
     DexProto proto = parseStartupMethodProto(protoDescriptor, dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
new file mode 100644
index 0000000..8995dbc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2022, 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.experimental.startup;
+
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import org.objectweb.asm.Opcodes;
+
+public class StartupInstrumentation {
+
+  private final AppView<?> appView;
+  private final DexItemFactory dexItemFactory;
+  private final StartupOptions options;
+
+  public StartupInstrumentation(AppView<?> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+    this.options = appView.options().getStartupOptions();
+  }
+
+  public void instrumentClasses(ExecutorService executorService) throws ExecutionException {
+    if (!appView.options().getStartupOptions().isStartupInstrumentationEnabled()) {
+      return;
+    }
+    ThreadUtils.processItems(
+        appView.appInfo().classes(), this::internalInstrumentClass, executorService);
+  }
+
+  public void instrumentClass(DexProgramClass clazz) {
+    if (!appView.options().getStartupOptions().isStartupInstrumentationEnabled()) {
+      return;
+    }
+    internalInstrumentClass(clazz);
+  }
+
+  private void internalInstrumentClass(DexProgramClass clazz) {
+    ProgramMethod classInitializer = ensureClassInitializer(clazz);
+    instrumentClassInitializer(classInitializer);
+  }
+
+  private ProgramMethod ensureClassInitializer(DexProgramClass clazz) {
+    if (!clazz.hasClassInitializer()) {
+      int maxLocals = 0;
+      int maxStack = 0;
+      ComputedApiLevel computedApiLevel =
+          appView.apiLevelCompute().computeInitialMinApiLevel(appView.options());
+      clazz.addDirectMethod(
+          DexEncodedMethod.syntheticBuilder()
+              .setAccessFlags(MethodAccessFlags.createForClassInitializer())
+              .setApiLevelForCode(computedApiLevel)
+              .setApiLevelForDefinition(computedApiLevel)
+              .setClassFileVersion(CfVersion.V1_6)
+              .setCode(
+                  new CfCode(
+                      clazz.getType(), maxStack, maxLocals, ImmutableList.of(new CfReturnVoid())))
+              .setMethod(dexItemFactory.createClassInitializer(clazz.getType()))
+              .build());
+    }
+    return clazz.getProgramClassInitializer();
+  }
+
+  private void instrumentClassInitializer(ProgramMethod classInitializer) {
+    Code code = classInitializer.getDefinition().getCode();
+    if (!code.isCfCode()) {
+      // Should generally not happen.
+      assert false;
+      return;
+    }
+
+    CfCode cfCode = code.asCfCode();
+    List<CfInstruction> instructions;
+    if (options.hasStartupInstrumentationTag()) {
+      instructions = new ArrayList<>(4 + cfCode.getInstructions().size());
+      instructions.add(
+          new CfConstString(dexItemFactory.createString(options.getStartupInstrumentationTag())));
+      instructions.add(new CfConstString(classInitializer.getHolderType().getDescriptor()));
+      instructions.add(
+          new CfInvoke(Opcodes.INVOKESTATIC, dexItemFactory.androidUtilLogMembers.i, false));
+      instructions.add(new CfStackInstruction(Opcode.Pop));
+    } else {
+      instructions = new ArrayList<>(3 + cfCode.getInstructions().size());
+      instructions.add(new CfStaticFieldRead(dexItemFactory.javaLangSystemMembers.out));
+      instructions.add(new CfConstString(classInitializer.getHolderType().getDescriptor()));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKEVIRTUAL,
+              dexItemFactory.javaIoPrintStreamMembers.printlnWithString,
+              false));
+    }
+    instructions.addAll(cfCode.getInstructions());
+    classInitializer.setCode(
+        new CfCode(
+            cfCode.getOriginalHolder(),
+            Math.max(cfCode.getMaxStack(), 2),
+            cfCode.getMaxLocals(),
+            instructions,
+            cfCode.getTryCatchRanges(),
+            cfCode.getLocalVariables(),
+            cfCode.getDiagnosticPosition(),
+            cfCode.getMetadata()),
+        appView);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
new file mode 100644
index 0000000..7fad730
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2022, 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.experimental.startup;
+
+import static com.android.tools.r8.utils.InternalOptions.getSystemPropertyForDevelopmentOrDefault;
+import static com.android.tools.r8.utils.InternalOptions.isSystemPropertyForDevelopmentSet;
+
+public class StartupOptions {
+
+  private boolean enableMinimalStartupDex =
+      isSystemPropertyForDevelopmentSet("com.android.tools.r8.startup.minimalstartupdex");
+  private boolean enableStartupCompletenessCheckForTesting =
+      isSystemPropertyForDevelopmentSet("com.android.tools.r8.startup.completenesscheck");
+  private boolean enableStartupInstrumentation =
+      isSystemPropertyForDevelopmentSet("com.android.tools.r8.startup.instrument");
+  private String startupInstrumentationTag =
+      getSystemPropertyForDevelopmentOrDefault(
+          "com.android.tools.r8.startup.instrumentationtag", null);
+
+  private StartupConfiguration startupConfiguration;
+
+  public boolean hasStartupInstrumentationTag() {
+    return startupInstrumentationTag != null;
+  }
+
+  public String getStartupInstrumentationTag() {
+    return startupInstrumentationTag;
+  }
+
+  public boolean isMinimalStartupDexEnabled() {
+    return enableMinimalStartupDex;
+  }
+
+  public StartupOptions setEnableMinimalStartupDex() {
+    enableMinimalStartupDex = true;
+    return this;
+  }
+
+  public boolean isStartupInstrumentationEnabled() {
+    return enableStartupInstrumentation;
+  }
+
+  public StartupOptions setEnableStartupInstrumentation() {
+    enableStartupInstrumentation = true;
+    return this;
+  }
+
+  public boolean isStartupCompletenessCheckForTesting() {
+    return enableStartupCompletenessCheckForTesting;
+  }
+
+  public StartupOptions setEnableStartupCompletenessCheckForTesting() {
+    enableStartupCompletenessCheckForTesting = true;
+    return this;
+  }
+
+  public boolean hasStartupConfiguration() {
+    return startupConfiguration != null;
+  }
+
+  public StartupConfiguration getStartupConfiguration() {
+    return startupConfiguration;
+  }
+
+  public void setStartupConfiguration(StartupConfiguration startupConfiguration) {
+    this.startupConfiguration = startupConfiguration;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 215b25a..82b0243 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -50,7 +50,7 @@
     return createInitialClassToFeatureSplitMap(
         options.dexItemFactory(),
         options.featureSplitConfiguration,
-        options.startupConfiguration,
+        options.getStartupOptions().getStartupConfiguration(),
         options.reporter);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 4c28ff0..81979c8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -532,17 +532,17 @@
    * may be abstract.
    */
   public DexClassAndMethod lookupMaximallySpecificMethod(DexClass clazz, DexMethod method) {
-    return new MethodResolution(this::definitionFor, dexItemFactory())
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
         .lookupMaximallySpecificTarget(clazz, method);
   }
 
   MethodResolutionResult resolveMaximallySpecificTarget(DexClass clazz, DexMethod method) {
-    return new MethodResolution(this::definitionFor, dexItemFactory())
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
         .resolveMaximallySpecificTarget(clazz, method);
   }
 
   MethodResolutionResult resolveMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
-    return new MethodResolution(this::definitionFor, dexItemFactory())
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
         .resolveMaximallySpecificTarget(lambda, method);
   }
 
@@ -648,7 +648,7 @@
    */
   public MethodResolutionResult unsafeResolveMethodDueToDexFormat(DexMethod method) {
     assert checkIfObsolete();
-    return new MethodResolution(this::definitionFor, dexItemFactory())
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
         .unsafeResolveMethodDueToDexFormat(method);
   }
 
@@ -698,7 +698,7 @@
   public MethodResolutionResult resolveMethodOnClass(
       DexType holder, DexProto proto, DexString name) {
     assert checkIfObsolete();
-    return new MethodResolution(this::definitionFor, dexItemFactory())
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
         .resolveMethodOnClass(holder, proto, name);
   }
 
@@ -715,7 +715,7 @@
   public MethodResolutionResult resolveMethodOnClass(
       DexClass clazz, DexProto proto, DexString name) {
     assert checkIfObsolete();
-    return new MethodResolution(this::definitionFor, dexItemFactory())
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
         .resolveMethodOnClass(clazz, proto, name);
   }
 
@@ -726,7 +726,7 @@
 
   public MethodResolutionResult resolveMethodOnInterface(DexType holder, DexMethod method) {
     assert checkIfObsolete();
-    return new MethodResolution(this::definitionFor, dexItemFactory())
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
         .resolveMethodOnInterface(holder, method.getProto(), method.getName());
   }
 
@@ -744,7 +744,7 @@
   public MethodResolutionResult resolveMethodOnInterface(
       DexClass clazz, DexProto proto, DexString name) {
     assert checkIfObsolete();
-    return new MethodResolution(this::definitionFor, dexItemFactory())
+    return MethodResolution.createLegacy(this::definitionFor, dexItemFactory())
         .resolveMethodOnInterface(clazz, proto, name);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 910d5f5..4974a5d 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -963,6 +963,16 @@
             isAssignablePredicate(appView),
             appView.dexItemFactory(),
             maxStack);
+    for (CfTryCatch tryCatchRange : tryCatchRanges) {
+      try {
+        builder.checkTryCatchRange(tryCatchRange);
+      } catch (CfCodeStackMapValidatingException ex) {
+        return reportStackMapError(
+            CfCodeStackMapValidatingException.invalidTryCatchRange(
+                method, tryCatchRange, ex.getMessage(), appView),
+            appView);
+      }
+    }
     if (stateMap.containsKey(null)) {
       assert !shouldComputeInitialFrame();
       builder.checkFrameAndSet(stateMap.get(null));
@@ -989,7 +999,7 @@
         instruction.evaluate(builder, previousMethodSignature, appView, appView.dexItemFactory());
       } catch (CfCodeStackMapValidatingException ex) {
         return reportStackMapError(
-            CfCodeStackMapValidatingException.toDiagnostics(
+            CfCodeStackMapValidatingException.invalidStackMapForInstruction(
                 method, i, instruction, ex.getMessage(), appView),
             appView);
       }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
index 1a25ce6..4b417bf 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.utils.StringUtils;
 
 public class CfCodeStackMapValidatingException extends RuntimeException {
 
@@ -52,7 +54,24 @@
         sb.toString());
   }
 
-  public static CfCodeDiagnostics toDiagnostics(
+  public static CfCodeDiagnostics invalidTryCatchRange(
+      ProgramMethod method, CfTryCatch tryCatch, String detailMessage, AppView<?> appView) {
+    StringBuilder sb =
+        new StringBuilder("Invalid try catch range for ")
+            .append(StringUtils.join(", ", tryCatch.guards, DexType::getTypeName))
+            .append(": ")
+            .append(detailMessage)
+            .append(".");
+    if (appView.enableWholeProgramOptimizations()) {
+      sb.append(" In later version of R8, the method may be assumed not reachable.");
+    }
+    return new CfCodeDiagnostics(
+        method.getOrigin(),
+        appView.graphLens().getOriginalMethodSignature(method.getReference()),
+        sb.toString());
+  }
+
+  public static CfCodeDiagnostics invalidStackMapForInstruction(
       ProgramMethod method,
       int instructionIndex,
       CfInstruction instruction,
diff --git a/src/main/java/com/android/tools/r8/graph/ClassResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ClassResolutionResult.java
index 4eca4e1..12baacf 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassResolutionResult.java
@@ -14,8 +14,17 @@
 
   DexClass toSingleClassWithProgramOverLibrary();
 
+  // The alternative class is:
+  // - the other class than the single class if the resolution resolves into multiple classes,
+  // - null if the resolution resolves into a single class.
+  DexClass toAlternativeClassWithProgramOverLibrary();
+
   void forEachClassResolutionResult(Consumer<DexClass> consumer);
 
+  default boolean isMultipleClassResolutionResult() {
+    return false;
+  }
+
   static Builder builder() {
     return new Builder();
   }
@@ -86,6 +95,11 @@
     }
 
     @Override
+    public DexClass toAlternativeClassWithProgramOverLibrary() {
+      return null;
+    }
+
+    @Override
     public void forEachClassResolutionResult(Consumer<DexClass> consumer) {
       // Intentionally empty
     }
@@ -112,6 +126,11 @@
       consumer.accept(programOrClasspathClass);
       consumer.accept(libraryClass);
     }
+
+    @Override
+    public boolean isMultipleClassResolutionResult() {
+      return true;
+    }
   }
 
   class ProgramAndLibraryClassResolutionResult
@@ -126,6 +145,11 @@
     public DexClass toSingleClassWithProgramOverLibrary() {
       return programOrClasspathClass;
     }
+
+    @Override
+    public DexClass toAlternativeClassWithProgramOverLibrary() {
+      return libraryClass;
+    }
   }
 
   class ClasspathAndLibraryClassResolutionResult
@@ -140,5 +164,10 @@
     public DexClass toSingleClassWithProgramOverLibrary() {
       return libraryClass;
     }
+
+    @Override
+    public DexClass toAlternativeClassWithProgramOverLibrary() {
+      return programOrClasspathClass;
+    }
   }
 }
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 4fac278..a615b1f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -150,6 +150,11 @@
     return this;
   }
 
+  @Override
+  public DexClass toAlternativeClassWithProgramOverLibrary() {
+    return null;
+  }
+
   public abstract void accept(
       Consumer<DexProgramClass> programClassConsumer,
       Consumer<DexClasspathClass> classpathClassConsumer,
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 98dac6d..7c0e5d2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -547,11 +547,13 @@
   public final ClassMethods classMethods = new ClassMethods();
   public final ConstructorMethods constructorMethods = new ConstructorMethods();
   public final EnumMembers enumMembers = new EnumMembers();
+  public final AndroidUtilLogMembers androidUtilLogMembers = new AndroidUtilLogMembers();
   public final JavaLangReflectArrayMembers javaLangReflectArrayMembers =
       new JavaLangReflectArrayMembers();
   public final JavaLangAnnotationRetentionPolicyMembers javaLangAnnotationRetentionPolicyMembers =
       new JavaLangAnnotationRetentionPolicyMembers();
-  public final JavaLangSystemMethods javaLangSystemMethods = new JavaLangSystemMethods();
+  public final JavaLangSystemMembers javaLangSystemMembers = new JavaLangSystemMembers();
+  public final JavaIoPrintStreamMembers javaIoPrintStreamMembers = new JavaIoPrintStreamMembers();
   public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods();
   public final IllegalArgumentExceptionMethods illegalArgumentExceptionMethods =
       new IllegalArgumentExceptionMethods();
@@ -1702,6 +1704,14 @@
     }
   }
 
+  public class AndroidUtilLogMembers {
+
+    public final DexMethod i =
+        createMethod(androidUtilLogType, createProto(intType, stringType, stringType), "i");
+
+    private AndroidUtilLogMembers() {}
+  }
+
   public class JavaLangAnnotationRetentionPolicyMembers {
 
     public final DexField CLASS =
@@ -1722,7 +1732,9 @@
     private JavaLangReflectArrayMembers() {}
   }
 
-  public class JavaLangSystemMethods {
+  public class JavaLangSystemMembers {
+
+    public final DexField out = createField(javaLangSystemType, javaIoPrintStreamType, "out");
 
     public final DexMethod arraycopy =
         createMethod(
@@ -1732,7 +1744,15 @@
     public final DexMethod identityHashCode =
         createMethod(javaLangSystemType, createProto(intType, objectType), identityHashCodeName);
 
-    private JavaLangSystemMethods() {}
+    private JavaLangSystemMembers() {}
+  }
+
+  public class JavaIoPrintStreamMembers {
+
+    public final DexMethod printlnWithString =
+        createMethod(javaIoPrintStreamType, createProto(voidType, stringType), "println");
+
+    private JavaIoPrintStreamMembers() {}
   }
 
   public class EnumMembers {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolution.java b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
index 2f6939e..0927de2 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolution.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.MethodResolutionResult.ArrayCloneMethodResult;
 import com.android.tools.r8.graph.MethodResolutionResult.ClassNotFoundResult;
 import com.android.tools.r8.graph.MethodResolutionResult.IllegalAccessOrNoSuchMethodResult;
@@ -27,15 +28,31 @@
  */
 public class MethodResolution {
 
-  private final Function<DexType, DexClass> definitionFor;
+  private final Function<DexType, ClassResolutionResult> definitionFor;
   private final DexItemFactory factory;
+  private final boolean escapeIfLibraryHasProgramSuperType;
 
-  public MethodResolution(Function<DexType, DexClass> definitionFor, DexItemFactory factory) {
+  private MethodResolution(
+      Function<DexType, ClassResolutionResult> definitionFor,
+      DexItemFactory factory,
+      boolean escapeIfLibraryHasProgramSuperType) {
     this.definitionFor = definitionFor;
     this.factory = factory;
+    this.escapeIfLibraryHasProgramSuperType = escapeIfLibraryHasProgramSuperType;
   }
 
-  private DexClass definitionFor(DexType type) {
+  public static MethodResolution createLegacy(
+      Function<DexType, DexClass> definitionFor, DexItemFactory factory) {
+    return new MethodResolution(
+        type -> {
+          DexClass clazz = definitionFor.apply(type);
+          return clazz == null ? ClassResolutionResult.NoResolutionResult.noResult() : clazz;
+        },
+        factory,
+        false);
+  }
+
+  private ClassResolutionResult definitionFor(DexType type) {
     return definitionFor.apply(type);
   }
 
@@ -53,14 +70,19 @@
     if (holder.isArrayType()) {
       return resolveMethodOnArray(holder, method.getProto(), method.getName());
     }
-    DexClass definition = definitionFor(holder);
-    if (definition == null) {
-      return ClassNotFoundResult.INSTANCE;
-    } else if (definition.isInterface()) {
-      return resolveMethodOnInterface(definition, method.getProto(), method.getName());
-    } else {
-      return resolveMethodOnClass(definition, method.getProto(), method.getName());
-    }
+    MethodResolutionResult.Builder builder = MethodResolutionResult.builder();
+    definitionFor(holder)
+        .forEachClassResolutionResult(
+            clazz -> {
+              if (clazz.isInterface()) {
+                builder.addResolutionResult(
+                    resolveMethodOnInterface(clazz, method.getProto(), method.getName()));
+              } else {
+                builder.addResolutionResult(
+                    resolveMethodOnClass(clazz, method.getProto(), method.getName()));
+              }
+            });
+    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE);
   }
 
   /**
@@ -96,15 +118,18 @@
     if (holder.isArrayType()) {
       return resolveMethodOnArray(holder, methodProto, methodName);
     }
-    DexClass clazz = definitionFor(holder);
-    if (clazz == null) {
-      return ClassNotFoundResult.INSTANCE;
-    }
-    // Step 1: If holder is an interface, resolution fails with an ICCE. We return null.
-    if (clazz.isInterface()) {
-      return IncompatibleClassResult.INSTANCE;
-    }
-    return resolveMethodOnClass(clazz, methodProto, methodName);
+    MethodResolutionResult.Builder builder = MethodResolutionResult.builder();
+    definitionFor(holder)
+        .forEachClassResolutionResult(
+            clazz -> {
+              // Step 1: If holder is an interface, resolution fails with an ICCE.
+              if (clazz.isInterface()) {
+                builder.addResolutionResult(IncompatibleClassResult.INSTANCE);
+              } else {
+                builder.addResolutionResult(resolveMethodOnClass(clazz, methodProto, methodName));
+              }
+            });
+    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE);
   }
 
   public MethodResolutionResult resolveMethodOnClass(
@@ -135,7 +160,8 @@
     // Section 2.9 of the JVM Spec</a>.
     DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(methodName, factory);
     if (result != null) {
-      return new SingleResolutionResult(initialResolutionHolder, clazz, result);
+      return MethodResolutionResult.createSingleResolutionResult(
+          initialResolutionHolder, clazz, result);
     }
     // Pt 2: Find a method that matches the descriptor.
     result = clazz.lookupMethod(methodProto, methodName);
@@ -148,17 +174,27 @@
       if (result.isPrivateMethod() && clazz != initialResolutionHolder) {
         return new IllegalAccessOrNoSuchMethodResult(initialResolutionHolder, result);
       }
-      return new SingleResolutionResult(initialResolutionHolder, clazz, result);
+      return MethodResolutionResult.createSingleResolutionResult(
+          initialResolutionHolder, clazz, result);
     }
     // Pt 3: Apply step two to direct superclass of holder.
+    MethodResolutionResult.Builder builder = MethodResolutionResult.builder();
     if (clazz.superType != null) {
-      DexClass superClass = definitionFor(clazz.superType);
-      if (superClass != null) {
-        return resolveMethodOnClassStep2(
-            superClass, methodProto, methodName, initialResolutionHolder);
-      }
+      definitionFor(clazz.superType)
+          .forEachClassResolutionResult(
+              superClass -> {
+                // Guard against going back into the program for resolution.
+                if (escapeIfLibraryHasProgramSuperType
+                    && clazz.isLibraryClass()
+                    && !superClass.isLibraryClass()) {
+                  return;
+                }
+                builder.addResolutionResult(
+                    resolveMethodOnClassStep2(
+                        superClass, methodProto, methodName, initialResolutionHolder));
+              });
     }
-    return null;
+    return builder.buildOrIfEmpty(null);
   }
 
   /**
@@ -201,7 +237,7 @@
     MaximallySpecificMethodsBuilder builder =
         new MaximallySpecificMethodsBuilder(definitionFor, factory);
     resolveMethodStep3Helper(
-        method.getProto(), method.getName(), factory.objectType, lambda.interfaces, builder);
+        method.getProto(), method.getName(), null, builder, factory.objectType, lambda.interfaces);
     return builder;
   }
 
@@ -212,37 +248,57 @@
       DexClass clazz,
       MaximallySpecificMethodsBuilder builder) {
     resolveMethodStep3Helper(
-        methodProto, methodName, clazz.superType, Arrays.asList(clazz.interfaces.values), builder);
+        methodProto,
+        methodName,
+        clazz,
+        builder,
+        clazz.superType,
+        Arrays.asList(clazz.interfaces.values));
   }
 
   private void resolveMethodStep3Helper(
       DexProto methodProto,
       DexString methodName,
+      DexClass clazz,
+      MaximallySpecificMethodsBuilder builder,
       DexType superType,
-      List<DexType> interfaces,
-      MaximallySpecificMethodsBuilder builder) {
+      List<DexType> interfaces) {
     for (DexType iface : interfaces) {
-      DexClass definition = definitionFor(iface);
-      if (definition == null) {
-        // Ignore missing interface definitions.
-        continue;
+      ClassResolutionResult classResolutionResult = definitionFor(iface);
+      if (classResolutionResult.isMultipleClassResolutionResult()) {
+        // TODO(b/214382176, b/226170842): Compute maximal specific set in precense of
+        //   multiple results.
+        throw new Unreachable(
+            "MethodResolution should not be passed definition with multiple results");
       }
-      assert definition.isInterface();
-      DexEncodedMethod result = definition.lookupMethod(methodProto, methodName);
-      if (isMaximallySpecificCandidate(result)) {
-        // The candidate is added and doing so will prohibit shadowed methods from being in the set.
-        builder.addCandidate(definition, result);
-      } else {
-        // Look at the super-interfaces of this class and keep searching.
-        resolveMethodStep3Helper(methodProto, methodName, definition, builder);
-      }
+      classResolutionResult.forEachClassResolutionResult(
+          definition -> {
+            assert definition.isInterface();
+            DexEncodedMethod result = definition.lookupMethod(methodProto, methodName);
+            if (isMaximallySpecificCandidate(result)) {
+              // The candidate is added and doing so will prohibit shadowed methods from being
+              // in the set.
+              builder.addCandidate(definition, result);
+            } else {
+              // Look at the super-interfaces of this class and keep searching.
+              resolveMethodStep3Helper(methodProto, methodName, definition, builder);
+            }
+          });
     }
     // Now look at indirect super interfaces.
     if (superType != null) {
-      DexClass superClass = definitionFor(superType);
-      if (superClass != null) {
-        resolveMethodStep3Helper(methodProto, methodName, superClass, builder);
-      }
+      definitionFor(superType)
+          .forEachClassResolutionResult(
+              superClass -> {
+                // Guard against going back into the program for resolution.
+                if (escapeIfLibraryHasProgramSuperType
+                    && clazz != null
+                    && clazz.isLibraryClass()
+                    && !superClass.isLibraryClass()) {
+                  return;
+                }
+                resolveMethodStep3Helper(methodProto, methodName, superClass, builder);
+              });
     }
   }
 
@@ -271,17 +327,20 @@
     if (holder.isArrayType()) {
       return IncompatibleClassResult.INSTANCE;
     }
+    MethodResolutionResult.Builder builder = MethodResolutionResult.builder();
     // Step 1: Lookup interface.
-    DexClass definition = definitionFor(holder);
-    // If the definition is not an interface, resolution fails with an ICCE. We just return the
-    // empty result here.
-    if (definition == null) {
-      return ClassNotFoundResult.INSTANCE;
-    }
-    if (!definition.isInterface()) {
-      return IncompatibleClassResult.INSTANCE;
-    }
-    return resolveMethodOnInterface(definition, proto, methodName);
+    definitionFor(holder)
+        .forEachClassResolutionResult(
+            definition -> {
+              // If the definition is not an interface, resolution fails with an ICCE.
+              if (!definition.isInterface()) {
+                builder.addResolutionResult(IncompatibleClassResult.INSTANCE);
+              } else {
+                builder.addResolutionResult(
+                    resolveMethodOnInterface(definition, proto, methodName));
+              }
+            });
+    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE);
   }
 
   public MethodResolutionResult resolveMethodOnInterface(
@@ -290,20 +349,28 @@
     // Step 2: Look for exact method on interface.
     DexEncodedMethod result = definition.lookupMethod(methodProto, methodName);
     if (result != null) {
-      return new SingleResolutionResult(definition, definition, result);
+      return MethodResolutionResult.createSingleResolutionResult(definition, definition, result);
     }
     // Step 3: Look for matching method on object class.
-    DexClass objectClass = definitionFor(factory.objectType);
-    if (objectClass == null) {
-      return ClassNotFoundResult.INSTANCE;
-    }
-    result = objectClass.lookupMethod(methodProto, methodName);
-    if (result != null && result.accessFlags.isPublic() && !result.accessFlags.isAbstract()) {
-      return new SingleResolutionResult(definition, objectClass, result);
-    }
-    // Step 3: Look for maximally-specific superinterface methods or any interface definition.
-    //         This is the same for classes and interfaces.
-    return resolveMethodStep3(definition, methodProto, methodName);
+    MethodResolutionResult.Builder builder = MethodResolutionResult.builder();
+    definitionFor(factory.objectType)
+        .forEachClassResolutionResult(
+            objectClass -> {
+              DexEncodedMethod objectResult = objectClass.lookupMethod(methodProto, methodName);
+              if (objectResult != null
+                  && objectResult.accessFlags.isPublic()
+                  && !objectResult.accessFlags.isAbstract()) {
+                builder.addResolutionResult(
+                    MethodResolutionResult.createSingleResolutionResult(
+                        definition, objectClass, objectResult));
+              } else {
+                // Step 3: Look for maximally-specific superinterface methods or any interface
+                // definition. This is the same for classes and interfaces.
+                builder.addResolutionResult(
+                    resolveMethodStep3(definition, methodProto, methodName));
+              }
+            });
+    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE);
   }
 
   static class MaximallySpecificMethodsBuilder {
@@ -316,11 +383,11 @@
     // prior to writing.
     private final LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods =
         new LinkedHashMap<>();
-    private final Function<DexType, DexClass> definitionFor;
+    private final Function<DexType, ClassResolutionResult> definitionFor;
     private final DexItemFactory factory;
 
     private MaximallySpecificMethodsBuilder(
-        Function<DexType, DexClass> definitionFor, DexItemFactory factory) {
+        Function<DexType, ClassResolutionResult> definitionFor, DexItemFactory factory) {
       this.definitionFor = definitionFor;
       this.factory = factory;
     }
@@ -343,22 +410,29 @@
       if (type == null) {
         return;
       }
-      DexClass clazz = definitionFor.apply(type);
-      if (clazz == null) {
-        return;
+      ClassResolutionResult classResolutionResult = definitionFor.apply(type);
+      if (classResolutionResult.isMultipleClassResolutionResult()) {
+        // TODO(b/214382176, b/226170842): Compute maximal specific set in precense of
+        //   multiple results.
+        throw new Unreachable(
+            "MethodResolution should not be passed definition with multiple results");
       }
-      assert clazz.isInterface();
-      assert clazz.superType == factory.objectType;
-      // A null entry signifies that the candidate is shadowed blocking future candidates.
-      // If the candidate is already shadowed at this type there is no need to shadow further up.
-      if (maximallySpecificMethods.containsKey(clazz)
-          && maximallySpecificMethods.get(clazz) == null) {
-        return;
-      }
-      maximallySpecificMethods.put(clazz, null);
-      for (DexType iface : clazz.interfaces.values) {
-        markShadowed(iface);
-      }
+      classResolutionResult.forEachClassResolutionResult(
+          clazz -> {
+            assert clazz.isInterface();
+            assert clazz.superType == factory.objectType;
+            // A null entry signifies that the candidate is shadowed blocking future candidates.
+            // If the candidate is already shadowed at this type there is no need to shadow
+            // further up.
+            if (maximallySpecificMethods.containsKey(clazz)
+                && maximallySpecificMethods.get(clazz) == null) {
+              return;
+            }
+            maximallySpecificMethods.put(clazz, null);
+            for (DexType iface : clazz.interfaces.values) {
+              markShadowed(iface);
+            }
+          });
     }
 
     DexClassAndMethod lookup() {
@@ -407,9 +481,9 @@
       return IncompatibleClassResult.create(ListUtils.map(nonAbstractMethods, Entry::getValue));
     }
 
-    private static SingleResolutionResult singleResultHelper(
+    private static SingleResolutionResult<?> singleResultHelper(
         DexClass initialResolutionResult, Entry<DexClass, DexEncodedMethod> entry) {
-      return new SingleResolutionResult(
+      return MethodResolutionResult.createSingleResolutionResult(
           initialResolutionResult != null ? initialResolutionResult : entry.getKey(),
           entry.getKey(),
           entry.getValue());
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 49860a7..65ff279 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -12,9 +13,13 @@
 import com.android.tools.r8.shaking.InstantiatedObject;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
@@ -43,7 +48,7 @@
   }
 
   /** Returns non-null if isSingleResolution() is true, otherwise null. */
-  public SingleResolutionResult asSingleResolution() {
+  public SingleResolutionResult<?> asSingleResolution() {
     return null;
   }
 
@@ -159,17 +164,49 @@
       AppInfoWithClassHierarchy appInfo,
       Consumer<? super DexEncodedMethod> methodCausingFailureConsumer);
 
+  public abstract void visitMethodResolutionResults(
+      Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+      Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
+      Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
+      Consumer<? super FailedResolutionResult> failedResolutionConsumer);
+
+  public boolean hasProgramResult() {
+    return false;
+  }
+
+  public SingleClasspathResolutionResult asSingleClasspathResolutionResult() {
+    return null;
+  }
+
+  protected SingleProgramResolutionResult asSingleProgramResolutionResult() {
+    return null;
+  }
+
+  public static SingleResolutionResult<?> createSingleResolutionResult(
+      DexClass initialResolutionHolder, DexClass holder, DexEncodedMethod definition) {
+    if (holder.isLibraryClass()) {
+      return new SingleLibraryResolutionResult(
+          initialResolutionHolder, holder.asLibraryClass(), definition);
+    } else if (holder.isClasspathClass()) {
+      return new SingleClasspathResolutionResult(
+          initialResolutionHolder, holder.asClasspathClass(), definition);
+    } else {
+      assert holder.isProgramClass();
+      return new SingleProgramResolutionResult(
+          initialResolutionHolder, holder.asProgramClass(), definition);
+    }
+  }
+
   /** Result for a resolution that succeeds with a known declaration/definition. */
-  public static class SingleResolutionResult extends MethodResolutionResult
+  public abstract static class SingleResolutionResult<T extends DexClass>
+      extends MethodResolutionResult
       implements SuccessfulMemberResolutionResult<DexEncodedMethod, DexMethod> {
     private final DexClass initialResolutionHolder;
-    private final DexClass resolvedHolder;
+    private final T resolvedHolder;
     private final DexEncodedMethod resolvedMethod;
 
     public SingleResolutionResult(
-        DexClass initialResolutionHolder,
-        DexClass resolvedHolder,
-        DexEncodedMethod resolvedMethod) {
+        DexClass initialResolutionHolder, T resolvedHolder, DexEncodedMethod resolvedMethod) {
       assert initialResolutionHolder != null;
       assert resolvedHolder != null;
       assert resolvedMethod != null;
@@ -181,11 +218,8 @@
           || initialResolutionHolder.type == resolvedMethod.getHolderType();
     }
 
-    public SingleResolutionResult withInitialResolutionHolder(DexClass newInitialResolutionHolder) {
-      return newInitialResolutionHolder != initialResolutionHolder
-          ? new SingleResolutionResult(newInitialResolutionHolder, resolvedHolder, resolvedMethod)
-          : this;
-    }
+    public abstract SingleResolutionResult<T> withInitialResolutionHolder(
+        DexClass newInitialResolutionHolder);
 
     @Override
     public DexClass getInitialResolutionHolder() {
@@ -193,7 +227,7 @@
     }
 
     @Override
-    public DexClass getResolvedHolder() {
+    public T getResolvedHolder() {
       return resolvedHolder;
     }
 
@@ -225,7 +259,7 @@
     }
 
     @Override
-    public SingleResolutionResult asSingleResolution() {
+    public SingleResolutionResult<?> asSingleResolution() {
       return this;
     }
 
@@ -778,6 +812,108 @@
     }
   }
 
+  public static class SingleProgramResolutionResult
+      extends SingleResolutionResult<DexProgramClass> {
+
+    public SingleProgramResolutionResult(
+        DexClass initialResolutionHolder,
+        DexProgramClass resolvedHolder,
+        DexEncodedMethod resolvedMethod) {
+      super(initialResolutionHolder, resolvedHolder, resolvedMethod);
+    }
+
+    @Override
+    public SingleResolutionResult<DexProgramClass> withInitialResolutionHolder(
+        DexClass newInitialResolutionHolder) {
+      return newInitialResolutionHolder != getInitialResolutionHolder()
+          ? new SingleProgramResolutionResult(
+              newInitialResolutionHolder, getResolvedHolder(), getResolvedMethod())
+          : this;
+    }
+
+    @Override
+    public void visitMethodResolutionResults(
+        Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+        Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
+        Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
+        Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
+      programOrClasspathConsumer.accept(this);
+    }
+
+    @Override
+    public boolean hasProgramResult() {
+      return true;
+    }
+
+    @Override
+    protected SingleProgramResolutionResult asSingleProgramResolutionResult() {
+      return this;
+    }
+  }
+
+  public static class SingleClasspathResolutionResult
+      extends SingleResolutionResult<DexClasspathClass> {
+
+    public SingleClasspathResolutionResult(
+        DexClass initialResolutionHolder,
+        DexClasspathClass resolvedHolder,
+        DexEncodedMethod resolvedMethod) {
+      super(initialResolutionHolder, resolvedHolder, resolvedMethod);
+    }
+
+    @Override
+    public SingleClasspathResolutionResult withInitialResolutionHolder(
+        DexClass newInitialResolutionHolder) {
+      return newInitialResolutionHolder != getInitialResolutionHolder()
+          ? new SingleClasspathResolutionResult(
+              newInitialResolutionHolder, getResolvedHolder(), getResolvedMethod())
+          : this;
+    }
+
+    @Override
+    public void visitMethodResolutionResults(
+        Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+        Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
+        Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
+        Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
+      programOrClasspathConsumer.accept(this);
+    }
+
+    @Override
+    public SingleClasspathResolutionResult asSingleClasspathResolutionResult() {
+      return this;
+    }
+  }
+
+  public static class SingleLibraryResolutionResult
+      extends SingleResolutionResult<DexLibraryClass> {
+
+    public SingleLibraryResolutionResult(
+        DexClass initialResolutionHolder,
+        DexLibraryClass resolvedHolder,
+        DexEncodedMethod resolvedMethod) {
+      super(initialResolutionHolder, resolvedHolder, resolvedMethod);
+    }
+
+    @Override
+    public SingleLibraryResolutionResult withInitialResolutionHolder(
+        DexClass newInitialResolutionHolder) {
+      return newInitialResolutionHolder != getInitialResolutionHolder()
+          ? new SingleLibraryResolutionResult(
+              newInitialResolutionHolder, getResolvedHolder(), getResolvedMethod())
+          : this;
+    }
+
+    @Override
+    public void visitMethodResolutionResults(
+        Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+        Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
+        Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
+        Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
+      libraryResultConsumer.accept(this);
+    }
+  }
+
   abstract static class EmptyResult extends MethodResolutionResult {
 
     @Override
@@ -870,6 +1006,15 @@
     }
 
     @Override
+    public void visitMethodResolutionResults(
+        Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+        Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
+        Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
+        Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
+      cloneResultConsumer.accept(this);
+    }
+
+    @Override
     public boolean isArrayCloneMethodResult() {
       return true;
     }
@@ -913,6 +1058,15 @@
     public boolean hasMethodsCausingError() {
       return false;
     }
+
+    @Override
+    public void visitMethodResolutionResults(
+        Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+        Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
+        Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
+        Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
+      failedResolutionConsumer.accept(this);
+    }
   }
 
   public static class ClassNotFoundResult extends FailedResolutionResult {
@@ -1038,4 +1192,230 @@
     }
   }
 
+  public abstract static class MultipleMethodResolutionResult<
+          C extends DexClass & ProgramOrClasspathClass, T extends SingleResolutionResult<C>>
+      extends MethodResolutionResult {
+
+    protected final T programOrClasspathResult;
+    protected final List<SingleLibraryResolutionResult> libraryResolutionResults;
+    protected final List<FailedResolutionResult> failedResolutionResults;
+
+    public MultipleMethodResolutionResult(
+        T programOrClasspathResult,
+        List<SingleLibraryResolutionResult> libraryResolutionResults,
+        List<FailedResolutionResult> failedResolutionResults) {
+      this.programOrClasspathResult = programOrClasspathResult;
+      this.libraryResolutionResults = libraryResolutionResults;
+      this.failedResolutionResults = failedResolutionResults;
+    }
+
+    @Override
+    public OptionalBool isAccessibleFrom(
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public OptionalBool isAccessibleForVirtualDispatchFrom(
+        ProgramDefinition context, AppInfoWithClassHierarchy appInfo) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public boolean isVirtualTarget() {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public DexClassAndMethod lookupInvokeSpecialTarget(
+        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public DexClassAndMethod lookupInvokeSuperTarget(
+        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public DexEncodedMethod lookupInvokeDirectTarget(
+        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public DexEncodedMethod lookupInvokeStaticTarget(
+        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public LookupResult lookupVirtualDispatchTargets(
+        DexProgramClass context,
+        AppInfoWithClassHierarchy appInfo,
+        InstantiatedSubTypeInfo instantiatedInfo,
+        PinnedPredicate pinnedPredicate) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public LookupResult lookupVirtualDispatchTargets(
+        DexProgramClass context,
+        AppInfoWithLiveness appInfo,
+        DexProgramClass refinedReceiverUpperBound,
+        DexProgramClass refinedReceiverLowerBound) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public LookupTarget lookupVirtualDispatchTarget(
+        InstantiatedObject instance, AppInfoWithClassHierarchy appInfo) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public DexClassAndMethod lookupVirtualDispatchTarget(
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public LookupTarget lookupVirtualDispatchTarget(
+        LambdaDescriptor lambdaInstance,
+        AppInfoWithClassHierarchy appInfo,
+        Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
+      throw new Unreachable("Should not be called on MultipleFieldResolutionResult");
+    }
+
+    @Override
+    public void visitMethodResolutionResults(
+        Consumer<? super SingleResolutionResult<?>> programOrClasspathConsumer,
+        Consumer<? super SingleLibraryResolutionResult> libraryResultConsumer,
+        Consumer<? super ArrayCloneMethodResult> cloneResultConsumer,
+        Consumer<? super FailedResolutionResult> failedResolutionConsumer) {
+      if (programOrClasspathResult != null) {
+        programOrClasspathConsumer.accept(programOrClasspathResult);
+      }
+      libraryResolutionResults.forEach(libraryResultConsumer);
+      failedResolutionResults.forEach(failedResolutionConsumer);
+    }
+  }
+
+  public static class MultipleProgramWithLibraryResolutionResult
+      extends MultipleMethodResolutionResult<DexProgramClass, SingleProgramResolutionResult> {
+
+    public MultipleProgramWithLibraryResolutionResult(
+        SingleProgramResolutionResult programOrClasspathResult,
+        List<SingleLibraryResolutionResult> libraryResolutionResults,
+        List<FailedResolutionResult> failedOrUnknownResolutionResults) {
+      super(programOrClasspathResult, libraryResolutionResults, failedOrUnknownResolutionResults);
+    }
+  }
+
+  public static class MultipleClasspathWithLibraryResolutionResult
+      extends MultipleMethodResolutionResult<DexClasspathClass, SingleClasspathResolutionResult> {
+
+    public MultipleClasspathWithLibraryResolutionResult(
+        SingleClasspathResolutionResult programOrClasspathResult,
+        List<SingleLibraryResolutionResult> libraryResolutionResults,
+        List<FailedResolutionResult> failedOrUnknownResolutionResults) {
+      super(programOrClasspathResult, libraryResolutionResults, failedOrUnknownResolutionResults);
+    }
+  }
+
+  public static class MultipleLibraryMethodResolutionResult
+      extends MultipleMethodResolutionResult<DexProgramClass, SingleProgramResolutionResult> {
+
+    public MultipleLibraryMethodResolutionResult(
+        List<SingleLibraryResolutionResult> libraryResolutionResults,
+        List<FailedResolutionResult> failedOrUnknownResolutionResults) {
+      super(null, libraryResolutionResults, failedOrUnknownResolutionResults);
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+
+    private MethodResolutionResult possiblySingleResult = null;
+    private List<MethodResolutionResult> allResults = null;
+
+    private Builder() {}
+
+    public void addResolutionResult(MethodResolutionResult result) {
+      if (possiblySingleResult == null) {
+        possiblySingleResult = result;
+        return;
+      }
+      if (allResults == null) {
+        allResults = new ArrayList<>();
+        allResults.add(possiblySingleResult);
+      }
+      allResults.add(result);
+    }
+
+    public MethodResolutionResult buildOrIfEmpty(MethodResolutionResult emptyResult) {
+      if (possiblySingleResult == null) {
+        return emptyResult;
+      } else if (allResults == null) {
+        return possiblySingleResult;
+      }
+      Box<SingleResolutionResult<?>> singleResult = new Box<>();
+      List<SingleLibraryResolutionResult> libraryResults = new ArrayList<>();
+      List<FailedResolutionResult> failedResults = new ArrayList<>();
+      allResults.forEach(
+          otherResult -> {
+            otherResult.visitMethodResolutionResults(
+                otherProgramOrClasspathResult -> {
+                  if (singleResult.isSet()) {
+                    assert false : "Unexpected multiple results between program and classpath";
+                    if (singleResult.get().hasProgramResult()) {
+                      return;
+                    }
+                  }
+                  singleResult.set(otherProgramOrClasspathResult);
+                },
+                newLibraryResult -> {
+                  if (!Iterables.any(
+                      libraryResults,
+                      existing ->
+                          existing.getResolvedHolder() == newLibraryResult.getResolvedHolder())) {
+                    libraryResults.add(newLibraryResult);
+                  }
+                },
+                ConsumerUtils.emptyConsumer(),
+                newFailedResult -> {
+                  if (!Iterables.any(
+                      failedResults,
+                      existing ->
+                          existing.isFailedResolution() == newFailedResult.isFailedResolution())) {
+                    failedResults.add(newFailedResult);
+                  }
+                });
+          });
+      if (!singleResult.isSet()) {
+        if (libraryResults.size() == 1 && failedResults.isEmpty()) {
+          return libraryResults.get(0);
+        } else if (libraryResults.isEmpty() && failedResults.size() == 1) {
+          return failedResults.get(0);
+        } else {
+          return new MultipleLibraryMethodResolutionResult(libraryResults, failedResults);
+        }
+      } else if (libraryResults.isEmpty() && failedResults.isEmpty()) {
+        return singleResult.get();
+      } else if (singleResult.get().hasProgramResult()) {
+        return new MultipleProgramWithLibraryResolutionResult(
+            singleResult.get().asSingleProgramResolutionResult(), libraryResults, failedResults);
+      } else {
+        SingleClasspathResolutionResult classpathResult =
+            singleResult.get().asSingleClasspathResolutionResult();
+        assert classpathResult != null;
+        return new MultipleClasspathWithLibraryResolutionResult(
+            classpathResult, libraryResults, failedResults);
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
index b61f288..f303854 100644
--- a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
@@ -234,6 +234,10 @@
     return ImmutableList.of();
   }
 
+  public Set<DexType> allImmediateSubtypes(DexType type) {
+    return getTypeInfo(type).directSubtypes;
+  }
+
   public void forAllInterfaceRoots(Consumer<DexType> fn) {
     Iterables.filter(
             getTypeInfo(factory.objectType).directSubtypes,
@@ -245,12 +249,13 @@
 
     private final DexType type;
 
-    int hierarchyLevel = UNKNOWN_LEVEL;
+    private int hierarchyLevel = UNKNOWN_LEVEL;
+
     /**
      * Set of direct subtypes. This set has to remain sorted to ensure determinism. The actual
      * sorting is not important but {@link DexType#compareTo(StructuralItem)} works well.
      */
-    Set<DexType> directSubtypes = NO_DIRECT_SUBTYPE;
+    private Set<DexType> directSubtypes = NO_DIRECT_SUBTYPE;
 
     TypeInfo(DexType type) {
       this.type = type;
@@ -296,32 +301,28 @@
       }
     }
 
-    synchronized void addDirectSubtype(TypeInfo subtypeInfo) {
+    private void addDirectSubtype(TypeInfo subtypeInfo) {
       assert hierarchyLevel != UNKNOWN_LEVEL;
       ensureDirectSubTypeSet();
       directSubtypes.add(subtypeInfo.type);
       subtypeInfo.setLevel(hierarchyLevel + 1);
     }
 
-    void tagAsSubtypeRoot() {
+    private void tagAsSubtypeRoot() {
       setLevel(ROOT_LEVEL);
     }
 
-    void tagAsInterface() {
+    private void tagAsInterface() {
       setLevel(INTERFACE_LEVEL);
     }
 
-    public boolean isInterface() {
+    private boolean isInterface() {
       assert hierarchyLevel != UNKNOWN_LEVEL : "Program class missing: " + this;
       assert type.isClassType();
       return hierarchyLevel == INTERFACE_LEVEL;
     }
 
-    public boolean isUnknown() {
-      return hierarchyLevel == UNKNOWN_LEVEL;
-    }
-
-    synchronized void addInterfaceSubtype(DexType type) {
+    private void addInterfaceSubtype(DexType type) {
       // Interfaces all inherit from java.lang.Object. However, we assign a special level to
       // identify them later on.
       setLevel(INTERFACE_LEVEL);
@@ -329,21 +330,4 @@
       directSubtypes.add(type);
     }
   }
-
-  public Set<DexType> allImmediateSubtypes(DexType type) {
-    return getTypeInfo(type).directSubtypes;
-  }
-
-  public boolean isUnknown(DexType type) {
-    return getTypeInfo(type).isUnknown();
-  }
-
-  public boolean hasSubtypes(DexType type) {
-    return !getTypeInfo(type).directSubtypes.isEmpty();
-  }
-
-  void registerNewType(DexType newType, DexType superType) {
-    // Register the relationship between this type and its superType.
-    getTypeInfo(superType).addDirectSubtype(getTypeInfo(newType));
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 2ff1bfb..eb5b57e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -44,7 +44,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoVirtualMethodMerging;
 import com.android.tools.r8.horizontalclassmerging.policies.NoWeakerAccessPrivileges;
 import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
-import com.android.tools.r8.horizontalclassmerging.policies.OnlyClassesWithStaticDefinitions;
+import com.android.tools.r8.horizontalclassmerging.policies.OnlyClassesWithStaticDefinitionsAndNoClassInitializer;
 import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
 import com.android.tools.r8.horizontalclassmerging.policies.PreventClassMethodAndDefaultMethodCollisions;
@@ -137,7 +137,7 @@
     ImmutableList.Builder<SingleClassPolicy> builder =
         ImmutableList.<SingleClassPolicy>builder()
             .add(new CheckSyntheticClasses(appView))
-            .add(new OnlyClassesWithStaticDefinitions());
+            .add(new OnlyClassesWithStaticDefinitionsAndNoClassInitializer());
     assert verifySingleClassPoliciesIrrelevantForMergingSyntheticsInD8(appView, mode, builder);
     return builder.build();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 1103c8f..6c8703b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -57,7 +57,7 @@
     private DexMethod superMethod(
         AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
       DexMethod template = methods.iterator().next().getReference();
-      SingleResolutionResult resolutionResult =
+      SingleResolutionResult<?> resolutionResult =
           appView
               .appInfo()
               .resolveMethodOnClass(group.getSuperType(), template)
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
index a914cf9..fecb888 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
@@ -105,7 +105,7 @@
   }
 
   private boolean hasNonAbstractDefinitionInSuperClass(DexType superType, ProgramMethod method) {
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
             .resolveMethodOnClass(superType, method.getReference())
@@ -118,7 +118,7 @@
     return Iterables.any(
         interfaceTypes,
         interfaceType -> {
-          SingleResolutionResult resolutionResult =
+          SingleResolutionResult<?> resolutionResult =
               appView
                   .appInfo()
                   .resolveMethodOnInterface(interfaceType, method.getReference())
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitionsAndNoClassInitializer.java
similarity index 81%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitions.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitionsAndNoClassInitializer.java
index 736ede0..8b6334a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitionsAndNoClassInitializer.java
@@ -9,10 +9,13 @@
 import com.google.common.collect.Iterables;
 
 /** Prevent merging of classes that has non-static methods or fields. */
-public class OnlyClassesWithStaticDefinitions extends SingleClassPolicy {
+public class OnlyClassesWithStaticDefinitionsAndNoClassInitializer extends SingleClassPolicy {
 
   @Override
   public boolean canMerge(DexProgramClass program) {
+    if (program.hasClassInitializer()) {
+      return false;
+    }
     return !Iterables.any(program.members(), member -> !member.isStatic());
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
index 54bcf57..ca52f7c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
@@ -170,7 +170,7 @@
         MethodCategory category = MethodCategory.CLASS_HIERARCHY_SAFE;
         if (clazzReserved.contains(signature)) {
           DexMethod template = signature.withHolder(clazz, appView.dexItemFactory());
-          SingleResolutionResult result =
+          SingleResolutionResult<?> result =
               appView.appInfo().resolveMethodOnClass(clazz, template).asSingleResolution();
           if (result == null || result.getResolvedHolder().isInterface()) {
             category = MethodCategory.KEEP_ABSENT;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index d6ca793..d2b1d3b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -331,8 +331,8 @@
 
       if (definition.isStatic() != isStatic
           || appView.isCfByteCodePassThrough(getContext().getDefinition())
-          || resolutionResult.isAccessibleFrom(getContext(), appView).isPossiblyFalse()
           || !resolutionResult.isSingleProgramFieldResolutionResult()
+          || resolutionResult.isAccessibleFrom(getContext(), appView).isPossiblyFalse()
           || appView.appInfo().isNeverReprocessMethod(getContext())) {
         recordAccessThatCannotBeOptimized(field, definition);
         return;
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 f7a77af..b9f07e3 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
@@ -799,11 +799,6 @@
     BasicBlock invokePredecessor = invokeBlock.getPredecessors().get(0);
     BasicBlock invokeSuccessor = invokeBlock.getSuccessors().get(0);
 
-    // Invalidate position-on-throwing-instructions property if it does not hold for the inlinee.
-    if (!inlinee.doAllThrowingInstructionsHavePositions()) {
-      code.setAllThrowingInstructionsHavePositions(false);
-    }
-
     Set<Value> argumentUsers = Sets.newIdentityHashSet();
 
     // Map all argument values. The first one needs special handling if there is a downcast type.
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 1544bdd..53fa359 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -125,10 +125,6 @@
   private boolean numbered = false;
   private int nextInstructionNumber = 0;
 
-  // Initial value indicating if the code does have actual positions on all throwing instructions.
-  // If this is the case, which holds for javac code, then we want to ensure that it remains so.
-  private boolean allThrowingInstructionsHavePositions;
-
   private final IRMetadata metadata;
   private final InternalOptions options;
 
@@ -154,8 +150,6 @@
     this.basicBlockNumberGenerator = basicBlockNumberGenerator;
     this.metadata = metadata;
     this.origin = origin;
-    // TODO(zerny): Remove or update this property now that all instructions have positions.
-    allThrowingInstructionsHavePositions = computeAllThrowingInstructionsHavePositions();
   }
 
   public IRMetadata metadata() {
@@ -642,7 +636,7 @@
     assert consistentCatchHandlers();
     assert consistentBlockInstructions(appView, ssa);
     assert consistentMetadata();
-    assert !allThrowingInstructionsHavePositions || computeAllThrowingInstructionsHavePositions();
+    assert verifyAllThrowingInstructionsHavePositions();
     return true;
   }
 
@@ -1233,15 +1227,7 @@
     return createNumberConstant(0, TypeElement.getNull(), local);
   }
 
-  public boolean doAllThrowingInstructionsHavePositions() {
-    return allThrowingInstructionsHavePositions;
-  }
-
-  public void setAllThrowingInstructionsHavePositions(boolean value) {
-    this.allThrowingInstructionsHavePositions = value;
-  }
-
-  private boolean computeAllThrowingInstructionsHavePositions() {
+  private boolean verifyAllThrowingInstructionsHavePositions() {
     for (Instruction instruction : instructions()) {
       if (instruction.instructionTypeCanThrow()
           && !instruction.isConstString()
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index cdf992b..c13800c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -197,7 +197,7 @@
     AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
         appView.withClassHierarchy();
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appViewWithClassHierarchy
             .appInfo()
             .resolveMethod(getInvokedMethod(), getInterfaceBit())
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 7aa20ad..f39baa4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -196,7 +196,7 @@
     AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
     AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appViewWithLiveness
             .appInfo()
             .resolveMethod(getInvokedMethod(), isInterface)
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 9ccaec9..fc23097 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
@@ -9,6 +9,7 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.startup.StartupInstrumentation;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
@@ -468,6 +469,8 @@
       D8CfInstructionDesugaringEventConsumer desugaringEventConsumer,
       D8MethodProcessor methodProcessor,
       InterfaceProcessor interfaceProcessor) {
+    new StartupInstrumentation(appView).instrumentClass(clazz);
+
     // When converting all methods on a class always convert <clinit> first.
     ProgramMethod classInitializer = clazz.getProgramClassInitializer();
 
@@ -483,7 +486,6 @@
 
     for (ProgramMethod method : methods) {
       if (!method.getDefinition().isClassInitializer()) {
-        DexEncodedMethod definition = method.getDefinition();
         methodProcessor.processMethod(method, desugaringEventConsumer);
         if (interfaceProcessor != null) {
           interfaceProcessor.processMethod(method, desugaringEventConsumer);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index eb5a30b..c039f9d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -249,7 +249,7 @@
               return;
             }
             AndroidApiLevel theApi = apiLevel.asKnownApiLevel().getApiLevel();
-            if (appView.typeRewriter.hasRewrittenType(type, appView)) {
+            if (typeIsInDesugaredLibrary(type)) {
               assert theApi.equals(appView.options().getMinApiLevel());
               return;
             }
@@ -258,6 +258,15 @@
       return true;
     }
 
+    private boolean typeIsInDesugaredLibrary(DexType type) {
+      return appView.typeRewriter.hasRewrittenType(type, appView)
+          || appView
+              .options()
+              .machineDesugaredLibrarySpecification
+              .getMaintainType()
+              .contains(type);
+    }
+
     private boolean typeIsAbsentOrPresentWithoutBackportsFrom(
         DexType type, AndroidApiLevel apiLevel) {
       return !typeIsPresent(type) || typeIsPresentWithoutBackportsFrom(type, apiLevel);
@@ -268,7 +277,7 @@
     }
 
     private boolean typeIsPresentWithoutBackportsFrom(DexType type, AndroidApiLevel methodsMinAPI) {
-      if (appView.typeRewriter.hasRewrittenType(type, appView)) {
+      if (typeIsInDesugaredLibrary(type)) {
         // Desugared library is enabled, the methods are present if desugared library specifies it.
         return methodsMinAPI.isGreaterThan(AndroidApiLevel.N)
             && !appView.options().machineDesugaredLibrarySpecification.includesJDK11Methods();
@@ -284,7 +293,7 @@
     private boolean typeIsPresent(DexType type) {
       // TODO(b/224954240): Always use the apiDatabase when always available.
       return appView.options().getMinApiLevel().isGreaterThanOrEqualTo(typeMinApi.get(type))
-          || appView.typeRewriter.hasRewrittenType(type, appView);
+          || typeIsInDesugaredLibrary(type);
     }
 
     boolean isEmpty() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index d01f7fc..012c31c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -381,7 +381,7 @@
             appView.dexItemFactory().icceType,
             descriptor.implHandle.isInterface);
       }
-      SingleResolutionResult result = resolution.asSingleResolution();
+      SingleResolutionResult<?> result = resolution.asSingleResolution();
       assert result.getResolvedMethod().isStatic();
       assert result.getResolvedHolder().isProgramClass();
       return new StaticLambdaImplTarget(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index 48dca2d..8935159 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -123,7 +123,7 @@
             .resolveMethod(bootstrapMethodReference, bootstrapMethodHandle.isInterface);
     if (resolution.isSingleResolution()
         && resolution.asSingleResolution().getResolvedMethod().isStatic()) {
-      SingleResolutionResult result = resolution.asSingleResolution();
+      SingleResolutionResult<?> result = resolution.asSingleResolution();
       if (bootstrapMethodHandle.isInterface
           && appView.options().isInterfaceMethodDesugaringEnabled()) {
         bootstrapMethodReference =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index b0c7920..50e98e1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -172,6 +172,7 @@
   public boolean isEmpty() {
     return rewritePrefix.isEmpty()
         && rewriteDerivedPrefix.isEmpty()
+        && maintainPrefix.isEmpty()
         && emulatedInterfaces.isEmpty()
         && retargetMethod.isEmpty()
         && retargetMethodEmulatedDispatch.isEmpty()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index d974c56..7367257 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -52,6 +52,10 @@
     this.rewritingFlags = rewritingFlags;
   }
 
+  public boolean isEmpty() {
+    return rewritingFlags.isEmpty();
+  }
+
   public boolean isLibraryCompilation() {
     return libraryCompilation;
   }
@@ -167,7 +171,10 @@
 
   public boolean isSupported(DexReference reference) {
     // Support through type rewriting.
-    if (rewritingFlags.getRewriteType().containsKey(reference.getContextType())) {
+    if (getRewriteType().containsKey(reference.getContextType())) {
+      return true;
+    }
+    if (getMaintainType().contains(reference.getContextType())) {
       return true;
     }
     if (!reference.isDexMethod()) {
@@ -193,6 +200,10 @@
     return topLevelFlags.getRequiredCompilationAPILevel();
   }
 
+  public boolean requiresTypeRewriting() {
+    return !getRewriteType().isEmpty() || !getRewriteDerivedTypeOnly().isEmpty();
+  }
+
   private int getLeadingVersionNumber() {
     if (leadingVersionNumberCache != -1) {
       return leadingVersionNumberCache;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index dcaa744..1a301da 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -196,6 +196,15 @@
     return emulatedInterfaces.get(method.getHolderType()).getEmulatedMethods().get(method);
   }
 
+  public boolean isEmpty() {
+    return rewriteType.isEmpty()
+        && maintainType.isEmpty()
+        && rewriteDerivedTypeOnly.isEmpty()
+        && !hasRetargeting()
+        && emulatedInterfaces.isEmpty()
+        && legacyBackport.isEmpty();
+  }
+
   public static class Builder {
 
     Builder() {}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
index ef19eb6..567cc79 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
@@ -26,6 +26,8 @@
       levelType = app.dexItemFactory.createType("Ljava/time/LocalTime;");
     } else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.R)) {
       levelType = app.dexItemFactory.createType("Ljava/util/concurrent/Flow;");
+    } else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.N)) {
+      levelType = app.dexItemFactory.createType("Ljava/util/function/Supplier;");
     } else {
       app.options.reporter.warning(
           "Unsupported requiredCompilationAPILevel: " + requiredCompilationAPILevel);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 4446275..266b1c5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -377,7 +377,8 @@
     this.dexItemFactory = appView.dexItemFactory();
     this.helper = new InterfaceDesugaringSyntheticHelper(appView);
     needsLibraryInfo =
-        appView.options().machineDesugaredLibrarySpecification.hasEmulatedInterfaces();
+        !appView.options().canUseDefaultAndStaticInterfaceMethods()
+            && !appView.options().machineDesugaredLibrarySpecification.isEmpty();
     this.isLiveMethod = isLiveMethod;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 778ed23..7b3bb07 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -405,7 +405,7 @@
           .build();
     }
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView
             .appInfoForDesugaring()
             .resolveMethodOnInterface(holder, invoke.getMethod())
@@ -435,7 +435,7 @@
   private DesugarDescription computeInvokeVirtualDispatch(
       DexClass holder, CfInvoke invoke, ProgramMethod context) {
     AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
-    SingleResolutionResult resolution =
+    SingleResolutionResult<?> resolution =
         appInfoForDesugaring
             .resolveMethod(invoke.getMethod(), invoke.isInterface())
             .asSingleResolution();
@@ -497,7 +497,7 @@
       return computeInvokeAsThrowRewrite(invoke, null, context);
     }
 
-    SingleResolutionResult singleResolution = resolution.asSingleResolution();
+    SingleResolutionResult<?> singleResolution = resolution.asSingleResolution();
     if (singleResolution == null) {
       return DesugarDescription.nothing();
     }
@@ -573,7 +573,7 @@
   }
 
   private DesugarDescription computeInvokeAsThrowRewrite(
-      CfInvoke invoke, SingleResolutionResult resolution, ProgramMethod context) {
+      CfInvoke invoke, SingleResolutionResult<?> resolution, ProgramMethod context) {
     assert !isAlreadyDesugared(invoke, context);
     return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowRewrite(
         appView, invoke, resolution);
@@ -637,7 +637,7 @@
           .build();
     }
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView.appInfoForDesugaring().resolveMethodOn(clazz, invokedMethod).asSingleResolution();
     if (clazz.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, false)) {
       return computeInvokeAsThrowRewrite(invoke, resolutionResult, context);
@@ -760,7 +760,7 @@
   }
 
   private boolean shouldRewriteToInvokeToThrow(
-      SingleResolutionResult resolutionResult, boolean isInvokeStatic) {
+      SingleResolutionResult<?> resolutionResult, boolean isInvokeStatic) {
     return resolutionResult == null
         || resolutionResult.getResolvedMethod().isStatic() != isInvokeStatic;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index 97c8d47..1c49fc9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -227,7 +227,7 @@
 
   private boolean computeAssumedValuesFromSingleTarget(
       IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
             .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 9490667..3788e98 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -124,7 +124,7 @@
   @Override
   public boolean passesInliningConstraints(
       InvokeMethod invoke,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethod singleTarget,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
@@ -261,7 +261,7 @@
   public InlineResult computeInlining(
       IRCode code,
       InvokeMethod invoke,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethod singleTarget,
       ProgramMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
@@ -336,7 +336,7 @@
 
   private boolean neverInline(
       InvokeMethod invoke,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethod singleTarget,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     AppInfoWithLiveness appInfo = appView.appInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index cd5b8b2..9ba2beb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -368,7 +368,7 @@
       return target;
     }
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView.appInfo().resolveMethodOnClass(target.getHolderType(), target).asSingleResolution();
     if (resolutionResult == null
         || resolutionResult
@@ -384,7 +384,7 @@
       return target;
     }
 
-    SingleResolutionResult newResolutionResult =
+    SingleResolutionResult<?> newResolutionResult =
         appView.appInfo().resolveMethodOnClass(receiverType, target).asSingleResolution();
     if (newResolutionResult == null
         || newResolutionResult
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 493b052..be64b57 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -50,7 +50,7 @@
   @Override
   public boolean passesInliningConstraints(
       InvokeMethod invoke,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethod candidate,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
@@ -70,7 +70,7 @@
   public InlineResult computeInlining(
       IRCode code,
       InvokeMethod invoke,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethod singleTarget,
       ProgramMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
@@ -88,7 +88,7 @@
 
   private InlineAction computeForInvoke(
       InvokeMethod invoke,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     Inliner.InliningInfo info = invokesToInline.get(invoke);
     if (info == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index 4b3bb6f..265d097 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -137,7 +137,7 @@
           AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
           AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
 
-          SingleResolutionResult resolutionResult =
+          SingleResolutionResult<?> resolutionResult =
               appInfoWithLiveness
                   .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
                   .asSingleResolution();
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 577dbf2..4ca07c5 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
@@ -960,7 +960,7 @@
           InvokeMethod invoke = current.asInvokeMethod();
           // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget()!
           DexMethod invokedMethod = invoke.getInvokedMethod();
-          SingleResolutionResult resolutionResult =
+          SingleResolutionResult<?> resolutionResult =
               appView
                   .appInfo()
                   .resolveMethod(invokedMethod, invoke.getInterfaceBit())
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index ddc31c2..b484dda 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -26,7 +26,7 @@
 
   boolean passesInliningConstraints(
       InvokeMethod invoke,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethod candidate,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
@@ -34,7 +34,7 @@
   InlineResult computeInlining(
       IRCode code,
       InvokeMethod invoke,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethod singleTarget,
       ProgramMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
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 7e494f3..09b614e 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
@@ -289,7 +289,7 @@
       return;
     }
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).asSingleResolution();
     if (resolutionResult == null) {
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
index 8312643..1a35898 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
@@ -88,7 +88,7 @@
         continue;
       }
 
-      SingleResolutionResult resolutionResult =
+      SingleResolutionResult<?> resolutionResult =
           appView
               .appInfo()
               .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
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 974bedd..0b876a7 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
@@ -291,7 +291,7 @@
 
         if (user.isInvokeMethod()) {
           InvokeMethod invoke = user.asInvokeMethod();
-          SingleResolutionResult resolutionResult =
+          SingleResolutionResult<?> resolutionResult =
               appView
                   .appInfo()
                   .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
@@ -1071,7 +1071,7 @@
   //
   private boolean isEligibleDirectMethodCall(
       InvokeMethod invoke,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethod singleTarget,
       Supplier<InliningOracle> defaultOracle,
       Set<Instruction> indirectUsers) {
@@ -1175,7 +1175,7 @@
     if (!usage.isBottom()) {
       NonEmptyParameterUsage nonEmptyUsage = usage.asNonEmpty();
       for (DexMethod invokedMethod : nonEmptyUsage.getMethodCallsWithParameterAsReceiver()) {
-        SingleResolutionResult resolutionResult =
+        SingleResolutionResult<?> resolutionResult =
             appView.appInfo().resolveMethodOn(eligibleClass, invokedMethod).asSingleResolution();
         if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) {
           return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
index 0e4a8dd..ebad797 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
@@ -247,7 +247,7 @@
       return state.abandonClassInliningInCurrentContexts(receiverRoot);
     }
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
             .resolveMethodOnClassHolder(invoke.getInvokedMethod())
@@ -286,7 +286,7 @@
       return state;
     }
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
             .resolveMethodOnInterfaceHolder(invoke.getInvokedMethod())
@@ -302,7 +302,7 @@
   private ParameterUsages analyzeInvokeStatic(InvokeStatic invoke, NonEmptyParameterUsages state) {
     // We generally don't class inline instances that escape through invoke-static calls, but we
     // make an exception for calls to Objects.requireNonNull().
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
             .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
@@ -328,7 +328,7 @@
       return state;
     }
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView
             .appInfo()
             .resolveMethodOnClassHolder(invoke.getInvokedMethod())
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index f455175..b2d2531 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -1338,12 +1338,12 @@
 
     // Calls to java.lang.System.
     if (targetHolder.getType() == factory.javaLangSystemType) {
-      if (singleTargetReference == factory.javaLangSystemMethods.arraycopy) {
+      if (singleTargetReference == factory.javaLangSystemMembers.arraycopy) {
         // Important for Kotlin 1.5 enums, which use arraycopy to create a copy of $VALUES instead
         // of int[].clone().
         return Reason.ELIGIBLE;
       }
-      if (singleTargetReference == factory.javaLangSystemMethods.identityHashCode) {
+      if (singleTargetReference == factory.javaLangSystemMembers.identityHashCode) {
         // Important for proto enum unboxing.
         return Reason.ELIGIBLE;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index bc981ea..cfec469 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -440,9 +440,9 @@
 
     // Calls to java.lang.System.
     if (invokedMethod.getHolderType() == factory.javaLangSystemType) {
-      if (invokedMethod == factory.javaLangSystemMethods.arraycopy) {
+      if (invokedMethod == factory.javaLangSystemMembers.arraycopy) {
         // Intentionally empty.
-      } else if (invokedMethod == factory.javaLangSystemMethods.identityHashCode) {
+      } else if (invokedMethod == factory.javaLangSystemMembers.identityHashCode) {
         assert invoke.arguments().size() == 1;
         Value argument = invoke.getFirstArgument();
         DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index 55d5c50..5edfe2e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -314,7 +314,7 @@
               new CfConstNumber(0, ValueType.INT),
               new CfLoad(ValueType.INT, argumentLocalSlot),
               new CfInvoke(
-                  Opcodes.INVOKESTATIC, dexItemFactory.javaLangSystemMethods.arraycopy, false),
+                  Opcodes.INVOKESTATIC, dexItemFactory.javaLangSystemMembers.arraycopy, false),
               // return result
               new CfLoad(ValueType.OBJECT, resultLocalSlot),
               new CfReturn(ValueType.OBJECT)));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
index 34514e3..9d4ae82 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
@@ -48,7 +48,7 @@
       // Check if this is a program class with a toString() method that does not have side effects.
       DexClass clazz = appInfo.definitionFor(classType);
       if (clazz != null && clazz.isEffectivelyFinal(appView)) {
-        SingleResolutionResult resolutionResult =
+        SingleResolutionResult<?> resolutionResult =
             appInfo.resolveMethodOn(clazz, toStringMethodReference).asSingleResolution();
         if (resolutionResult != null
             && !resolutionResult.getResolvedMethod().getOptimizationInfo().mayHaveSideEffects()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
index b28520c..d207ac7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
@@ -18,7 +18,7 @@
 
   public static AssumeInfo lookupAssumeInfo(
       AppView<AppInfoWithLiveness> appView,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       DexClassAndMethod singleTarget) {
     AssumeInfo resolutionLookup = lookupAssumeInfo(appView, resolutionResult.getResolutionPair());
     if (resolutionLookup == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index 93f6242..ef2f42d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -173,7 +173,7 @@
       return null;
     }
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView.appInfo().resolveMethodOn(superClass, method.getReference()).asSingleResolution();
     if (resolutionResult == null) {
       return null;
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index d67d177..7657adc 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -126,8 +126,8 @@
       boolean allowEmptyMappedRanges,
       boolean allowExperimentalMapping)
       throws IOException {
-    return mapperFromBufferedReader(
-        CharSource.wrap(contents).openBufferedStream(),
+    return mapperFromLineReader(
+        LineReader.fromBufferedReader(CharSource.wrap(contents).openBufferedStream()),
         diagnosticsHandler,
         allowEmptyMappedRanges,
         allowExperimentalMapping);
@@ -144,6 +144,19 @@
       boolean allowEmptyMappedRanges,
       boolean allowExperimentalMapping)
       throws IOException {
+    return mapperFromLineReader(
+        LineReader.fromBufferedReader(reader),
+        diagnosticsHandler,
+        allowEmptyMappedRanges,
+        allowExperimentalMapping);
+  }
+
+  public static ClassNameMapper mapperFromLineReader(
+      LineReader reader,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean allowEmptyMappedRanges,
+      boolean allowExperimentalMapping)
+      throws IOException {
     try (ProguardMapReader proguardReader =
         new ProguardMapReader(
             reader,
@@ -156,8 +169,8 @@
     }
   }
 
-  public static ClassNameMapper mapperFromBufferedReaderWithFiltering(
-      BufferedReader reader,
+  public static ClassNameMapper mapperFromLineReaderWithFiltering(
+      LineReader reader,
       DiagnosticsHandler diagnosticsHandler,
       boolean allowEmptyMappedRanges,
       boolean allowExperimentalMapping,
diff --git a/src/main/java/com/android/tools/r8/naming/LineReader.java b/src/main/java/com/android/tools/r8/naming/LineReader.java
new file mode 100644
index 0000000..a3f7cc3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/LineReader.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, 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.naming;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+/** This is an abstraction over BufferedReader */
+public interface LineReader {
+
+  String readLine() throws IOException;
+
+  void close() throws IOException;
+
+  static LineReader fromBufferedReader(BufferedReader bufferedReader) {
+    return new BufferedLineReader(bufferedReader);
+  }
+
+  class BufferedLineReader implements LineReader {
+
+    private final BufferedReader bufferedReader;
+
+    private BufferedLineReader(BufferedReader bufferedReader) {
+      this.bufferedReader = bufferedReader;
+    }
+
+    @Override
+    public String readLine() throws IOException {
+      return bufferedReader.readLine();
+    }
+
+    @Override
+    public void close() throws IOException {
+      bufferedReader.close();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 1ba7556..2177ea3 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -53,7 +53,9 @@
     ClassNameMinifier classNameMinifier =
         new ClassNameMinifier(
             appView,
-            new MinificationClassNamingStrategy(appView),
+            appView.options().synthesizedClassPrefix.isEmpty()
+                ? new MinificationClassNamingStrategy(appView)
+                : new L8MinificationClassNamingStrategy(appView),
             // Use deterministic class order to make sure renaming is deterministic.
             appView.appInfo().classesWithDeterministicOrder());
     ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
@@ -122,6 +124,11 @@
     String nextName(char[] packagePrefix, InternalNamingState state) {
       StringBuilder nextName = new StringBuilder();
       nextName.append(packagePrefix);
+      nextName.append(nextString(packagePrefix, state));
+      return nextName.toString();
+    }
+
+    String nextString(char[] packagePrefix, InternalNamingState state) {
       String nextString;
       do {
         if (state.getDictionaryIndex() < obfuscationDictionary.size()) {
@@ -133,8 +140,36 @@
           } while (obfuscationDictionaryForLookup.contains(nextString));
         }
       } while (RESERVED_NAMES.contains(nextString));
-      nextName.append(nextString);
-      return nextName.toString();
+      return nextString;
+    }
+  }
+
+  static class L8MinificationClassNamingStrategy extends MinificationClassNamingStrategy {
+
+    private final String prefix;
+
+    L8MinificationClassNamingStrategy(AppView<AppInfoWithLiveness> appView) {
+      super(appView);
+      String synthesizedClassPrefix = appView.options().synthesizedClassPrefix;
+      prefix = synthesizedClassPrefix.substring(0, synthesizedClassPrefix.length() - 1);
+    }
+
+    private boolean startsWithPrefix(char[] packagePrefix) {
+      if (packagePrefix.length < prefix.length() + 1) {
+        return false;
+      }
+      for (int i = 0; i < prefix.length(); i++) {
+        if (prefix.charAt(i) != packagePrefix[i + 1]) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
+    String nextString(char[] packagePrefix, InternalNamingState state) {
+      String nextString = super.nextString(packagePrefix, state);
+      return startsWithPrefix(packagePrefix) ? nextString : prefix + nextString;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 3e6816b..bbea4c4 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
-import java.io.BufferedReader;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -62,7 +61,7 @@
  */
 public class ProguardMapReader implements AutoCloseable {
 
-  private final BufferedReader reader;
+  private final LineReader reader;
   private final JsonParser jsonParser = new JsonParser();
   private final DiagnosticsHandler diagnosticsHandler;
   private final boolean allowEmptyMappedRanges;
@@ -74,7 +73,7 @@
   }
 
   ProguardMapReader(
-      BufferedReader reader,
+      LineReader reader,
       DiagnosticsHandler diagnosticsHandler,
       boolean allowEmptyMappedRanges,
       boolean allowExperimentalMapping) {
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index 40fc12f..bf8044e 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -81,7 +81,9 @@
 
   private static SeedMapper seedMapperFromInputStream(Reporter reporter, InputStream in)
       throws IOException {
-    BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+    LineReader reader =
+        LineReader.fromBufferedReader(
+            new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)));
     try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter, false, false)) {
       SeedMapper.Builder builder = SeedMapper.builder(reporter);
       proguardReader.parse(builder);
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index e13e860..2e373e0 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -62,7 +62,7 @@
 
   private DexMethod validMemberRebindingTargetForNonProgramMethod(
       DexClassAndMethod resolvedMethod,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethodSet contexts,
       Type invokeType,
       DexMethod original) {
@@ -95,7 +95,7 @@
     }
 
     LibraryMethod eligibleLibraryMethod = null;
-    SingleResolutionResult currentResolutionResult = resolutionResult;
+    SingleResolutionResult<?> currentResolutionResult = resolutionResult;
     while (currentResolutionResult != null) {
       DexClassAndMethod currentResolvedMethod = currentResolutionResult.getResolutionPair();
       if (canRebindDirectlyToLibraryMethod(
@@ -138,7 +138,7 @@
 
   private boolean canRebindDirectlyToLibraryMethod(
       DexClassAndMethod resolvedMethod,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethodSet contexts,
       Type invokeType) {
     // TODO(b/194422791): It could potentially be that `original.holder` is not a subtype of
@@ -154,7 +154,7 @@
 
   private boolean isAccessibleInAllContexts(
       DexClassAndMethod resolvedMethod,
-      SingleResolutionResult resolutionResult,
+      SingleResolutionResult<?> resolutionResult,
       ProgramMethodSet contexts) {
     if (resolvedMethod.getHolder().isPublic() && resolvedMethod.getDefinition().isPublic()) {
       return true;
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index ebf61af..b819518 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -215,7 +215,7 @@
       }
       DexClass holder = appView.contextIndependentDefinitionFor(reference.getHolderType());
       if (holder != null) {
-        SingleResolutionResult resolutionResult =
+        SingleResolutionResult<?> resolutionResult =
             appView.appInfo().resolveMethodOn(holder, reference).asSingleResolution();
         if (resolutionResult != null && resolutionResult.getResolvedHolder() != holder) {
           recordNonReboundMethodAccess(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
index 1363a68..38c08c6 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
@@ -215,7 +215,7 @@
       if (holder == null) {
         return;
       }
-      SingleResolutionResult resolutionResult =
+      SingleResolutionResult<?> resolutionResult =
           appInfo.resolveMethodOn(holder, method).asSingleResolution();
       if (resolutionResult == null) {
         return;
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java
index 745cf99..cbb7761 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingUtils.java
@@ -21,7 +21,7 @@
     if (clazz == null) {
       return false;
     }
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView.appInfo().resolveMethodOn(clazz, method).asSingleResolution();
     return resolutionResult != null
         && resolutionResult.getResolvedHolder().getType() != method.getHolderType();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index ba4b05a..150f7ba 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -163,7 +163,7 @@
       return;
     }
 
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).asSingleResolution();
     if (resolutionResult == null) {
       // Nothing to propagate; the invoke instruction fails.
@@ -540,7 +540,7 @@
     // If the bootstrap method is program declared it will be called. The call is with runtime
     // provided arguments so ensure that the argument information is unknown.
     DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
-    SingleResolutionResult resolution =
+    SingleResolutionResult<?> resolution =
         appView
             .appInfo()
             .resolveMethod(bootstrapMethod.asMethod(), bootstrapMethod.isInterface)
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index fdf81ab..f7f7d06 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -187,7 +187,7 @@
     }
 
     private void registerInvokeMethod(DexMethod method) {
-      SingleResolutionResult resolutionResult =
+      SingleResolutionResult<?> resolutionResult =
           appView.appInfo().unsafeResolveMethodDueToDexFormat(method).asSingleResolution();
       if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) {
         return;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
index 787ae13..59d8ba7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -102,7 +102,7 @@
 
               inactiveMethodStates.forEach(
                   (signature, methodState) -> {
-                    SingleResolutionResult resolutionResult =
+                    SingleResolutionResult<?> resolutionResult =
                         appView.appInfo().resolveMethodOn(clazz, signature).asSingleResolution();
 
                     // Find the first virtual method in the super class hierarchy.
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
index 90e9ead..87f26e7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
@@ -124,7 +124,7 @@
             }
 
             // Do not reprocess the method if the invoke resolves to a library method.
-            SingleResolutionResult resolutionResult =
+            SingleResolutionResult<?> resolutionResult =
                 appView
                     .appInfo()
                     .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java b/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java
index c3f94be..f2b6783 100644
--- a/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java
@@ -6,28 +6,53 @@
 
 import com.android.tools.r8.Keep;
 import com.google.common.primitives.Bytes;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.Reader;
-import java.io.StringReader;
+import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
 import java.nio.file.Path;
 
 /** Interface for producing a string format of a mapping file. */
 @Keep
 public interface ProguardMapProducer {
 
-  Reader get() throws IOException;
+  InputStream get() throws IOException;
+
+  default boolean isFileBacked() {
+    return false;
+  }
+
+  default Path getPath() throws FileNotFoundException {
+    return null;
+  }
 
   static ProguardMapProducer fromString(String proguardMapString) {
-    return () -> new StringReader(proguardMapString);
+    return () -> new ByteArrayInputStream(proguardMapString.getBytes(StandardCharsets.UTF_8));
   }
 
   static ProguardMapProducer fromPath(Path path) {
-    return () -> Files.newBufferedReader(path, StandardCharsets.UTF_8);
+    return new ProguardMapProducer() {
+      @Override
+      public InputStream get() throws IOException {
+        return new BufferedInputStream(new FileInputStream(path.toFile()));
+      }
+
+      @Override
+      public boolean isFileBacked() {
+        return true;
+      }
+
+      @Override
+      public Path getPath() {
+        return path;
+      }
+    };
   }
 
   static ProguardMapProducer fromBytes(byte[]... partitions) {
-    return fromString(new String(Bytes.concat(partitions), StandardCharsets.UTF_8));
+    return () -> new ByteArrayInputStream(Bytes.concat(partitions));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 01dc051..82a04e6 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -33,9 +33,9 @@
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.io.CharStreams;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintStream;
-import java.io.Reader;
 import java.io.UnsupportedEncodingException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -302,9 +302,10 @@
       Timing timing = Timing.create("R8 retrace", command.printMemory());
       RetraceOptions options = command.getOptions();
       if (command.getOptions().isVerifyMappingFileHash()) {
-        try (Reader reader = options.getProguardMapProducer().get()) {
+        try (InputStream reader = options.getProguardMapProducer().get()) {
           VerifyMappingFileHashResult checkResult =
-              ProguardMapChecker.validateProguardMapHash(CharStreams.toString(reader));
+              ProguardMapChecker.validateProguardMapHash(
+                  CharStreams.toString(new InputStreamReader(reader, Charsets.UTF_8)));
           if (checkResult.isError()) {
             command
                 .getOptions()
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
new file mode 100644
index 0000000..279a038
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
@@ -0,0 +1,214 @@
+// Copyright (c) 2022, 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.retrace.internal;
+
+import static java.lang.Integer.MAX_VALUE;
+
+import com.android.tools.r8.naming.LineReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+
+public abstract class ProguardMapReaderWithFiltering implements LineReader {
+
+  private int startIndex = 0;
+  private int endIndex = 0;
+
+  public abstract byte[] read() throws IOException;
+
+  public abstract int getStartIndex();
+
+  public abstract int getEndIndex();
+
+  public abstract boolean exceedsBuffer();
+
+  @Override
+  public String readLine() throws IOException {
+    byte[] bytes = readLineFromMultipleReads();
+    if (bytes == null) {
+      return null;
+    }
+    return new String(bytes, startIndex, endIndex - startIndex, StandardCharsets.UTF_8);
+  }
+
+  private byte[] readLineFromMultipleReads() throws IOException {
+    startIndex = 0;
+    endIndex = 0;
+    byte[] currentReadBytes = null;
+    do {
+      byte[] readBytes = read();
+      if (readBytes == null) {
+        return currentReadBytes;
+      }
+      if (exceedsBuffer() || currentReadBytes != null) {
+        // We are building up a partial result where all bytes will be present in the
+        // currentReadBytes array.
+        int thisLength = getEndIndex() - getStartIndex();
+        int currentReadBytesLength = currentReadBytes == null ? 0 : currentReadBytes.length;
+        byte[] newReadBytes = new byte[thisLength + currentReadBytesLength];
+        if (currentReadBytes != null) {
+          System.arraycopy(currentReadBytes, 0, newReadBytes, 0, currentReadBytes.length);
+        }
+        System.arraycopy(
+            readBytes, getStartIndex(), newReadBytes, currentReadBytesLength, thisLength);
+        currentReadBytes = newReadBytes;
+        endIndex = newReadBytes.length;
+      } else {
+        currentReadBytes = readBytes;
+        startIndex = getStartIndex();
+        endIndex = getEndIndex();
+      }
+    } while (exceedsBuffer());
+    return currentReadBytes;
+  }
+
+  public static class ProguardMapReaderWithFilteringMappedBuffer
+      extends ProguardMapReaderWithFiltering {
+
+    private final int PAGE_SIZE = 1024 * 8;
+
+    private final FileChannel fileChannel;
+    private MappedByteBuffer mappedByteBuffer;
+    private final long channelSize;
+    private final byte[] buffer = new byte[PAGE_SIZE];
+
+    private int currentPosition = 0;
+    private int temporaryBufferPosition = 0;
+
+    public ProguardMapReaderWithFilteringMappedBuffer(Path mappingFile) throws IOException {
+      fileChannel = FileChannel.open(mappingFile, StandardOpenOption.READ);
+      channelSize = fileChannel.size();
+      readFromChannel();
+    }
+
+    private void readFromChannel() throws IOException {
+      mappedByteBuffer =
+          fileChannel.map(
+              MapMode.READ_ONLY,
+              currentPosition,
+              Math.min(channelSize - currentPosition, MAX_VALUE));
+    }
+
+    @Override
+    public byte[] read() throws IOException {
+      if (currentPosition >= channelSize) {
+        return null;
+      }
+      temporaryBufferPosition = 0;
+      while (currentPosition < channelSize) {
+        if (!mappedByteBuffer.hasRemaining()) {
+          readFromChannel();
+        }
+        byte readByte = readByte();
+        if (readByte == '\n') {
+          break;
+        }
+        buffer[temporaryBufferPosition++] = readByte;
+        if (temporaryBufferPosition == PAGE_SIZE) {
+          break;
+        }
+      }
+      return buffer;
+    }
+
+    @Override
+    public int getStartIndex() {
+      return 0;
+    }
+
+    @Override
+    public int getEndIndex() {
+      if (temporaryBufferPosition > 0 && buffer[temporaryBufferPosition - 1] == '\r') {
+        return temporaryBufferPosition - 1;
+      }
+      return temporaryBufferPosition;
+    }
+
+    @Override
+    public boolean exceedsBuffer() {
+      return temporaryBufferPosition == PAGE_SIZE;
+    }
+
+    private byte readByte() {
+      currentPosition += 1;
+      return mappedByteBuffer.get();
+    }
+
+    @Override
+    public void close() throws IOException {
+      fileChannel.close();
+    }
+  }
+
+  public static class ProguardMapReaderWithFilteringInputBuffer
+      extends ProguardMapReaderWithFiltering {
+
+    private final InputStream inputStream;
+
+    private final int BUFFER_SIZE = 1024 * 8;
+    private final byte[] buffer = new byte[BUFFER_SIZE];
+
+    private int bufferIndex = BUFFER_SIZE;
+    private int startIndex = 0;
+    private int endIndex = 0;
+    private int endReadIndex = 0;
+
+    public ProguardMapReaderWithFilteringInputBuffer(InputStream inputStream) {
+      this.inputStream = inputStream;
+    }
+
+    @Override
+    public void close() throws IOException {
+      inputStream.close();
+    }
+
+    @Override
+    public byte[] read() throws IOException {
+      if (bufferIndex >= endReadIndex) {
+        endReadIndex = inputStream.read(buffer);
+        if (endReadIndex == -1) {
+          return null;
+        }
+        bufferIndex = 0;
+      }
+      startIndex = bufferIndex;
+      boolean foundLineBreak = false;
+      for (endIndex = startIndex; endIndex < endReadIndex; endIndex++) {
+        if (buffer[endIndex] == '\n') {
+          foundLineBreak = true;
+          break;
+        }
+      }
+      bufferIndex = endIndex;
+      if (foundLineBreak) {
+        bufferIndex += 1;
+      }
+      return buffer;
+    }
+
+    @Override
+    public int getStartIndex() {
+      return startIndex;
+    }
+
+    @Override
+    public int getEndIndex() {
+      if (endIndex > 0 && buffer[endIndex - 1] == '\r') {
+        return endIndex - 1;
+      }
+      return endIndex;
+    }
+
+    @Override
+    public boolean exceedsBuffer() {
+      return endReadIndex == endIndex;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
index e18adca..cb6991e 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
@@ -6,11 +6,13 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.LineReader;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.retrace.InvalidMappingFileException;
 import com.android.tools.r8.retrace.ProguardMapProducer;
 import com.android.tools.r8.retrace.ProguardMappingProvider;
-import java.io.BufferedReader;
+import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
+import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringMappedBuffer;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -62,23 +64,17 @@
   @Override
   public ProguardMappingProvider build() {
     try {
-      if (allowLookupAllClasses) {
-        return new ProguardMappingProviderImpl(
-            ClassNameMapper.mapperFromBufferedReader(
-                new BufferedReader(proguardMapProducer.get()),
-                diagnosticsHandler,
-                true,
-                allowExperimental));
-      } else {
-        return new ProguardMappingProviderImpl(
-            ClassNameMapper.mapperFromBufferedReaderWithFiltering(
-                new BufferedReader(proguardMapProducer.get()),
-                diagnosticsHandler,
-                true,
-                allowExperimental,
-                allowedLookup),
-            allowedLookup);
-      }
+      LineReader reader =
+          proguardMapProducer.isFileBacked()
+              ? new ProguardMapReaderWithFilteringMappedBuffer(proguardMapProducer.getPath())
+              : new ProguardMapReaderWithFilteringInputBuffer(proguardMapProducer.get());
+      return new ProguardMappingProviderImpl(
+          ClassNameMapper.mapperFromLineReaderWithFiltering(
+              reader,
+              diagnosticsHandler,
+              true,
+              allowExperimental,
+              allowLookupAllClasses ? null : allowedLookup));
     } catch (Exception e) {
       throw new InvalidMappingFileException(e);
     }
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 eac02d2..34425b3 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -1387,7 +1387,7 @@
           singleTargetLookupCache.getCachedItem(refinedReceiverType, method);
       return cachedItem;
     }
-    SingleResolutionResult resolution =
+    SingleResolutionResult<?> resolution =
         resolveMethodOn(initialResolutionHolder, method).asSingleResolution();
     if (resolution == null
         || resolution.isAccessibleForVirtualDispatchFrom(context.getHolder(), this).isFalse()) {
@@ -1460,7 +1460,7 @@
   private DexEncodedMethod getMethodTargetFromExactRuntimeInformation(
       DexType refinedReceiverType,
       ClassTypeElement receiverLowerBoundType,
-      SingleResolutionResult resolution,
+      SingleResolutionResult<?> resolution,
       DexClass refinedReceiverClass) {
     // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
     // runtime type information. In this case, the invoke will dispatch to the resolution result
@@ -1555,6 +1555,7 @@
   public SubtypingInfo computeSubtypingInfo() {
     return SubtypingInfo.create(this);
   }
+
   /** Predicate on types that *must* never be merged horizontally. */
   public boolean isNoHorizontalClassMergingOfType(DexType type) {
     return noClassMerging.contains(type) || noHorizontalClassMerging.contains(type);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 7d54b09..98562f9 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassDefinition;
+import com.android.tools.r8.graph.ClassResolutionResult;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.ClasspathOrLibraryDefinition;
 import com.android.tools.r8.graph.Definition;
@@ -160,6 +161,7 @@
 import com.google.common.collect.Sets.SetView;
 import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
+import java.lang.reflect.InvocationHandler;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -667,26 +669,39 @@
     return definitionFor(type, context, this::recordNonProgramClass, this::reportMissingClass);
   }
 
+  public boolean hasAlternativeLibraryDefinition(DexProgramClass programClass) {
+    ClassResolutionResult classResolutionResult =
+        internalDefinitionFor(
+            programClass.type, programClass, this::recordNonProgramClass, this::reportMissingClass);
+    assert classResolutionResult.hasClassResolutionResult();
+    DexClass alternativeClass = classResolutionResult.toAlternativeClassWithProgramOverLibrary();
+    assert alternativeClass == null || alternativeClass.isLibraryClass();
+    return alternativeClass != null;
+  }
+
   private DexClass definitionFor(
       DexType type,
       ProgramDerivedContext context,
       BiConsumer<DexClass, ProgramDerivedContext> foundClassConsumer,
       BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
-    return internalDefinitionFor(type, context, foundClassConsumer, missingClassConsumer);
+    return internalDefinitionFor(type, context, foundClassConsumer, missingClassConsumer)
+        .toSingleClassWithProgramOverLibrary();
   }
 
-  private DexClass internalDefinitionFor(
+  private ClassResolutionResult internalDefinitionFor(
       DexType type,
       ProgramDerivedContext context,
       BiConsumer<DexClass, ProgramDerivedContext> foundClassConsumer,
       BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
-    DexClass clazz = appInfo().definitionFor(type);
-    if (clazz == null) {
+    ClassResolutionResult classResolutionResult =
+        appInfo().contextIndependentDefinitionForWithResolutionResult(type);
+    if (classResolutionResult.hasClassResolutionResult()) {
+      classResolutionResult.forEachClassResolutionResult(
+          clazz -> foundClassConsumer.accept(clazz, context));
+    } else {
       missingClassConsumer.accept(type, context);
-      return null;
     }
-    foundClassConsumer.accept(clazz, context);
-    return clazz;
+    return classResolutionResult;
   }
 
   public FieldAccessInfoCollectionImpl getFieldAccessInfoCollection() {
@@ -783,12 +798,18 @@
     if (!type.isClassType()) {
       return;
     }
-    DexClass clazz = appView.definitionFor(type);
-    if (clazz == null) {
+    ClassResolutionResult classResolutionResult =
+        appView.contextIndependentDefinitionForWithResolutionResult(type);
+    if (!classResolutionResult.hasClassResolutionResult()) {
       missingClassConsumer.accept(type, context);
-    } else if (!clazz.isProgramClass()) {
-      classAdder.accept(clazz.asClasspathOrLibraryClass());
+      return;
     }
+    classResolutionResult.forEachClassResolutionResult(
+        clazz -> {
+          if (!clazz.isProgramClass()) {
+            classAdder.accept(clazz.asClasspathOrLibraryClass());
+          }
+        });
   }
 
   private DexProgramClass getProgramClassOrNull(DexType type, ProgramDefinition context) {
@@ -1119,7 +1140,7 @@
   }
 
   private void disableClosedWorldReasoning(DexMethod reference, ProgramMethod context) {
-    SingleResolutionResult resolutionResult =
+    SingleResolutionResult<?> resolutionResult =
         resolveMethod(reference, context, KeepReason.methodHandleReferencedIn(context));
     if (resolutionResult != null && resolutionResult.getResolvedHolder().isProgramClass()) {
       applyMinimumKeepInfoWhenLiveOrTargeted(
@@ -2086,6 +2107,9 @@
     // Update keep info.
     applyMinimumKeepInfo(clazz);
     applyMinimumKeepInfoDependentOn(new LiveClassEnqueuerEvent(clazz));
+    if (hasAlternativeLibraryDefinition(clazz)) {
+      getKeepInfo().keepClass(clazz);
+    }
 
     processAnnotations(clazz);
 
@@ -2272,7 +2296,7 @@
     return fieldResolutionResult;
   }
 
-  private SingleResolutionResult resolveMethod(
+  private SingleResolutionResult<?> resolveMethod(
       DexMethod method, ProgramDefinition context, KeepReason reason) {
     // Record the references in case they are not program types.
     MethodResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
@@ -2286,7 +2310,7 @@
     return resolutionResult.asSingleResolution();
   }
 
-  private SingleResolutionResult resolveMethod(
+  private SingleResolutionResult<?> resolveMethod(
       DexMethod method, ProgramDefinition context, KeepReason reason, boolean interfaceInvoke) {
     // Record the references in case they are not program types.
     MethodResolutionResult resolutionResult = appInfo.resolveMethod(method, interfaceInvoke);
@@ -2304,7 +2328,7 @@
 
   private void handleInvokeOfStaticTarget(
       DexMethod reference, ProgramDefinition context, KeepReason reason) {
-    SingleResolutionResult resolution = resolveMethod(reference, context, reason);
+    SingleResolutionResult<?> resolution = resolveMethod(reference, context, reason);
     if (resolution == null || resolution.getResolvedHolder().isNotProgramClass()) {
       return;
     }
@@ -2472,6 +2496,16 @@
     if (clazz == null) {
       return;
     }
+    DexClass alternativeResolutionResult =
+        appInfo()
+            .contextIndependentDefinitionForWithResolutionResult(type)
+            .toAlternativeClassWithProgramOverLibrary();
+    if (alternativeResolutionResult != null && alternativeResolutionResult.isLibraryClass()) {
+      // We are in a situation where a library class inherits from a library class, which has a
+      // program class duplicated version for low API levels.
+      recordNonProgramClass(alternativeResolutionResult, clazz);
+      return;
+    }
     if (forceProguardCompatibility) {
       // To ensure that the program works correctly we have to pin all super types and members
       // in the tree.
@@ -2758,7 +2792,7 @@
     getReachableVirtualTargets(currentClass)
         .forEach(
             (resolutionSearchKey, contexts) -> {
-              SingleResolutionResult singleResolution =
+              SingleResolutionResult<?> singleResolution =
                   appInfo
                       .resolveMethod(resolutionSearchKey.method, resolutionSearchKey.isInterface)
                       .asSingleResolution();
@@ -3000,6 +3034,9 @@
 
     // Update keep info.
     applyMinimumKeepInfo(field);
+    if (hasAlternativeLibraryDefinition(field.getHolder()) && !field.getDefinition().isPrivate()) {
+      getKeepInfo().keepField(field);
+    }
 
     // Notify analyses.
     analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context, workList));
@@ -3203,7 +3240,7 @@
       return;
     }
 
-    SingleResolutionResult resolution = resolveMethod(method, context, reason, interfaceInvoke);
+    SingleResolutionResult<?> resolution = resolveMethod(method, context, reason, interfaceInvoke);
     if (resolution == null) {
       return;
     }
@@ -3349,7 +3386,7 @@
   // Package protected due to entry point from worklist.
   void markSuperMethodAsReachable(DexMethod reference, ProgramMethod from) {
     KeepReason reason = KeepReason.targetedBySuperFrom(from);
-    SingleResolutionResult resolution = resolveMethod(reference, from, reason);
+    SingleResolutionResult<?> resolution = resolveMethod(reference, from, reason);
     if (resolution == null) {
       return;
     }
@@ -4561,6 +4598,10 @@
 
     // Update keep info.
     applyMinimumKeepInfo(method);
+    if (hasAlternativeLibraryDefinition(method.getHolder())
+        && !method.getDefinition().isPrivateMethod()) {
+      getKeepInfo().keepMethod(method);
+    }
   }
 
   private void traceNonDesugaredCode(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 484cd3a..3b96973 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -598,7 +598,7 @@
       }
 
       private void tryAndKeepMethodOnClass(DexClassAndMethod method, ProguardMemberRule rule) {
-        SingleResolutionResult resolutionResult =
+        SingleResolutionResult<?> resolutionResult =
             appView
                 .appInfo()
                 .resolveMethodOn(originalClazz, method.getReference())
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 309e92a5..8942e41 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -32,7 +32,7 @@
 import com.android.tools.r8.errors.MissingNestHostNestDesugarDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
-import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.experimental.startup.StartupOptions;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -168,7 +168,6 @@
 
   public DataResourceConsumer dataResourceConsumer;
   public FeatureSplitConfiguration featureSplitConfiguration;
-  public StartupConfiguration startupConfiguration;
 
   public List<Consumer<InspectorImpl>> outputInspections = Collections.emptyList();
 
@@ -786,6 +785,7 @@
       new KotlinOptimizationOptions();
   private final ApiModelTestingOptions apiModelTestingOptions = new ApiModelTestingOptions();
   private final DesugarSpecificOptions desugarSpecificOptions = new DesugarSpecificOptions();
+  private final StartupOptions startupOptions = new StartupOptions();
   public final TestingOptions testing = new TestingOptions();
 
   public List<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
@@ -837,6 +837,10 @@
     return openClosedInterfacesOptions;
   }
 
+  public StartupOptions getStartupOptions() {
+    return startupOptions;
+  }
+
   private static Set<String> getExtensiveLoggingFilter() {
     String property = System.getProperty("com.android.tools.r8.extensiveLoggingFilter");
     if (property != null) {
@@ -862,13 +866,24 @@
     return ImmutableSet.of();
   }
 
-  private static boolean isSystemPropertyForDevelopmentSet(String propertyName) {
+  public static boolean isSystemPropertyForDevelopmentSet(String propertyName) {
     if (Version.isDevelopmentVersion()) {
       return System.getProperty(propertyName) != null;
     }
     return false;
   }
 
+  public static String getSystemPropertyForDevelopmentOrDefault(
+      String propertyName, String defaultValue) {
+    if (Version.isDevelopmentVersion()) {
+      String propertyValue = System.getProperty(propertyName);
+      if (propertyValue != null) {
+        return propertyValue;
+      }
+    }
+    return defaultValue;
+  }
+
   private static int parseSystemPropertyForDevelopmentOrDefault(
       String propertyName, int defaultValue) {
     if (Version.isDevelopmentVersion()) {
@@ -969,9 +984,9 @@
       MachineDesugaredLibrarySpecification.empty();
 
   public TypeRewriter getTypeRewriter() {
-    return machineDesugaredLibrarySpecification.getRewriteType().isEmpty()
-        ? TypeRewriter.empty()
-        : new MachineDesugarPrefixRewritingMapper(machineDesugaredLibrarySpecification);
+    return machineDesugaredLibrarySpecification.requiresTypeRewriting()
+        ? new MachineDesugarPrefixRewritingMapper(machineDesugaredLibrarySpecification)
+        : TypeRewriter.empty();
   }
 
   public boolean relocatorCompilation = false;
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index e0863ce..9d8cedd 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -239,6 +239,10 @@
       return new ZipBuilder(zipFile);
     }
 
+    public ZipOutputStream getOutputStream() {
+      return stream;
+    }
+
     public ZipBuilder addFilesRelative(Path basePath, Collection<Path> filesToAdd)
         throws IOException {
       for (Path path : filesToAdd) {
@@ -269,6 +273,14 @@
       return this;
     }
 
+    public ZipBuilder addText(String path, String text) throws IOException {
+      ZipEntry zipEntry = new ZipEntry(path);
+      stream.putNextEntry(zipEntry);
+      stream.write(text.getBytes(StandardCharsets.UTF_8));
+      stream.closeEntry();
+      return this;
+    }
+
     public Path build() throws IOException {
       stream.close();
       return zipFile;
diff --git a/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java b/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java
index 7407b59..7dc7b3e 100644
--- a/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java
+++ b/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java
@@ -108,7 +108,7 @@
         }
 
         for (DexMethodSignature interfaceMethod : interfaceMethodsOfInterestForClass) {
-          SingleResolutionResult resolutionResult =
+          SingleResolutionResult<?> resolutionResult =
               appView
                   .appInfo()
                   .resolveMethodOnClass(implementer, interfaceMethod)
diff --git a/src/test/examples/mockito_interface/InterfaceTest.java b/src/test/examples/mockito_interface/InterfaceTest.java
index 2502b13..f6ea73c 100644
--- a/src/test/examples/mockito_interface/InterfaceTest.java
+++ b/src/test/examples/mockito_interface/InterfaceTest.java
@@ -6,15 +6,11 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(Parameterized.class)
 public class InterfaceTest {
+
   @Mock
   private Interface fld;
 
@@ -22,22 +18,23 @@
 
   private boolean flag;
 
-  @Parameterized.Parameters(name = "flag: {0}")
-  public static Boolean[] data() {
-    return new Boolean[] {true, false};
+  public static void main(String[] args) {
+    for (boolean flag : new boolean[] {true, false}) {
+      InterfaceTest test = new InterfaceTest(flag);
+      test.setUp();
+      test.test();
+    }
   }
 
   public InterfaceTest(boolean flag) {
     this.flag = flag;
   }
 
-  @Before
   public void setUp() {
     MockitoAnnotations.initMocks(this);
     user = new InterfaceUser(fld);
   }
 
-  @Test
   public void test() {
     if (flag) {
       user.consume();
diff --git a/src/test/examples/mockito_interface/keep-rules-conditional-on-mock.txt b/src/test/examples/mockito_interface/keep-rules-conditional-on-mock.txt
index edb4c08..e82228a 100644
--- a/src/test/examples/mockito_interface/keep-rules-conditional-on-mock.txt
+++ b/src/test/examples/mockito_interface/keep-rules-conditional-on-mock.txt
@@ -8,7 +8,7 @@
   public static void main(...);
 }
 
--keep @**.RunWith class * { *; }
+-keep class **.InterfaceTest { *; }
 
 # Mockito generates mocks of interface types at runtime. If interface methods are optimized, i.e.,
 # stripped out, mock-based tests will fail. So, keep all methods of interfaces if they are used as
diff --git a/src/test/examples/mockito_interface/keep-rules.txt b/src/test/examples/mockito_interface/keep-rules.txt
index b398a9b..f821af4 100644
--- a/src/test/examples/mockito_interface/keep-rules.txt
+++ b/src/test/examples/mockito_interface/keep-rules.txt
@@ -8,4 +8,4 @@
   public static void main(...);
 }
 
--keep @**.RunWith class * { *; }
\ No newline at end of file
+-keep class **.InterfaceTest { *; }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/OriginMatcher.java b/src/test/java/com/android/tools/r8/OriginMatcher.java
index 8484ea3..4f5ad82 100644
--- a/src/test/java/com/android/tools/r8/OriginMatcher.java
+++ b/src/test/java/com/android/tools/r8/OriginMatcher.java
@@ -30,4 +30,18 @@
       }
     };
   }
+
+  public static Matcher<Origin> hasPart(String part) {
+    return new OriginMatcher() {
+      @Override
+      protected boolean matchesSafely(Origin origin) {
+        return origin.part().equals(part);
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("part is not " + part);
+      }
+    };
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index b96cef4..fe500f8 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -111,6 +111,19 @@
     return self();
   }
 
+  @SafeVarargs
+  public final <E extends Throwable> R8TestCompileResult inspectMultiDex(
+      ThrowingConsumer<CodeInspector, E>... consumers) throws IOException, E {
+    Path out = state.getNewTempFolder();
+    getApp().writeToDirectory(out, OutputMode.DexIndexed);
+    consumers[0].accept(new CodeInspector(out.resolve("classes.dex"), getProguardMap()));
+    for (int i = 1; i < consumers.length; i++) {
+      consumers[i].accept(
+          new CodeInspector(out.resolve("classes" + (i + 1) + ".dex"), getProguardMap()));
+    }
+    return self();
+  }
+
   public final <E extends Throwable> R8TestCompileResult inspectGraph(
       ThrowingConsumer<GraphInspector, E> consumer) throws IOException, E {
     consumer.accept(graphInspector());
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 3a482a5..21ca89e 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -690,8 +690,8 @@
   protected Path jarTestClasses(Iterable<Class<?>> classes, List<DataResource> dataResources)
       throws IOException {
     Path jar = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
-    try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
-      addTestClassesToJar(out, classes);
+    try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(jar.toFile()))) {
+      addTestClassesToZip(out, classes);
       if (dataResources != null) {
         addDataResourcesToJar(out, dataResources);
       }
@@ -700,7 +700,7 @@
   }
 
   /** Create a temporary JAR file containing the specified test classes. */
-  protected void addTestClassesToJar(JarOutputStream out, Iterable<Class<?>> classes)
+  protected void addTestClassesToZip(ZipOutputStream out, Iterable<Class<?>> classes)
       throws IOException {
     for (Class<?> clazz : classes) {
       try (FileInputStream in =
@@ -714,7 +714,7 @@
 
   /** Create a temporary JAR file containing the specified data resources. */
   protected void addDataResourcesToJar(
-      JarOutputStream out, List<? extends DataResource> dataResources) throws IOException {
+      ZipOutputStream out, List<? extends DataResource> dataResources) throws IOException {
     try {
       for (DataResource dataResource : dataResources) {
         String name = dataResource.getName();
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index ae8a263..c4d8683 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -16,6 +16,7 @@
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.BiFunction;
+import java.util.function.Function;
 
 public abstract class TestBuilder<RR extends TestRunResult<RR>, T extends TestBuilder<RR, T>> {
 
@@ -154,6 +155,14 @@
     return addLibraryFiles(Arrays.asList(files));
   }
 
+  public T addLibraryClassFileData(byte[]... classes) {
+    return addLibraryClassFileData(Arrays.asList(classes));
+  }
+
+  public T addLibraryClassFileData(Collection<byte[]> classes) {
+    return addByteCollectionToJar("library.jar", classes, this::addLibraryFiles);
+  }
+
   public T addDefaultRuntimeLibrary(TestParameters parameters) {
     if (parameters.getBackend() == Backend.DEX) {
       addLibraryFiles(ToolHelper.getFirstSupportedAndroidJar(parameters.getApiLevel()));
@@ -181,9 +190,14 @@
   }
 
   public T addClasspathClassFileData(Collection<byte[]> classes) {
+    return addByteCollectionToJar("cp.jar", classes, this::addClasspathFiles);
+  }
+
+  private T addByteCollectionToJar(
+      String name, Collection<byte[]> classes, Function<Path, T> outputConsumer) {
     Path out;
     try {
-      out = getState().getNewTempFolder().resolve("cp.jar");
+      out = getState().getNewTempFolder().resolve(name);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
@@ -192,7 +206,7 @@
       consumer.accept(ByteDataView.of(bytes), TestBase.extractClassDescriptor(bytes), null);
     }
     consumer.finished(null);
-    return addClasspathFiles(out);
+    return outputConsumer.apply(out);
   }
 
   public final T addTestingAnnotationsAsProgramClasses() {
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index a476d13..719dbd8 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ObjectArrays;
 import java.io.File;
 import java.io.IOException;
@@ -286,7 +287,8 @@
     try {
       AndroidApp.Builder appBuilder = AndroidApp.builder();
       for (byte[] clazz : classes) {
-        appBuilder.addClassProgramData(clazz, Origin.unknown());
+        appBuilder.addClassProgramData(
+            clazz, Origin.unknown(), ImmutableSet.of(TestBase.extractClassDescriptor(clazz)));
       }
       Path path = state.getNewTempFolder().resolve("runtime-classes.jar");
       appBuilder.build().writeToZip(path, OutputMode.ClassFile);
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index cb24356..48e94f0 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -232,6 +232,10 @@
     return withApiLevelsEndingAtExcluding(AndroidApiLevel.L);
   }
 
+  public TestParametersBuilder apiLevelWithDefaultMethodsSupport() {
+    return withApiLevelsStartingAtIncluding(TestBase.apiLevelWithDefaultInterfaceMethodsSupport());
+  }
+
   public TestParametersBuilder withCustomRuntime(TestRuntime runtime) {
     assert getUnfilteredAvailableRuntimes().noneMatch(r -> r == runtime);
     customRuntimes.add(runtime);
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 5b8bb7a..7bcdfb6 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -196,6 +196,10 @@
     return System.getProperty("desugar_jdk_json_dir", "src/library_desugar");
   }
 
+  public static Path getDesugarLibJsonMinimalForTesting() {
+    return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs_minimal.json");
+  }
+
   public static Path getDesugarLibJsonForTesting() {
     return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs.json");
   }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java
index f558932..fe36c52 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkEnvironment.java
@@ -29,7 +29,11 @@
 
   public Path translateDependencyPath(String directoryName, Path location) {
     return isGolem
-        ? Paths.get("benchmarks", config.getDependencyDirectoryName(), directoryName)
+        ? getGolemDependencyRoot().resolve(directoryName)
         : location.resolve(directoryName);
   }
+
+  public Path getGolemDependencyRoot() {
+    return Paths.get("benchmarks", config.getDependencyDirectoryName());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java
index 3151343..d5f84a8 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java
@@ -3,6 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks;
 
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import org.junit.rules.TemporaryFolder;
 
 public class BenchmarkMainEntryRunner {
@@ -19,18 +25,40 @@
       throw new RuntimeException("Invalid identifier identifier: " + benchmarkName);
     }
     boolean isGolem = isGolemArg.equals("golem");
-    if (!isGolem && !isGolemArg.equals("local")) {
-      throw new RuntimeException(
-          "Invalid value for arg 3, expected 'golem' or 'local', got '" + isGolemArg + "'");
-    }
     BenchmarkCollection collection = BenchmarkCollection.computeCollection();
     BenchmarkConfig config = collection.getBenchmark(identifier);
     if (config == null) {
       throw new RuntimeException("Unknown identifier: " + identifier);
     }
+
     TemporaryFolder temp = new TemporaryFolder();
     temp.create();
-    config.run(new BenchmarkEnvironment(config, temp, isGolem));
-    temp.delete();
+    try {
+      // When running locally we still setup a "golem" environment and manually unpack dependencies.
+      BenchmarkEnvironment environment = new BenchmarkEnvironment(config, temp, true /* isGolem */);
+      if (!isGolem) {
+        // When not running with golem, the python wrapper will run the benchmark in a temp
+        // directory.
+        // In this case the argument is the absolute path to the R8 repo.
+        Path repoRoot = Paths.get(isGolemArg);
+        Path dependencyDirectory = Files.createDirectories(environment.getGolemDependencyRoot());
+        for (BenchmarkDependency dependency : config.getDependencies()) {
+          untar(repoRoot.resolve(dependency.getTarball()), dependencyDirectory);
+        }
+      }
+      System.out.println("Running benchmark");
+      config.run(environment);
+    } finally {
+      temp.delete();
+    }
+  }
+
+  private static void untar(Path tarball, Path target) throws IOException {
+    ProcessBuilder builder =
+        new ProcessBuilder("tar", "zxf", tarball.toString(), "-C", target.toString());
+    ProcessResult result = ToolHelper.runProcess(builder);
+    if (result.exitCode != 0) {
+      throw new IOException(result.toString());
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index f8b16df..ecfb059 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -224,6 +224,7 @@
 
                   TestBase.testForD8(environment.getTemp(), Backend.DEX)
                       .addProgramFiles(programOutputs)
+                      .addLibraryFiles(dump.getLibraryArchive())
                       .setMinApi(dumpProperties.getMinApi())
                       .benchmarkCompile(results.getSubResults(builder.nameForMergePart()));
                 });
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/ImpreciseFrameAtCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/ImpreciseFrameAtCatchHandlerTest.java
new file mode 100644
index 0000000..f734f79
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/ImpreciseFrameAtCatchHandlerTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2022, 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.cf.stackmap;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.IntBox;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ImpreciseFrameAtCatchHandlerTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(getTransformedMain())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Caught");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Caught");
+  }
+
+  private static byte[] getTransformedMain() throws IOException {
+    IntBox witness = new IntBox();
+    byte[] transformed =
+        transformer(Main.class)
+            .addMethodTransformer(
+                new MethodTransformer() {
+
+                  @Override
+                  public void visitFrame(
+                      int type, int numLocal, Object[] local, int numStack, Object[] stack) {
+                    if (getContext().method.getMethodName().equals("main")
+                        && numLocal == 0
+                        && numStack == 1
+                        && stack[0].equals(binaryName(Exception.class))) {
+                      stack = new Object[] {binaryName(Object.class)};
+                      witness.increment();
+                    }
+                    super.visitFrame(type, numLocal, local, numStack, stack);
+                  }
+                })
+            .transform();
+    assertEquals(1, witness.get());
+    return transformed;
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      try {
+        System.out.println(args[0]);
+      } catch (Exception e) {
+        System.out.println("Caught");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/SharedExceptionTypeTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/SharedExceptionTypeTest.java
new file mode 100644
index 0000000..1f30f40
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/SharedExceptionTypeTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2022, 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.cf.stackmap;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SharedExceptionTypeTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String EXPECTED = "Hello World!";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public SharedExceptionTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addInnerClasses(SharedExceptionTypeTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addInnerClasses(SharedExceptionTypeTest.class)
+        .setMinApi(AndroidApiLevel.B)
+        .addOptionsModification(options -> options.testing.readInputStackMaps = true)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public static class ExceptionSuper extends RuntimeException {
+
+    public void printError() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class ExceptionA extends ExceptionSuper {}
+
+  public static class ExceptionB extends ExceptionSuper {}
+
+  public static class Main {
+
+    public static void foo(String[] args) {
+      if (args.length == 0) {
+        throw new ExceptionA();
+      } else if (args.length == 1) {
+        throw new ExceptionB();
+      } else {
+        throw new RuntimeException("FOO BAR");
+      }
+    }
+
+    public static void main(String[] args) {
+      try {
+        foo(args);
+      } catch (ExceptionA | ExceptionB e) {
+        e.printError();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingInD8WithClInitOnCCTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingInD8WithClInitOnCCTest.java
new file mode 100644
index 0000000..4e955d0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingInD8WithClInitOnCCTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2022, 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.classmerging.horizontal;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a regression test for b/227791663 */
+@RunWith(Parameterized.class)
+public class HorizontalClassMergingInD8WithClInitOnCCTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMode(CompilationMode.RELEASE)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> {
+              options.horizontalClassMergerOptions().enable();
+              options.horizontalClassMergerOptions().setRestrictToSynthetics();
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0", "1");
+  }
+
+  public interface A {
+
+    int value = Main.getAndIncrement();
+
+    static int getValue() {
+      return value;
+    }
+  }
+
+  public interface B {
+
+    int value = Main.getAndIncrement();
+
+    static int getValue() {
+      return value;
+    }
+  }
+
+  public static class Main {
+
+    private static int value = 0;
+
+    public static int getAndIncrement() {
+      return value++;
+    }
+
+    public static void main(String[] args) {
+      System.out.println(A.getValue());
+      System.out.println(B.getValue());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java
index d2e91ec..ae8d0b6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingWithStartupClassesTest.java
@@ -44,12 +44,14 @@
         .addOptionsModification(
             options -> {
               DexItemFactory dexItemFactory = options.dexItemFactory();
-              options.startupConfiguration =
-                  new StartupConfiguration(
-                      startupClasses.stream()
-                          .map(clazz -> toDexType(clazz, dexItemFactory))
-                          .collect(Collectors.toList()),
-                      Collections.emptyList());
+              options
+                  .getStartupOptions()
+                  .setStartupConfiguration(
+                      new StartupConfiguration(
+                          startupClasses.stream()
+                              .map(clazz -> toDexType(clazz, dexItemFactory))
+                              .collect(Collectors.toList()),
+                          Collections.emptyList()));
             })
         .addHorizontallyMergedClassesInspector(
             inspector ->
diff --git a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
index a292274..265bb19 100644
--- a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
@@ -55,9 +55,6 @@
         .addAll(
             findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR)),
             ContinuousSteppingTest::fromAndroidN)
-        // TODO(b/79911828) Investigate timeout issues for Android O examples.
-        //  .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR)),
-        //      ContinuousSteppingTest::fromAndroidO)
         .build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index 3b41b98..6ce91e1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import org.hamcrest.CoreMatchers;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -130,7 +131,13 @@
   }
 
   private void assertCorrect(CodeInspector inspector) {
-    inspector.allClasses().forEach(clazz -> assertThat(clazz.getOriginalName(), startsWith("j$.")));
+    inspector
+        .allClasses()
+        .forEach(
+            clazz ->
+                assertThat(
+                    clazz.getOriginalName(),
+                    CoreMatchers.anyOf(startsWith("j$."), startsWith("java."))));
     assertThat(inspector.clazz("j$.time.Clock"), isPresent());
     // Above N the following classes are removed instead of being desugared.
     if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FunctionOnlyTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FunctionOnlyTest.java
new file mode 100644
index 0000000..0b443a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FunctionOnlyTest.java
@@ -0,0 +1,277 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.L8Command;
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.BinaryOperator;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.DoublePredicate;
+import java.util.function.DoubleSupplier;
+import java.util.function.Function;
+import java.util.function.IntSupplier;
+import java.util.function.LongConsumer;
+import java.util.function.LongSupplier;
+import java.util.function.Supplier;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FunctionOnlyTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(" true true true", "2", "false", "3", "true", "5", "42.0");
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values());
+  }
+
+  public FunctionOnlyTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testFunctionCompositionD8() throws Exception {
+    testForD8()
+        .addLibraryFiles(getLibraryFile())
+        .setMinApi(parameters.getApiLevel())
+        .addInnerClasses(getClass())
+        .enableCoreLibraryDesugaring(minimalConfiguration())
+        .compile()
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  private LibraryDesugaringTestConfiguration minimalConfiguration() {
+    if (!isJDK11DesugaredLibrary()) {
+      return LibraryDesugaringTestConfiguration.builder()
+          .setMinApi(parameters.getApiLevel())
+          .setMode(CompilationMode.RELEASE)
+          .withKeepRuleConsumer()
+          .build();
+    }
+    return LibraryDesugaringTestConfiguration.builder()
+        .setMinApi(parameters.getApiLevel())
+        .addDesugaredLibraryConfiguration(
+            StringResource.fromFile(ToolHelper.getDesugarLibJsonMinimalForTesting()))
+        .setMode(shrinkDesugaredLibrary ? CompilationMode.RELEASE : CompilationMode.DEBUG)
+        .withKeepRuleConsumer()
+        .build();
+  }
+
+  @Test
+  public void testFunctionCompositionD8Cf2Cf() throws Exception {
+    Assume.assumeTrue(isJDK11DesugaredLibrary());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    Path jar =
+        testForD8(Backend.CF)
+            .addLibraryFiles(getLibraryFile())
+            .setMinApi(parameters.getApiLevel())
+            .addInnerClasses(FunctionOnlyTest.class)
+            .enableCoreLibraryDesugaring(
+                parameters.getApiLevel(),
+                keepRuleConsumer,
+                StringResource.fromFile(ToolHelper.getDesugarLibJsonMinimalForTesting()))
+            .compile()
+            .writeToZip();
+    String desugaredLibraryKeepRules = "";
+    if (shrinkDesugaredLibrary && keepRuleConsumer.get() != null) {
+      // Collection keep rules is only implemented in the DEX writer.
+      assertEquals(0, keepRuleConsumer.get().length());
+      desugaredLibraryKeepRules = "-keep class java.util.function.* { *; }";
+    }
+    if (parameters.getRuntime().isDex()) {
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              (api, keep, shrink) ->
+                  buildDesugaredLibrary(
+                      api,
+                      keep,
+                      shrink,
+                      ImmutableList.of(),
+                      opt ->
+                          opt.setDesugaredLibrarySpecification(
+                              DesugaredLibrarySpecificationParser
+                                  .parseDesugaredLibrarySpecification(
+                                      StringResource.fromFile(
+                                          ToolHelper.getDesugarLibJsonMinimalForTesting()),
+                                      opt.itemFactory,
+                                      opt.reporter,
+                                      true,
+                                      parameters.getApiLevel().getLevel()))),
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+
+    } else {
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(getDesugaredLibraryInCFMinimal(parameters, options -> {}))
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    }
+  }
+
+  public Path getDesugaredLibraryInCFMinimal(
+      TestParameters parameters, Consumer<InternalOptions> configurationForLibraryCompilation)
+      throws IOException, CompilationFailedException {
+    Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
+    L8Command.Builder l8Builder =
+        L8Command.builder()
+            .addLibraryFiles(getLibraryFile())
+            .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+            .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+            .setMode(CompilationMode.DEBUG)
+            .addDesugaredLibraryConfiguration(
+                StringResource.fromFile(ToolHelper.getDesugarLibJsonMinimalForTesting()))
+            .setMinApiLevel(parameters.getApiLevel().getLevel())
+            .setOutput(desugaredLib, OutputMode.ClassFile);
+    ToolHelper.runL8(l8Builder.build(), configurationForLibraryCompilation);
+    return desugaredLib;
+  }
+
+  @Test
+  public void testFunctionCompositionR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(getLibraryFile())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Executor.class)
+        .addInnerClasses(getClass())
+        .enableCoreLibraryDesugaring(minimalConfiguration())
+        .allowStdoutMessages()
+        .compile()
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      Function<Executor.Object1, Executor.Object3> function =
+          FunctionClass.composeFunction(Executor.Object2::new, Executor.Object3::new);
+      System.out.println(function.apply(new Executor.Object1()).toString());
+      BiFunction<String, String, Character> biFunction =
+          FunctionClass.composeBiFunctions(
+              (String i, String j) -> i + j, (String s) -> s.charAt(1));
+      System.out.println(biFunction.apply("1", "2"));
+      BooleanSupplier booleanSupplier =
+          () -> FunctionClass.composeBoolSuppliers(() -> true, () -> false).get();
+      System.out.println(booleanSupplier.getAsBoolean());
+      LongConsumer longConsumer = FunctionClass.composeLong(() -> 1L, System.out::println);
+      longConsumer.accept(2L);
+      DoublePredicate doublePredicate =
+          FunctionClass.composePredicate(d -> d > 1.0, d -> d == 2.0, d -> d < 3.0);
+      System.out.println(doublePredicate.test(2.0));
+      System.out.println(FunctionClass.extractInt(() -> 5));
+      System.out.println(FunctionClass.getDoubleSupplier().get());
+    }
+
+    static class Object1 {}
+
+    static class Object2 {
+
+      private Executor.Object1 field;
+
+      Object2(Executor.Object1 o) {
+        this.field = o;
+      }
+    }
+
+    static class Object3 {
+
+      private Executor.Object2 field;
+
+      Object3(Executor.Object2 o) {
+        this.field = o;
+      }
+
+      @Override
+      public String toString() {
+        return " "
+            + (field.field.getClass() == Executor.Object1.class)
+            + " "
+            + (field.getClass() == Executor.Object2.class)
+            + " "
+            + (getClass() == Executor.Object3.class);
+      }
+    }
+  }
+
+  static class FunctionClass {
+
+    public static <T, Q, R> Function<T, R> composeFunction(Function<T, Q> f1, Function<Q, R> f2) {
+      return f1.andThen(f2);
+    }
+
+    public static <T, R> BiFunction<T, T, R> composeBiFunctions(
+        BinaryOperator<T> operator, Function<T, R> function) {
+      return operator.andThen(function);
+    }
+
+    // BooleanSupplier is not a wrapped type, so it can't be placed on the boundary.
+    public static Supplier<Boolean> composeBoolSuppliers(
+        Supplier<Boolean> supplier1, Supplier<Boolean> supplier2) {
+      BooleanSupplier wrap1 = supplier1::get;
+      BooleanSupplier wrap2 = supplier2::get;
+      return () -> wrap1.getAsBoolean() && wrap2.getAsBoolean();
+    }
+
+    // LongSupplier is not a wrapped type, so it can't be placed on the boundary.
+    public static LongConsumer composeLong(Supplier<Long> supplier, LongConsumer consumer) {
+      LongSupplier wrap = supplier::get;
+      return l -> consumer.accept(l + wrap.getAsLong());
+    }
+
+    public static DoublePredicate composePredicate(
+        DoublePredicate predicate1, DoublePredicate predicate2, DoublePredicate predicate3) {
+      return predicate1.and(predicate2).and(predicate3);
+    }
+
+    // IntSupplier is not a wrapped type, so it can't be placed on the boundary.
+    public static int extractInt(Supplier<Integer> supplier) {
+      IntSupplier wrap = supplier::get;
+      return wrap.getAsInt();
+    }
+
+    // DoubleSupplier is not a wrapped type, so it can't be placed on the boundary.
+    public static Supplier<Double> getDoubleSupplier() {
+      DoubleSupplier supplier = () -> 42.0;
+      return supplier::getAsDouble;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
index ef978a7..e05e09a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -69,21 +68,15 @@
   private final boolean shrinkDesugaredLibrary = false;
   private final Path androidJar;
 
-  @Parameters(name = "{0}, libraryDesugarJavaUtilObjects: {1}")
+  @Parameters(name = "{0}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
-        BooleanUtils.values());
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
   }
 
-  public ObjectsTest(TestParameters parameters, boolean libraryDesugarJavaUtilObjects) {
+  public ObjectsTest(TestParameters parameters) {
     this.parameters = parameters;
-    if (libraryDesugarJavaUtilObjects) {
-      Assume.assumeTrue(
-          "The alternative 3 configuration is available only in JDK 11 desugared library.",
-          isJDK11DesugaredLibrary());
-    }
-    this.libraryDesugarJavaUtilObjects = libraryDesugarJavaUtilObjects;
+    this.libraryDesugarJavaUtilObjects = isJDK11DesugaredLibrary();
     this.androidJar =
         ToolHelper.getAndroidJar(
             Ordered.max(parameters.getApiLevel(), getRequiredCompilationAPILevel()));
@@ -92,10 +85,7 @@
   DesugaredLibrarySpecification desugaredLibrarySpecification(
       InternalOptions options, boolean libraryCompilation, TestParameters parameters) {
     return DesugaredLibrarySpecificationParser.parseDesugaredLibrarySpecification(
-        StringResource.fromFile(
-            libraryDesugarJavaUtilObjects
-                ? ToolHelper.getDesugarLibJsonForTestingAlternative3()
-                : ToolHelper.getDesugarLibJsonForTesting()),
+        StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()),
         options.dexItemFactory(),
         options.reporter,
         libraryCompilation,
diff --git a/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/TwrSuppressedExceptionsTest.java b/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/TwrSuppressedExceptionsTest.java
index e80af63..443c7ed 100644
--- a/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/TwrSuppressedExceptionsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/suppressedexceptions/TwrSuppressedExceptionsTest.java
@@ -20,6 +20,7 @@
 import java.io.Closeable;
 import java.util.List;
 import java.util.stream.Collectors;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -58,6 +59,7 @@
   }
 
   @Test
+  @Ignore("b/228587114")
   public void testD8() throws Exception {
     testForDesugaring(parameters)
         .addProgramClasses(TestClass.class, MyClosable.class)
@@ -94,6 +96,7 @@
   }
 
   @Test
+  @Ignore("b/228587114")
   public void testR8() throws Exception {
     assumeTrue(
         "R8 does not desugar CF so only run the high API variant.",
diff --git a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
index 10d15e9..2bee502 100644
--- a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -16,6 +16,7 @@
 import java.io.IOException;
 import java.util.List;
 import java.util.jar.JarFile;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -67,6 +68,7 @@
   }
 
   @Test
+  @Ignore("b/228587114")
   public void testD8() throws Exception {
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
@@ -89,6 +91,7 @@
   }
 
   @Test
+  @Ignore("b/228587114")
   public void testR8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/ProgramAndLibraryDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/ProgramAndLibraryDefinitionTest.java
new file mode 100644
index 0000000..d0c2f5c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/ProgramAndLibraryDefinitionTest.java
@@ -0,0 +1,231 @@
+// Copyright (c) 2022, 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.resolution.interfacetargets;
+
+import static com.android.tools.r8.resolution.interfacetargets.ProgramAndLibraryDefinitionTest.ClassTestParam.DEFINED_WITH_METHOD;
+import static com.android.tools.r8.resolution.interfacetargets.ProgramAndLibraryDefinitionTest.ClassTestParam.NOT_DEFINED;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import java.util.List;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ProgramAndLibraryDefinitionTest extends TestBase {
+
+  public enum ClassTestParam {
+    NOT_DEFINED,
+    DEFINED_NO_METHOD,
+    DEFINED_WITH_METHOD
+  }
+
+  private final TestParameters parameters;
+  private final ClassTestParam aInLibrary;
+  private final ClassTestParam bInLibrary;
+  private final ClassTestParam aInProgram;
+  private final ClassTestParam bInProgram;
+
+  @Parameters(name = "{0}, aInLibrary: {1}, bInLibrary: {2}, aInProgram: {3}, bInProgram: {4}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDefaultDexRuntime()
+            .withDefaultCfRuntime()
+            .apiLevelWithDefaultMethodsSupport()
+            .build(),
+        ClassTestParam.values(),
+        ClassTestParam.values(),
+        ClassTestParam.values(),
+        ClassTestParam.values());
+  }
+
+  public ProgramAndLibraryDefinitionTest(
+      TestParameters parameters,
+      ClassTestParam aInLibrary,
+      ClassTestParam bInLibrary,
+      ClassTestParam aInProgram,
+      ClassTestParam bInProgram) {
+    this.parameters = parameters;
+    this.aInLibrary = aInLibrary;
+    this.bInLibrary = bInLibrary;
+    this.aInProgram = aInProgram;
+    this.bInProgram = bInProgram;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8FullTestBuilder testBuilder =
+        testForR8(parameters.getBackend())
+            .addDefaultRuntimeLibrary(parameters)
+            .addProgramClasses(Main.class, Implementer.class, I.class)
+            .setMinApi(parameters.getApiLevel())
+            .addDontWarn(A.class, B.class)
+            .addKeepMainRule(Main.class)
+            .noMinification()
+            .allowUnusedDontWarnPatterns();
+    byte[] libraryA = getAFromClassTestParam(this.aInLibrary);
+    addIfNotNull(libraryA, testBuilder::addLibraryClassFileData);
+    byte[] libraryB = getBFromClassTestParam(bInLibrary);
+    addIfNotNull(libraryB, testBuilder::addLibraryClassFileData);
+    addIfNotNull(getAFromClassTestParam(aInProgram), testBuilder::addProgramClassFileData);
+    addIfNotNull(getBFromClassTestParam(bInProgram), testBuilder::addProgramClassFileData);
+    R8TestCompileResult compileResult = testBuilder.compile();
+    if (libraryA != null) {
+      compileResult.addRunClasspathClassFileData(libraryA);
+    }
+    if (libraryB != null) {
+      compileResult.addRunClasspathClassFileData(libraryB);
+    }
+    R8TestRunResult runResult = compileResult.run(parameters.getRuntime(), Main.class);
+    if (isExpectedToFailWithNoClassDefError()) {
+      runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+    } else if (isExpectedToFailWithNoSuchMethodError()) {
+      runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+    } else if (isExpectedToFailWithICCE()) {
+      runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+    } else if (isDefinedOnAProgram() || (isDefinedOnALibrary() && !isAInProgram())) {
+      runResult.assertSuccessWithOutputLines("A::foo");
+    } else {
+      assert isDefinedOnBProgram() || (isDefinedOnBLibrary() && !isBInProgram());
+      runResult.assertSuccessWithOutputLines("B::foo");
+    }
+  }
+
+  private boolean isAInProgram() {
+    return aInProgram != NOT_DEFINED;
+  }
+
+  private boolean isBInProgram() {
+    return bInProgram != NOT_DEFINED;
+  }
+
+  private boolean isAInLibrary() {
+    return aInLibrary != NOT_DEFINED;
+  }
+
+  private boolean isBInLibrary() {
+    return bInLibrary != NOT_DEFINED;
+  }
+
+  private boolean isDefinedOnAProgram() {
+    return aInProgram == DEFINED_WITH_METHOD;
+  }
+
+  private boolean isDefinedOnBProgram() {
+    return bInProgram == DEFINED_WITH_METHOD;
+  }
+
+  private boolean isDefinedOnALibrary() {
+    return aInLibrary == DEFINED_WITH_METHOD;
+  }
+
+  private boolean isDefinedOnBLibrary() {
+    return bInLibrary == DEFINED_WITH_METHOD;
+  }
+
+  private boolean isExpectedToFailWithNoClassDefError() {
+    return (!isAInLibrary() && !isAInProgram()) || (!isBInLibrary() && !isBInProgram());
+  }
+
+  private boolean isExpectedToFailWithNoSuchMethodError() {
+    boolean notDefinedInProgram = !isDefinedOnAProgram() && !isDefinedOnBProgram();
+    boolean notDefinedInLibrary = !isDefinedOnALibrary() && !isDefinedOnBLibrary();
+    if (notDefinedInLibrary && notDefinedInProgram) {
+      return true;
+    }
+    if (notDefinedInProgram) {
+      // TODO(b/214382176): Currently, a program definition will shadow the library definition and
+      //  R8 will optimize the interfaces away.
+      if (isDefinedOnALibrary() && isDefinedOnBLibrary()) {
+        return isAInProgram() && isBInProgram();
+      } else if (isDefinedOnALibrary()) {
+        return isAInProgram();
+      } else {
+        assert isDefinedOnBLibrary();
+        return isBInProgram();
+      }
+    }
+    return false;
+  }
+
+  private boolean isExpectedToFailWithICCE() {
+    if (isDefinedOnAProgram() && isDefinedOnBProgram()) {
+      return true;
+    }
+    if (!isAInProgram() && !isBInProgram()) {
+      return isDefinedOnALibrary() && isDefinedOnBLibrary();
+    }
+    if (isDefinedOnALibrary() && !isAInProgram() && isDefinedOnBProgram()) {
+      return true;
+    }
+    if (isDefinedOnBLibrary() && !isBInProgram() && isDefinedOnAProgram()) {
+      return true;
+    }
+    return false;
+  }
+
+  private void addIfNotNull(byte[] clazz, Consumer<byte[]> consumer) {
+    if (clazz != null) {
+      consumer.accept(clazz);
+    }
+  }
+
+  private byte[] getAFromClassTestParam(ClassTestParam param) throws Exception {
+    switch (param) {
+      case NOT_DEFINED:
+        return null;
+      case DEFINED_NO_METHOD:
+        return transformer(A.class).removeMethods(MethodPredicate.onName("foo")).transform();
+      default:
+        assert param == DEFINED_WITH_METHOD;
+        return transformer(A.class).transform();
+    }
+  }
+
+  private byte[] getBFromClassTestParam(ClassTestParam param) throws Exception {
+    switch (param) {
+      case NOT_DEFINED:
+        return null;
+      case DEFINED_NO_METHOD:
+        return transformer(B.class).removeMethods(MethodPredicate.onName("bar")).transform();
+      default:
+        assert param == DEFINED_WITH_METHOD;
+        return transformer(B.class).renameMethod(MethodPredicate.onName("bar"), "foo").transform();
+    }
+  }
+
+  public interface A {
+
+    default void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  public interface B {
+
+    default void /* foo */ bar() { // Will be renamed to foo.
+      System.out.println("B::foo");
+    }
+  }
+
+  public interface I extends A {}
+
+  public static class Implementer implements I, B {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new Implementer().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index d348b54..b1e37b4 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -126,6 +126,16 @@
   }
 
   @Test
+  public void testWindowsLineEndings() throws IOException {
+    ActualRetraceBotStackTrace stackTrace = new ActualRetraceBotStackTrace();
+    runTest(
+        stackTrace.mapping().replace("\n", "\r\n"),
+        StringUtils.joinLines(stackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(stackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR);
+  }
+
+  @Test
   public void testRegularExpression() throws IOException {
     ActualRetraceBotStackTrace stackTrace = new ActualRetraceBotStackTrace();
     runTest(
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceWhitespaceTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceWhitespaceTest.java
new file mode 100644
index 0000000..7b9c08d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceWhitespaceTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2022, 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.retrace;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceWhitespaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  private final String MAPPING =
+      StringUtils.lines(
+          " foo -> bar:", "void someMethod() -> a", " baz -> qux:", "void someOtherMethod() -> b");
+  private final List<String> STACKTRACE =
+      ImmutableList.of(" at bar.a(SourceFile)", " at qux.b(SourceFile)");
+  private final String EXPECTED =
+      StringUtils.lines(" at foo.someMethod(foo.java)", " at baz.someOtherMethod(baz.java)");
+
+  private Path mappingFile;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public RetraceWhitespaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Before
+  public void before() throws Exception {
+    mappingFile = temp.newFile("mapping.txt").toPath();
+    Files.write(mappingFile, MAPPING.getBytes());
+  }
+
+  @Test
+  public void testR8Retrace() {
+    // TODO(b/228154323): R8 Retrace should not be white space sensitive.
+    assertThrows(
+        InvalidMappingFileException.class,
+        () ->
+            Retrace.run(
+                RetraceCommand.builder()
+                    .setProguardMapProducer(ProguardMapProducer.fromPath(mappingFile))
+                    .setStackTrace(STACKTRACE)
+                    .setRetracedStackTraceConsumer(lines -> {})
+                    .build()));
+  }
+
+  @Test
+  public void testPGRetrace() throws Exception {
+    Path stackTraceFile = temp.newFile("stacktrace.txt").toPath();
+    Files.write(stackTraceFile, StringUtils.joinLines(STACKTRACE).getBytes(StandardCharsets.UTF_8));
+    List<String> command = new ArrayList<>();
+    command.add(ProguardVersion.V7_0_0.getRetraceScript().toString());
+    command.add(mappingFile.toString());
+    command.add(stackTraceFile.toString());
+    ProcessBuilder builder = new ProcessBuilder(command);
+    ProcessResult processResult = ToolHelper.runProcess(builder);
+    assertEquals(0, processResult.exitCode);
+    assertEquals(EXPECTED, processResult.stdout);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
index c688182..a0029a0 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
@@ -4,50 +4,45 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.OriginMatcher.hasPart;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DataResourceProvider;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DiagnosticsChecker;
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ProgramResourceProvider;
-import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
-import com.google.common.io.CharSource;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.jar.JarOutputStream;
-import java.util.zip.ZipEntry;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
@@ -60,7 +55,7 @@
 
     public static void main(String[] args) {
       try {
-        Class bClass = Class.forName(buildClassName("B"));
+        Class.forName(buildClassName("B"));
         System.out.println("YES");
       } catch (ClassNotFoundException e) {
         System.out.println("NO");
@@ -70,113 +65,139 @@
 
   static class B {}
 
-  private Backend backend;
+  enum LibraryType {
+    JAR_WITH_RULES,
+    AAR_WITH_RULES,
+    AAR_WITH_RULES_ONLY_IN_JAR,
+    AAR_WITH_RULES_BOTH_IN_JAR_AND_IN_AAR;
 
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+    boolean isAar() {
+      return this != JAR_WITH_RULES;
+    }
+
+    boolean hasRulesInJar() {
+      return this != AAR_WITH_RULES;
+    }
+
+    boolean hasRulesInAar() {
+      return this == AAR_WITH_RULES || this == AAR_WITH_RULES_BOTH_IN_JAR_AND_IN_AAR;
+    }
   }
 
-  public LibraryProvidedProguardRulesTest(Backend backend) {
-    this.backend = backend;
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public LibraryType libraryType;
+
+  @Parameters(name = "{0} AAR: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), LibraryType.values());
   }
 
-  private void addTextJarEntry(JarOutputStream out, String name, String content) throws Exception {
-    out.putNextEntry(new ZipEntry(name));
-    ByteStreams.copy(
-        CharSource.wrap(content).asByteSource(StandardCharsets.UTF_8).openBufferedStream(), out);
-    out.closeEntry();
-  }
-
-  private AndroidApp runTest(List<String> rules, DiagnosticsHandler handler) throws Exception {
-    Path jar = temp.newFile("test.jar").toPath();
-    try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
-      addTestClassesToJar(out, ImmutableList.of(A.class, B.class));
-      for (int i =  0; i < rules.size(); i++) {
+  private Path buildLibrary(List<String> rules) throws Exception {
+    ZipBuilder jarBuilder =
+        ZipBuilder.builder(temp.newFile(libraryType.isAar() ? "classes.jar" : "test.jar").toPath());
+    addTestClassesToZip(jarBuilder.getOutputStream(), ImmutableList.of(A.class, B.class));
+    if (libraryType.hasRulesInJar()) {
+      for (int i = 0; i < rules.size(); i++) {
         String name = "META-INF/proguard/jar" + (i == 0 ? "" : i) + ".rules";
-        addTextJarEntry(out, name, rules.get(i));
+        jarBuilder.addText(name, rules.get(i));
       }
     }
-
-    try {
-      R8Command.Builder builder =
-          (handler != null ? R8Command.builder(handler) : R8Command.builder()).addProgramFiles(jar);
-      if (backend == Backend.DEX) {
-        builder
-            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-            .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
-      } else {
-        assert backend == Backend.CF;
-        builder
-            .setProgramConsumer(ClassFileConsumer.emptyConsumer())
-            .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+    if (libraryType.isAar()) {
+      Path jar = jarBuilder.build();
+      String allRules = StringUtils.lines(rules);
+      ZipBuilder aarBuilder = ZipBuilder.builder(temp.newFile("test.aar").toPath());
+      aarBuilder.addFilesRelative(jar.getParent(), jar);
+      if (libraryType.hasRulesInAar()) {
+        aarBuilder.addText("proguard.txt", allRules);
       }
-      return ToolHelper.runR8(builder.build());
-    } catch (CompilationFailedException e) {
-      assertNotNull(handler);
-      return null;
+      return aarBuilder.build();
+    } else {
+      return jarBuilder.build();
     }
   }
 
-  private AndroidApp runTest(String rules, DiagnosticsHandler handler) throws Exception {
-    return runTest(ImmutableList.of(rules), handler);
+  private CodeInspector runTest(List<String> rules) throws Exception {
+    return testForR8(parameters.getBackend())
+        .addProgramFiles(buildLibrary(rules))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspector();
   }
 
+  private CodeInspector runTest(String rules) throws Exception {
+    return runTest(ImmutableList.of(rules));
+  }
 
   @Test
   public void keepOnlyA() throws Exception {
-    AndroidApp app = runTest("-keep class " + A.class.getTypeName() +" {}", null);
-    CodeInspector inspector = new CodeInspector(app);
-    assertThat(inspector.clazz(A.class), isPresent());
+    CodeInspector inspector = runTest("-keep class " + A.class.getTypeName() + " {}");
+    // TODO(b/228319861): Read Proguard rules from AAR's.
+    assertThat(inspector.clazz(A.class), notIf(isPresent(), libraryType.isAar()));
     assertThat(inspector.clazz(B.class), not(isPresent()));
   }
 
   @Test
   public void keepOnlyB() throws Exception {
-    AndroidApp app = runTest("-keep class **B {}", null);
-    CodeInspector inspector = new CodeInspector(app);
+    CodeInspector inspector = runTest("-keep class **B {}");
     assertThat(inspector.clazz(A.class), not(isPresent()));
-    assertThat(inspector.clazz(B.class), isPresent());
+    // TODO(b/228319861): Read Proguard rules from AAR's.
+    assertThat(inspector.clazz(B.class), notIf(isPresent(), libraryType.isAar()));
   }
 
   @Test
   public void keepBoth() throws Exception {
-    AndroidApp app = runTest("-keep class ** {}", null);
-    CodeInspector inspector = new CodeInspector(app);
-    assertThat(inspector.clazz(A.class), isPresent());
-    assertThat(inspector.clazz(B.class), isPresent());
+    CodeInspector inspector = runTest("-keep class ** {}");
+    // TODO(b/228319861): Read Proguard rules from AAR's.
+    assertThat(inspector.clazz(A.class), notIf(isPresent(), libraryType.isAar()));
+    assertThat(inspector.clazz(B.class), notIf(isPresent(), libraryType.isAar()));
   }
 
   @Test
   public void multipleFiles() throws Exception {
-    AndroidApp app = runTest(ImmutableList.of("-keep class **A {}", "-keep class **B {}"), null);
-    CodeInspector inspector = new CodeInspector(app);
-    assertThat(inspector.clazz(A.class), isPresent());
-    assertThat(inspector.clazz(B.class), isPresent());
-  }
-
-  private void checkOrigin(Origin origin) {
-    assertTrue(origin instanceof ArchiveEntryOrigin);
-    assertEquals(origin.part(), "META-INF/proguard/jar.rules");
-    assertTrue(origin.parent() instanceof PathOrigin);
+    CodeInspector inspector = runTest(ImmutableList.of("-keep class **A {}", "-keep class **B {}"));
+    // TODO(b/228319861): Read Proguard rules from AAR's.
+    assertThat(inspector.clazz(A.class), notIf(isPresent(), libraryType.isAar()));
+    assertThat(inspector.clazz(B.class), notIf(isPresent(), libraryType.isAar()));
   }
 
   @Test
   public void syntaxError() throws Exception {
-    DiagnosticsChecker checker = new DiagnosticsChecker();
-    AndroidApp app = runTest("error", checker);
-    assertNull(app);
-    DiagnosticsChecker.checkDiagnostic(
-        checker.errors.get(0), this::checkOrigin, 1, 1, "Expected char '-'");
+    // TODO(b/228319861): Read Proguard rules from AAR's.
+    assumeTrue(!libraryType.isAar());
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramFiles(buildLibrary(ImmutableList.of("error")))
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorThatMatches(
+                            allOf(
+                                diagnosticMessage(containsString("Expected char '-'")),
+                                diagnosticOrigin(hasPart("META-INF/proguard/jar.rules")),
+                                diagnosticOrigin(instanceOf(ArchiveEntryOrigin.class))))));
   }
 
   @Test
   public void includeError() throws Exception {
-    DiagnosticsChecker checker = new DiagnosticsChecker();
-    AndroidApp app = runTest("-include other.rules", checker);
-    assertNull(app);
-    DiagnosticsChecker.checkDiagnostic(checker.errors.get(0), this::checkOrigin, 1, 10,
-        "Options with file names are not supported");
+    // TODO(b/228319861): Read Proguard rules from AAR's.
+    assumeTrue(!libraryType.isAar());
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramFiles(buildLibrary(ImmutableList.of("-include other.rules")))
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorThatMatches(
+                            diagnosticMessage(
+                                containsString("Options with file names are not supported")))));
   }
 
   static class TestProvider implements ProgramResourceProvider, DataResourceProvider {
@@ -207,27 +228,20 @@
 
   @Test
   public void throwingDataResourceProvider() throws Exception {
-    DiagnosticsChecker checker = new DiagnosticsChecker();
-    try {
-      R8Command.Builder builder =
-          R8Command.builder(checker).addProgramResourceProvider(new TestProvider());
-      if (backend == Backend.DEX) {
-        builder
-            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-            .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
-      } else {
-        assert backend == Backend.CF;
-        builder
-            .setProgramConsumer(ClassFileConsumer.emptyConsumer())
-            .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
-      }
-      builder.build();
-      fail("Should not succeed");
-    } catch (CompilationFailedException e) {
-      DiagnosticsChecker.checkDiagnostic(
-          checker.errors.get(0),
-          origin -> assertSame(origin, Origin.unknown()),
-          "Cannot provide data resources after all");
-    }
+    // TODO(b/228319861): Read Proguard rules from AAR's.
+    assumeTrue(!libraryType.isAar());
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramResourceProviders(new TestProvider())
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorThatMatches(
+                            allOf(
+                                diagnosticMessage(
+                                    containsString("Cannot provide data resources after all")),
+                                diagnosticOrigin(is(Origin.unknown()))))));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
index d31da79..8a4113a 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
@@ -51,7 +51,7 @@
         testForR8(parameters.getBackend())
             .addProgramFiles(MOCKITO_INTERFACE_JAR)
             .addKeepRuleFiles(flagToKeepTestRunner)
-            .addDontWarn("org.junit.**", "org.mockito.**")
+            .addDontWarn("org.mockito.**")
             .minification(minify)
             .setMinApi(parameters.getApiLevel())
             .compile()
@@ -70,7 +70,7 @@
         testForR8(parameters.getBackend())
             .addProgramFiles(MOCKITO_INTERFACE_JAR)
             .addKeepRuleFiles(flagToKeepInterfaceConditionally)
-            .addDontWarn("org.junit.**", "org.mockito.**")
+            .addDontWarn("org.mockito.**")
             .minification(minify)
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
new file mode 100644
index 0000000..a6dbbb7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2022, 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.startup;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Lists;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MinimalStartupDexTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+        .build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepClassAndMembersRules(Main.class)
+        .addOptionsModification(
+            options ->
+                options
+                    .getStartupOptions()
+                    .setEnableMinimalStartupDex()
+                    .setEnableStartupCompletenessCheckForTesting()
+                    .setStartupConfiguration(
+                        new StartupConfiguration(
+                            Lists.newArrayList(
+                                toDexType(Main.class, options.dexItemFactory()),
+                                toDexType(StartupClass.class, options.dexItemFactory())),
+                            Collections.emptyList())))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectMultiDex(
+            primaryDexInspector -> {
+              // StartupClass should be in the primary dex.
+              ClassSubject startupClassSubject = primaryDexInspector.clazz(StartupClass.class);
+              assertThat(startupClassSubject, isPresent());
+
+              MethodSubject startupMethodSubject = startupClassSubject.uniqueMethodWithName("foo");
+              assertThat(startupMethodSubject, isPresent());
+              assertTrue(
+                  startupMethodSubject.streamInstructions().noneMatch(InstructionSubject::isThrow));
+            },
+            secondaryDexInspector -> {
+              // NonStartupClass should be in the secondary dex and should be transformed such that
+              // all methods throw null.
+              ClassSubject nonStartupClassSubject =
+                  secondaryDexInspector.clazz(NonStartupClass.class);
+              assertThat(nonStartupClassSubject, isPresent());
+
+              MethodSubject nonStartupClinitSubject = nonStartupClassSubject.clinit();
+              assertThat(nonStartupClinitSubject, isPresent());
+              assertTrue(
+                  nonStartupClinitSubject
+                      .streamInstructions()
+                      .anyMatch(InstructionSubject::isThrow));
+
+              MethodSubject nonStartupMethodSubject =
+                  nonStartupClassSubject.uniqueMethodWithName("bar");
+              assertThat(nonStartupMethodSubject, isPresent());
+              assertTrue(
+                  nonStartupMethodSubject
+                      .streamInstructions()
+                      .anyMatch(InstructionSubject::isThrow));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("foo");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      StartupClass.foo();
+    }
+
+    // @Keep
+    public void onClick() {
+      NonStartupClass.bar();
+    }
+  }
+
+  static class StartupClass {
+
+    @NeverInline
+    static void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  static class NonStartupClass {
+
+    @NeverInline
+    static void bar() {
+      System.out.println("bar");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
new file mode 100644
index 0000000..8a73ac4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2022, 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.startup;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StartupInstrumentationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addOptionsModification(
+            options -> options.getStartupOptions().setEnableStartupInstrumentation())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepClassAndMembersRules(Main.class)
+        .addOptionsModification(
+            options -> options.getStartupOptions().setEnableStartupInstrumentation())
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutput());
+  }
+
+  private static List<String> getExpectedOutput() {
+    return ImmutableList.of(
+        "Lcom/android/tools/r8/startup/StartupInstrumentationTest$Main;",
+        "Lcom/android/tools/r8/startup/StartupInstrumentationTest$StartupClass;",
+        "foo");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      StartupClass.foo();
+    }
+
+    // @Keep
+    public void onClick() {
+      NonStartupClass.bar();
+    }
+  }
+
+  static class StartupClass {
+
+    @NeverInline
+    static void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  static class NonStartupClass {
+
+    @NeverInline
+    static void bar() {
+      System.out.println("bar");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/testingannotations/NeverInlineStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/testingannotations/NeverInlineStaticInterfaceMethodTest.java
new file mode 100644
index 0000000..c5bfa0d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/testingannotations/NeverInlineStaticInterfaceMethodTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2022, 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.testingannotations;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NeverInlineStaticInterfaceMethodTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimes()
+        .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+        .build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject iClassSubject = inspector.clazz(I.class);
+              assertThat(iClassSubject, isPresent());
+
+              MethodSubject iMethodSubject = iClassSubject.uniqueMethodWithName("m");
+              assertThat(iClassSubject, isPresent());
+
+              MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+              assertThat(mainMethodSubject, isPresent());
+              assertThat(mainMethodSubject, invokesMethod(iMethodSubject));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I.m()");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I.m();
+    }
+  }
+
+  interface I {
+
+    @NeverInline
+    static void m() {
+      System.out.println("I.m()");
+    }
+  }
+}
diff --git a/tools/archive.py b/tools/archive.py
index a3f906f..893fc19 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -127,55 +127,7 @@
   if not utils.IsWindows():
     PrintResourceInfo()
 
-  # Create maven release which uses a build that exclude dependencies.
-  create_maven_release.generate_r8_maven_zip(utils.MAVEN_ZIP)
-  create_maven_release.generate_r8_maven_zip(
-      utils.MAVEN_ZIP_LIB, is_r8lib=True)
-
-  # Generate and copy a full build without dependencies.
-  gradle.RunGradleExcludeDeps([utils.R8, utils.R8_SRC])
-  shutil.copyfile(utils.R8_JAR, utils.R8_FULL_EXCLUDE_DEPS_JAR)
-
-  # Ensure all archived artifacts has been built before archiving.
-  # The target tasks postfixed by 'lib' depend on the actual target task so
-  # building it invokes the original task first.
-  # The '-Pno_internal' flag is important because we generate the lib based on uses in tests.
-  gradle.RunGradle([
-    utils.R8,
-    utils.D8,
-    utils.R8LIB,
-    utils.R8LIB_NO_DEPS,
-    utils.R8RETRACE,
-    utils.R8RETRACE_NO_DEPS,
-    utils.LIBRARY_DESUGAR_CONVERSIONS,
-    '-Pno_internal'
-  ])
-
-  # Create maven release of the desuage_jdk_libs configuration. This require
-  # an r8.jar with dependencies to have been built.
-  create_maven_release.generate_desugar_configuration_maven_zip(
-      utils.DESUGAR_CONFIGURATION_MAVEN_ZIP,
-      utils.DESUGAR_CONFIGURATION,
-      utils.DESUGAR_IMPLEMENTATION)
-  create_maven_release.generate_desugar_configuration_maven_zip(
-      utils.DESUGAR_CONFIGURATION_LEGACY_JDK11_MAVEN_ZIP,
-      utils.DESUGAR_CONFIGURATION_JDK11_LEGACY,
-      utils.DESUGAR_IMPLEMENTATION_JDK11)
-
-  version = GetVersion()
-  is_main = IsMain(version)
-  if is_main:
-    # On main we use the git hash to archive with
-    print('On main, using git hash for archiving')
-    version = GetGitHash()
-
-  destination = GetVersionDestination('gs://', version, is_main)
-  if utils.cloud_storage_exists(destination) and not options.dry_run:
-    raise Exception('Target archive directory %s already exists' % destination)
   with utils.TempDir() as temp:
-    # Create pom file for our maven repository that we build for testing.
-    default_pom_file = os.path.join(temp, 'r8.pom')
-    create_maven_release.write_default_r8_pom_file(default_pom_file, version)
 
     version_file = os.path.join(temp, 'r8-version.properties')
     with open(version_file,'w') as version_writer:
@@ -190,6 +142,56 @@
       version_writer.write(releaser)
       version_writer.write('version-file.version.code=1\n')
 
+    # Create maven release which uses a build that exclude dependencies.
+    create_maven_release.generate_r8_maven_zip(utils.MAVEN_ZIP, version_file=version_file)
+    create_maven_release.generate_r8_maven_zip(
+        utils.MAVEN_ZIP_LIB, is_r8lib=True, version_file=version_file)
+
+    # Generate and copy a full build without dependencies.
+    gradle.RunGradleExcludeDeps([utils.R8, utils.R8_SRC])
+    shutil.copyfile(utils.R8_JAR, utils.R8_FULL_EXCLUDE_DEPS_JAR)
+
+    # Ensure all archived artifacts has been built before archiving.
+    # The target tasks postfixed by 'lib' depend on the actual target task so
+    # building it invokes the original task first.
+    # The '-Pno_internal' flag is important because we generate the lib based on uses in tests.
+    gradle.RunGradle([
+        utils.R8,
+        utils.D8,
+        utils.R8LIB,
+        utils.R8LIB_NO_DEPS,
+        utils.R8RETRACE,
+        utils.R8RETRACE_NO_DEPS,
+        utils.LIBRARY_DESUGAR_CONVERSIONS,
+        '-Pno_internal'
+    ])
+
+    # Create maven release of the desuage_jdk_libs configuration. This require
+    # an r8.jar with dependencies to have been built.
+    create_maven_release.generate_desugar_configuration_maven_zip(
+        utils.DESUGAR_CONFIGURATION_MAVEN_ZIP,
+        utils.DESUGAR_CONFIGURATION,
+        utils.DESUGAR_IMPLEMENTATION)
+    create_maven_release.generate_desugar_configuration_maven_zip(
+        utils.DESUGAR_CONFIGURATION_LEGACY_JDK11_MAVEN_ZIP,
+        utils.DESUGAR_CONFIGURATION_JDK11_LEGACY,
+        utils.DESUGAR_IMPLEMENTATION_JDK11)
+
+    version = GetVersion()
+    is_main = IsMain(version)
+    if is_main:
+      # On main we use the git hash to archive with
+      print('On main, using git hash for archiving')
+      version = GetGitHash()
+
+    destination = GetVersionDestination('gs://', version, is_main)
+    if utils.cloud_storage_exists(destination) and not options.dry_run:
+      raise Exception('Target archive directory %s already exists' % destination)
+
+    # Create pom file for our maven repository that we build for testing.
+    default_pom_file = os.path.join(temp, 'r8.pom')
+    create_maven_release.write_default_r8_pom_file(default_pom_file, version)
+
     for file in [
       utils.D8_JAR,
       utils.R8_JAR,
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 10bbea4..6250c75 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -9,7 +9,7 @@
 import jdk
 import json
 from os import makedirs
-from os.path import join
+from os.path import join, basename
 from shutil import copyfile, make_archive, move, rmtree
 import subprocess
 import sys
@@ -309,7 +309,7 @@
     base_no_zip = out[0:len(out)-4]
     make_archive(base_no_zip, 'zip', tmp_dir)
 
-def generate_r8_maven_zip(out, is_r8lib=False):
+def generate_r8_maven_zip(out, is_r8lib=False, version_file=None):
   # Build the R8 no deps artifact.
   if not is_r8lib:
     gradle.RunGradleExcludeDeps([utils.R8])
@@ -318,6 +318,13 @@
 
   version = determine_version()
   with utils.TempDir() as tmp_dir:
+    file_copy = join(tmp_dir, 'copy_of_jar.jar')
+    copyfile(utils.R8LIB_JAR if is_r8lib else utils.R8_JAR, file_copy)
+
+    if version_file:
+      with zipfile.ZipFile(file_copy, 'a') as zip:
+        zip.write(version_file, basename(version_file))
+
     # Generate the pom file.
     pom_file = join(tmp_dir, 'r8.pom')
     write_pom_file(
@@ -331,7 +338,7 @@
         'r8',
         version,
         pom_file,
-        utils.R8LIB_JAR if is_r8lib else utils.R8_JAR,
+        file_copy,
         out)
 
 # Write the desugaring configuration of a jar file with the following content:
diff --git a/tools/jdk.py b/tools/jdk.py
index ea6be86..c9cb628 100755
--- a/tools/jdk.py
+++ b/tools/jdk.py
@@ -11,7 +11,7 @@
 JDK_DIR = os.path.join(defines.THIRD_PARTY, 'openjdk')
 
 def GetJdkHome():
-  return GetJdk9Home()
+  return GetJdk11Home()
 
 def GetJdk11Home():
   root = os.path.join(JDK_DIR, 'jdk-11')
diff --git a/tools/r8_release.py b/tools/r8_release.py
index ef7ea7a..eab96be 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -15,7 +15,7 @@
 
 import utils
 
-R8_DEV_BRANCH = '3.3'
+R8_DEV_BRANCH = '4.0'
 R8_VERSION_FILE = os.path.join(
     'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
 THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py
index 904b509..41da451 100755
--- a/tools/run_benchmark.py
+++ b/tools/run_benchmark.py
@@ -8,10 +8,10 @@
 import subprocess
 import sys
 
+import compiledump
 import gradle
 import jdk
 import utils
-import compiledump
 
 NONLIB_BUILD_TARGET = 'R8WithRelocatedDeps'
 NONLIB_TEST_BUILD_TARGETS = [utils.R8_TESTS_TARGET, utils.R8_TESTS_DEPS_TARGET]
@@ -99,7 +99,13 @@
   if not options.no_build:
     gradle.RunGradle(buildTargets + ['-Pno_internal'])
 
-  return run(options, r8jar, testjars)
+  if not options.golem:
+    # When running locally, change the working directory to be in 'temp'.
+    # This is hard to do properly within the JVM so we do it here.
+    with utils.ChangedWorkingDirectory(temp):
+      return run(options, r8jar, testjars)
+  else:
+    return run(options, r8jar, testjars)
 
 def run(options, r8jar, testjars):
   jdkhome = get_jdk_home(options, options.benchmark)
@@ -113,7 +119,9 @@
     'com.android.tools.r8.benchmarks.BenchmarkMainEntryRunner',
     options.benchmark,
     options.target,
-    'golem' if options.golem else 'local',
+    # When running locally the working directory is moved and we pass the
+    # repository root as an argument. The runner can then setup dependencies.
+    'golem' if options.golem else utils.REPO_ROOT,
     ])
   return subprocess.check_call(cmd)
 
diff --git a/tools/startup/adb_utils.py b/tools/startup/adb_utils.py
index 136c99a..981bcbd 100644
--- a/tools/startup/adb_utils.py
+++ b/tools/startup/adb_utils.py
@@ -91,7 +91,7 @@
 
 def get_minor_major_page_faults(app_id, device_id=None):
   pid = get_pid(app_id, device_id)
-  cmd = create_adb_cmd('shell ps -p %i -o MINFL,MAJFL' % pid)
+  cmd = create_adb_cmd('shell ps -p %i -o MINFL,MAJFL' % pid, device_id)
   stdout = subprocess.check_output(cmd).decode('utf-8')
   lines_it = iter(stdout.splitlines())
   first_line = next(lines_it)
@@ -145,7 +145,7 @@
   cmd = create_adb_cmd(
     'shell profman --dump-classes-and-methods'
     ' --profile-file=%s --apk=%s --dex-location=%s'
-        % (profile_path, apk_path, apk_path))
+        % (profile_path, apk_path, apk_path), device_id)
   stdout = subprocess.check_output(cmd).decode('utf-8').strip()
   lines = stdout.splitlines()
   classes_and_methods = []
diff --git a/tools/startup/generate_startup_descriptors.py b/tools/startup/generate_startup_descriptors.py
index a3eb72c..ea0fdf3 100755
--- a/tools/startup/generate_startup_descriptors.py
+++ b/tools/startup/generate_startup_descriptors.py
@@ -22,6 +22,7 @@
     print(
         'Found %i new startup descriptors in iteration %i'
             % (number_of_new_startup_descriptors, iteration + 1))
+  return number_of_new_startup_descriptors
 
 def generate_startup_profile_on_device(options):
   if not options.use_existing_profile:
@@ -74,6 +75,8 @@
 def parse_options(argv):
   result = argparse.ArgumentParser(
       description='Generate a perfetto trace file.')
+  result.add_argument('--apk',
+                      help='Path to the APK')
   result.add_argument('--app-id',
                       help='The application ID of interest',
                       required=True)
@@ -104,6 +107,11 @@
                            'descriptors are found',
                       action='store_true',
                       default=False)
+  result.add_argument('--until-stable-iterations',
+                      help='Number of times that profile generation must must '
+                           'not find new startup descriptors before exiting',
+                      default=1,
+                      type=int)
   result.add_argument('--use-existing-profile',
                       help='Do not launch app to generate startup profile',
                       action='store_true',
@@ -116,13 +124,22 @@
 
 def main(argv):
   (options, args) = parse_options(argv)
+  adb_utils.root(options.device_id)
+  if options.apk:
+    adb_utils.uninstall(options.app_id, options.device_id)
+    adb_utils.install(options.apk, options.device_id)
   startup_descriptors = set()
   if options.until_stable:
     iteration = 0
+    stable_iterations = 0
     while True:
       diff = extend_startup_descriptors(startup_descriptors, iteration, options)
       if diff == 0:
-        break
+        stable_iterations = stable_iterations + 1
+        if stable_iterations == options.until_stable_iterations:
+          break
+      else:
+        stable_iterations = 0
       iteration = iteration + 1
   else:
     for iteration in range(options.iterations):
diff --git a/tools/startup/trace_generator.py b/tools/startup/measure_startup.py
similarity index 95%
rename from tools/startup/trace_generator.py
rename to tools/startup/measure_startup.py
index 37f5c71..080f827 100755
--- a/tools/startup/trace_generator.py
+++ b/tools/startup/measure_startup.py
@@ -82,11 +82,14 @@
   os.makedirs(out_dir, exist_ok=True)
 
 def run(out_dir, options, tmp_dir):
-  assert adb_utils.get_screen_state().is_on_and_unlocked()
+  assert adb_utils.get_screen_state(options.device_id).is_on_and_unlocked()
 
   # Start perfetto trace collector.
-  perfetto_process, perfetto_trace_path = perfetto_utils.record_android_trace(
-      out_dir, tmp_dir)
+  perfetto_process = None
+  perfetto_trace_path = None
+  if not options.no_perfetto:
+    perfetto_process, perfetto_trace_path = perfetto_utils.record_android_trace(
+        out_dir, tmp_dir)
 
   # Launch main activity.
   launch_activity_result = adb_utils.launch_activity(
@@ -96,7 +99,8 @@
       wait_for_activity_to_launch=True)
 
   # Wait for perfetto trace collector to stop.
-  perfetto_utils.stop_record_android_trace(perfetto_process, out_dir)
+  if not options.no_perfetto:
+    perfetto_utils.stop_record_android_trace(perfetto_process, out_dir)
 
   # Get minor and major page faults from app process.
   data = compute_data(launch_activity_result, perfetto_trace_path, options)
diff --git a/tools/utils.py b/tools/utils.py
index 3200b3a..2a4b18e 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -636,7 +636,7 @@
     # Build metadata currently not suppported
 
   def larger_than(self, other):
-    if self.prepelease or other.prepelease:
+    if self.prerelease or other.prerelease:
       raise Exception("Comparison with prerelease not implemented")
     if self.major > other.major:
       return True