Merge commit 'e069b00496e7dab73907ae88ad55390072ba6e4b' into dev-release
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_path.json b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
index a00bb68..e1298de 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_path.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
@@ -309,16 +309,6 @@
       }
     },
     {
-      "api_level_below_or_equal": 25,
-      "rewrite_prefix": {
-        "java.io.DesugarFile": "j$.io.DesugarFile",
-        "java.nio.file.": "j$.nio.file."
-      },
-      "retarget_method_with_emulated_dispatch": {
-        "java.nio.file.Path java.io.File#toPath()": "java.io.DesugarFile"
-      }
-    },
-    {
       "api_level_below_or_equal": 23,
       "retarget_method": {
         "int java.util.concurrent.atomic.AtomicInteger#accumulateAndGet(int, java.util.function.IntBinaryOperator)": "java.util.concurrent.atomic.DesugarAtomicInteger",
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index a94d834..b46e8f5 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -47,6 +47,9 @@
       throws CompilationFailedException {
     InternalOptions options = command.getInternalOptions();
 
+    // TODO(b/241351268): Don't compile in intermediate mode as the output is a final "shard".
+    options.intermediate = true;
+
     // TODO(b/241063980): Move this to D8Command.Builder.setDisableDesugaring(true) in bazel.
     options.desugarState = DesugarState.OFF;
 
@@ -62,11 +65,4 @@
 
     D8.runForTesting(command.getInputApp(), options);
   }
-
-  public static void runD8ForTesting(D8Command command, boolean dontCreateMarkerInD8)
-      throws CompilationFailedException {
-    InternalOptions options = command.getInternalOptions();
-    options.testing.dontCreateMarkerInD8 = dontCreateMarkerInD8;
-    D8.runForTesting(command.getInputApp(), options);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index c9073be..b009e9d 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -22,21 +22,30 @@
 public class DexSegments {
   public static class Command extends BaseCommand {
 
+    private final boolean csv;
+
     public static class Builder
         extends BaseCommand.Builder<Command, Builder> {
 
+      private boolean csv = false;
+
       @Override
       Command.Builder self() {
         return this;
       }
 
+      private Builder setCsv(boolean csv) {
+        this.csv = csv;
+        return self();
+      }
+
       @Override
       protected Command makeCommand() {
         // If printing versions ignore everything else.
         if (isPrintHelp()) {
           return new Command(isPrintHelp());
         }
-        return new Command(getAppBuilder().build());
+        return new Command(getAppBuilder().build(), csv);
       }
     }
 
@@ -63,6 +72,8 @@
           continue;
         } else if (arg.equals("--help")) {
           builder.setPrintHelp(true);
+        } else if (arg.equals("--csv")) {
+          builder.setCsv(true);
         } else {
           if (arg.startsWith("--")) {
             builder.getReporter().error(new StringDiagnostic("Unknown option: " + arg,
@@ -73,12 +84,14 @@
       }
     }
 
-    private Command(AndroidApp inputApp) {
+    private Command(AndroidApp inputApp, boolean csv) {
       super(inputApp);
+      this.csv = csv;
     }
 
     private Command(boolean printHelp) {
       super(printHelp, false);
+      this.csv = false;
     }
 
     @Override
@@ -90,16 +103,41 @@
   public static void main(String[] args)
       throws IOException, CompilationFailedException, ResourceException {
     Command.Builder builder = Command.parse(args);
-    Map<Integer, SegmentInfo> result = run(builder.build());
+    Command cmd = builder.build();
+    Map<Integer, SegmentInfo> result = run(cmd);
     if (result == null) {
       return;
     }
-    System.out.println("Segments in dex application (name: size / items):");
-    // This output is parsed by tools/test_framework.py. Check the parsing there when updating.
-    result.forEach(
-        (key, value) ->
+    if (cmd.csv) {
+      System.out.println("\"Name\",\"Size\",\"Items\"");
+      result.forEach(
+          (key, value) -> {
             System.out.println(
-                " - " + DexSection.typeName(key) + ": " + value.size + " / " + value.items));
+                "\"" + DexSection.typeName(key) + "\", " + value.size + ", " + value.items);
+            if (key == Constants.TYPE_TYPE_LIST) {
+              // Type items header is just a uint, and each element is a ushort. see
+              // https://source.android.com/devices/tech/dalvik/dex-format#type-list.
+              int typeItemsSize = (value.size - value.items * 4);
+              System.out.println(
+                  "\"TypeItems\", " + typeItemsSize + ", " + (typeItemsSize / 2) + "");
+            }
+          });
+    } else {
+      System.out.println("Segments in dex application (name: size / items):");
+      // This output is parsed by tools/test_framework.py. Check the parsing there when updating.
+      result.forEach(
+          (key, value) -> {
+            System.out.print(
+                " - " + DexSection.typeName(key) + ": " + value.size + " / " + value.items);
+            if (key == Constants.TYPE_TYPE_LIST) {
+              // Type items header is just a uint, and each element is a ushort. see
+              // https://source.android.com/devices/tech/dalvik/dex-format#type-list.
+              int typeItemsSize = (value.size - value.items * 4);
+              System.out.print(" (TypeItems: " + typeItemsSize + " / " + (typeItemsSize / 2) + ")");
+            }
+            System.out.println();
+          });
+    }
   }
 
   public static Map<Integer, SegmentInfo> run(Command command)
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index 351a3a1..b9ef649 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -284,6 +284,9 @@
   }
 
   private void freeRegistersForIntervals(LiveIntervals intervals) {
+    if (options().testing.neverReuseCfLocalRegisters) {
+      return;
+    }
     int register = intervals.getRegister();
     freeRegisters.add(register);
     if (intervals.getType().isWide()) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 31967b1..a64b883 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -166,7 +166,7 @@
     }
     Path dumpOutput = dumpInputFlags.getDumpPath();
     timing.begin("ApplicationReader.dump");
-    inputApp.dump(dumpOutput, dumpOptions, options.reporter, options.dexItemFactory());
+    inputApp.dump(dumpOutput, dumpOptions, options);
     timing.end();
     Diagnostic message = new StringDiagnostic("Dumped compilation inputs to: " + dumpOutput);
     if (dumpInputFlags.shouldFailCompilation()) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 492e61a..b0d4999 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
 import com.android.tools.r8.dex.VirtualFile.FilePerInputClassDistributor;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.experimental.startup.StartupCompleteness;
 import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
@@ -289,6 +290,8 @@
         timing.end();
       }
 
+      StartupCompleteness.run(appView);
+
       // Generate the dex file contents.
       timing.begin("Distribute");
       List<VirtualFile> virtualFiles = distribute(executorService);
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/MixedSectionLayoutStrategy.java
index 7143d6b..2381e6f 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionLayoutStrategy.java
@@ -23,7 +23,9 @@
   public static MixedSectionLayoutStrategy create(
       AppView<?> appView, MixedSectionOffsets mixedSectionOffsets, VirtualFile virtualFile) {
     StartupOrder startupOrderForWriting =
-        virtualFile.getId() == 0 && appView.hasClassHierarchy()
+        appView.options().getStartupOptions().isStartupLayoutOptimizationsEnabled()
+                && virtualFile.getId() == 0
+                && appView.hasClassHierarchy()
             ? appView
                 .appInfoWithClassHierarchy()
                 .getStartupOrder()
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 1056bda..55923ed 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -27,9 +26,7 @@
 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;
@@ -48,7 +45,6 @@
 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;
@@ -1010,7 +1006,6 @@
 
     public void run() {
       addStartupClasses();
-      enableStartupCompletenessCheckForTesting();
       List<DexProgramClass> nonPackageClasses = addNonStartupClasses();
       addNonPackageClasses(cycler, nonPackageClasses);
     }
@@ -1057,31 +1052,6 @@
       }
     }
 
-    /**
-     * 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() {
       int prefixLength = MINIMUM_PREFIX_LENGTH;
       int transactionStartIndex = 0;
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
index 4b749f3..954fcba 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
@@ -16,10 +16,8 @@
 import com.android.tools.r8.utils.LazyBox;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.IdentityHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -131,7 +129,7 @@
     LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems =
         new LinkedHashSet<>(startupItems.size());
     Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
-        computeSyntheticContextsToSyntheticClasses(appView);
+        appView.getSyntheticItems().computeSyntheticContextsToSyntheticClasses(appView);
     for (StartupItem<DexType, DexMethod, ?> startupItem : startupItems) {
       addStartupItem(
           startupItem, rewrittenStartupItems, syntheticContextsToSyntheticClasses, appView);
@@ -140,23 +138,6 @@
     return createNonEmpty(rewrittenStartupItems);
   }
 
-  private Map<DexType, List<DexProgramClass>> computeSyntheticContextsToSyntheticClasses(
-      AppView<?> appView) {
-    Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
-        new IdentityHashMap<>();
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (appView.getSyntheticItems().isSyntheticClass(clazz)) {
-        for (DexType synthesizingContextType :
-            appView.getSyntheticItems().getSynthesizingContextTypes(clazz.getType())) {
-          syntheticContextsToSyntheticClasses
-              .computeIfAbsent(synthesizingContextType, ignoreKey -> new ArrayList<>())
-              .add(clazz);
-        }
-      }
-    }
-    return syntheticContextsToSyntheticClasses;
-  }
-
   private static void addStartupItem(
       StartupItem<DexType, DexMethod, ?> startupItem,
       LinkedHashSet<StartupItem<DexType, DexMethod, ?>> rewrittenStartupItems,
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java
new file mode 100644
index 0000000..8d75d7c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupCompleteness.java
@@ -0,0 +1,104 @@
+// 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.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ThrowNullCode;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class StartupCompleteness {
+
+  private final AppView<?> appView;
+  private final StartupOrder startupOrder;
+
+  private StartupCompleteness(AppView<?> appView) {
+    this.appView = appView;
+    this.startupOrder =
+        appView.hasClassHierarchy()
+            ? appView.appInfoWithClassHierarchy().getStartupOrder()
+            : StartupOrder.empty();
+  }
+
+  /**
+   * Replaces the code of each non-startup method by {@code throw null}. If the application fails on
+   * launch with this enabled this points to the startup configuration being incomplete, or
+   * inadequate lens rewriting of the startup list in R8.
+   */
+  public static void run(AppView<?> appView) {
+    InternalOptions options = appView.options();
+    if (options.getStartupOptions().isStartupCompletenessCheckForTestingEnabled()) {
+      new StartupCompleteness(appView).processClasses();
+    }
+  }
+
+  private void processClasses() {
+    Set<DexReference> startupItems = computeStartupItems();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      processClass(clazz, startupItems);
+    }
+  }
+
+  private void processClass(DexProgramClass clazz, Set<DexReference> startupItems) {
+    clazz.forEachProgramMethodMatching(
+        method -> !startupItems.contains(method.getReference()), this::processNonStartupMethod);
+    if (!startupItems.contains(clazz.getType())) {
+      if (clazz.hasClassInitializer()) {
+        processNonStartupMethod(clazz.getProgramClassInitializer());
+      } else {
+        clazz.addDirectMethod(
+            DexEncodedMethod.syntheticBuilder()
+                .setAccessFlags(MethodAccessFlags.createForClassInitializer())
+                .setCode(ThrowNullCode.get())
+                .setMethod(appView.dexItemFactory().createClassInitializer(clazz.getType()))
+                .build());
+      }
+    }
+  }
+
+  private void processNonStartupMethod(ProgramMethod method) {
+    method.getDefinition().setCode(ThrowNullCode.get(), Int2ReferenceMaps.emptyMap());
+  }
+
+  private Set<DexReference> computeStartupItems() {
+    Set<DexReference> startupItems = Sets.newIdentityHashSet();
+    Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
+        appView.getSyntheticItems().computeSyntheticContextsToSyntheticClasses(appView);
+    for (StartupItem<DexType, DexMethod, ?> startupItem : startupOrder.getItems()) {
+      if (startupItem.isSynthetic()) {
+        assert startupItem.isStartupClass();
+        List<DexProgramClass> syntheticClasses =
+            syntheticContextsToSyntheticClasses.getOrDefault(
+                startupItem.asStartupClass().getReference(), Collections.emptyList());
+        for (DexProgramClass syntheticClass : syntheticClasses) {
+          startupItems.add(syntheticClass.getType());
+          syntheticClass.forEachProgramMethod(method -> startupItems.add(method.getReference()));
+        }
+      } else {
+        if (startupItem.isStartupClass()) {
+          StartupClass<DexType, DexMethod> startupClass = startupItem.asStartupClass();
+          startupItems.add(startupClass.getReference());
+        } else {
+          assert startupItem.isStartupMethod();
+          StartupMethod<DexType, DexMethod> startupMethod = startupItem.asStartupMethod();
+          startupItems.add(startupMethod.getReference());
+        }
+      }
+    }
+    return startupItems;
+  }
+}
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
index 7ff913d..e737c9d 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
@@ -18,6 +18,16 @@
           "com.android.tools.r8.startup.minimalstartupdex", false);
 
   /**
+   * When enabled, optimizations crossing the startup/non-startup boundary will be allowed.
+   *
+   * <p>The disabling of this may help to avoid that more code may be loaded during startup as a
+   * result of optimizations such as inlining and class merging.
+   */
+  private boolean enableStartupBoundaryOptimizations =
+      parseSystemPropertyForDevelopmentOrDefault(
+          "com.android.tools.r8.startup.boundaryoptimizations", false);
+
+  /**
    * When enabled, each method that is not classified as a startup method at the end of compilation
    * will be changed to have a throwing method body.
    *
@@ -39,6 +49,13 @@
       parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.startup.instrument", false);
 
   /**
+   * When enabled, the layout of the primary dex file will be generated using the startup list,
+   * using {@link com.android.tools.r8.dex.StartupMixedSectionLayoutStrategy}.
+   */
+  private boolean enableStartupLayoutOptimizations =
+      parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.startup.layout", true);
+
+  /**
    * Specifies the synthetic context of the startup runtime library. When this is set, the startup
    * runtime library will only be injected into the app when the synthetic context is in the
    * program. This can be used to avoid that the startup runtime library is injected multiple times
@@ -101,6 +118,10 @@
     return this;
   }
 
+  public boolean isStartupBoundaryOptimizationsEnabled() {
+    return enableStartupBoundaryOptimizations;
+  }
+
   public boolean isStartupInstrumentationEnabled() {
     return enableStartupInstrumentation;
   }
@@ -110,12 +131,21 @@
     return this;
   }
 
-  public boolean isStartupCompletenessCheckForTesting() {
+  public boolean isStartupLayoutOptimizationsEnabled() {
+    return enableStartupLayoutOptimizations;
+  }
+
+  public boolean isStartupCompletenessCheckForTestingEnabled() {
     return enableStartupCompletenessCheckForTesting;
   }
 
   public StartupOptions setEnableStartupCompletenessCheckForTesting() {
-    enableStartupCompletenessCheckForTesting = true;
+    return setEnableStartupCompletenessCheckForTesting(true);
+  }
+
+  public StartupOptions setEnableStartupCompletenessCheckForTesting(
+      boolean enableStartupCompletenessCheckForTesting) {
+    this.enableStartupCompletenessCheckForTesting = enableStartupCompletenessCheckForTesting;
     return this;
   }
 
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 2113b52..5b8e3db 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -107,14 +107,20 @@
   public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
       Set<DexProgramClass> classes, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return getFeatureSplitClasses(
-        classes, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+        classes,
+        appView.options(),
+        appView.appInfo().getStartupOrder(),
+        appView.getSyntheticItems());
   }
 
   public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
-      Set<DexProgramClass> classes, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+      Set<DexProgramClass> classes,
+      InternalOptions options,
+      StartupOrder startupOrder,
+      SyntheticItems syntheticItems) {
     Map<FeatureSplit, Set<DexProgramClass>> result = new IdentityHashMap<>();
     for (DexProgramClass clazz : classes) {
-      FeatureSplit featureSplit = getFeatureSplit(clazz, startupOrder, syntheticItems);
+      FeatureSplit featureSplit = getFeatureSplit(clazz, options, startupOrder, syntheticItems);
       if (featureSplit != null && !featureSplit.isBase()) {
         result.computeIfAbsent(featureSplit, ignore -> Sets.newIdentityHashSet()).add(clazz);
       }
@@ -125,21 +131,31 @@
   public FeatureSplit getFeatureSplit(
       ProgramDefinition definition, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return getFeatureSplit(
-        definition, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+        definition,
+        appView.options(),
+        appView.appInfo().getStartupOrder(),
+        appView.getSyntheticItems());
   }
 
   public FeatureSplit getFeatureSplit(
-      ProgramDefinition definition, StartupOrder startupOrder, SyntheticItems syntheticItems) {
-    return getFeatureSplit(definition.getContextType(), startupOrder, syntheticItems);
+      ProgramDefinition definition,
+      InternalOptions options,
+      StartupOrder startupOrder,
+      SyntheticItems syntheticItems) {
+    return getFeatureSplit(definition.getContextType(), options, startupOrder, syntheticItems);
   }
 
   public FeatureSplit getFeatureSplit(
       DexType type, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return getFeatureSplit(type, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+    return getFeatureSplit(
+        type, appView.options(), appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
   }
 
   public FeatureSplit getFeatureSplit(
-      DexType type, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+      DexType type,
+      InternalOptions options,
+      StartupOrder startupOrder,
+      SyntheticItems syntheticItems) {
     if (syntheticItems == null) {
       // Called from AndroidApp.dumpProgramResources().
       assert startupOrder.isEmpty();
@@ -154,16 +170,20 @@
         // the shared utility class in case it is used during startup. The use of base startup
         // allows for merging startup classes with the shared utility class, however, which could be
         // bad for startup if the shared utility class is not used during startup.
-        return startupOrder.isEmpty() ? FeatureSplit.BASE : FeatureSplit.BASE_STARTUP;
+        return startupOrder.isEmpty()
+                || options.getStartupOptions().isStartupBoundaryOptimizationsEnabled()
+            ? FeatureSplit.BASE
+            : FeatureSplit.BASE_STARTUP;
       }
       feature = syntheticItems.getContextualFeatureSplitOrDefault(type, FeatureSplit.BASE);
     } else {
       feature = classToFeatureSplitMap.getOrDefault(type, FeatureSplit.BASE);
     }
     if (feature.isBase()) {
-      return startupOrder.contains(type, syntheticItems)
-          ? FeatureSplit.BASE_STARTUP
-          : FeatureSplit.BASE;
+      return !startupOrder.contains(type, syntheticItems)
+              || options.getStartupOptions().isStartupBoundaryOptimizationsEnabled()
+          ? FeatureSplit.BASE
+          : FeatureSplit.BASE_STARTUP;
     }
     return feature;
   }
@@ -177,12 +197,16 @@
 
   public boolean isInBase(
       DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isInBase(clazz, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+    return isInBase(
+        clazz, appView.options(), appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
   }
 
   public boolean isInBase(
-      DexProgramClass clazz, StartupOrder startupOrder, SyntheticItems syntheticItems) {
-    return getFeatureSplit(clazz, startupOrder, syntheticItems).isBase();
+      DexProgramClass clazz,
+      InternalOptions options,
+      StartupOrder startupOrder,
+      SyntheticItems syntheticItems) {
+    return getFeatureSplit(clazz, options, startupOrder, syntheticItems).isBase();
   }
 
   public boolean isInBaseOrSameFeatureAs(
@@ -190,15 +214,21 @@
       ProgramDefinition context,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
     return isInBaseOrSameFeatureAs(
-        clazz, context, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+        clazz,
+        context,
+        appView.options(),
+        appView.appInfo().getStartupOrder(),
+        appView.getSyntheticItems());
   }
 
   public boolean isInBaseOrSameFeatureAs(
       DexProgramClass clazz,
       ProgramDefinition context,
+      InternalOptions options,
       StartupOrder startupOrder,
       SyntheticItems syntheticItems) {
-    return isInBaseOrSameFeatureAs(clazz.getContextType(), context, startupOrder, syntheticItems);
+    return isInBaseOrSameFeatureAs(
+        clazz.getContextType(), context, options, startupOrder, syntheticItems);
   }
 
   public boolean isInBaseOrSameFeatureAs(
@@ -206,48 +236,62 @@
       ProgramDefinition context,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
     return isInBaseOrSameFeatureAs(
-        clazz, context, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+        clazz,
+        context,
+        appView.options(),
+        appView.appInfo().getStartupOrder(),
+        appView.getSyntheticItems());
   }
 
   public boolean isInBaseOrSameFeatureAs(
       DexType clazz,
       ProgramDefinition context,
+      InternalOptions options,
       StartupOrder startupOrder,
       SyntheticItems syntheticItems) {
-    FeatureSplit split = getFeatureSplit(clazz, startupOrder, syntheticItems);
-    return split.isBase() || split == getFeatureSplit(context, startupOrder, syntheticItems);
+    FeatureSplit split = getFeatureSplit(clazz, options, startupOrder, syntheticItems);
+    return split.isBase()
+        || split == getFeatureSplit(context, options, startupOrder, syntheticItems);
   }
 
   public boolean isInFeature(
-      DexProgramClass clazz, StartupOrder startupOrder, SyntheticItems syntheticItems) {
-    return !isInBase(clazz, startupOrder, syntheticItems);
+      DexProgramClass clazz,
+      InternalOptions options,
+      StartupOrder startupOrder,
+      SyntheticItems syntheticItems) {
+    return !isInBase(clazz, options, startupOrder, syntheticItems);
   }
 
   public boolean isInSameFeatureOrBothInSameBase(
       ProgramMethod a, ProgramMethod b, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return isInSameFeatureOrBothInSameBase(
-        a, b, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+        a, b, appView.options(), appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
   }
 
   public boolean isInSameFeatureOrBothInSameBase(
-      ProgramMethod a, ProgramMethod b, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+      ProgramMethod a,
+      ProgramMethod b,
+      InternalOptions options,
+      StartupOrder startupOrder,
+      SyntheticItems syntheticItems) {
     return isInSameFeatureOrBothInSameBase(
-        a.getHolder(), b.getHolder(), startupOrder, syntheticItems);
+        a.getHolder(), b.getHolder(), options, startupOrder, syntheticItems);
   }
 
   public boolean isInSameFeatureOrBothInSameBase(
       DexProgramClass a, DexProgramClass b, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return isInSameFeatureOrBothInSameBase(
-        a, b, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+        a, b, appView.options(), appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
   }
 
   public boolean isInSameFeatureOrBothInSameBase(
       DexProgramClass a,
       DexProgramClass b,
+      InternalOptions options,
       StartupOrder startupOrder,
       SyntheticItems syntheticItems) {
-    return getFeatureSplit(a, startupOrder, syntheticItems)
-        == getFeatureSplit(b, startupOrder, syntheticItems);
+    return getFeatureSplit(a, options, startupOrder, syntheticItems)
+        == getFeatureSplit(b, options, startupOrder, syntheticItems);
   }
 
   public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens) {
@@ -287,6 +331,10 @@
 
   public static boolean isInFeature(DexProgramClass clazz, AppView<AppInfoWithLiveness> appView) {
     return getMap(appView)
-        .isInFeature(clazz, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+        .isInFeature(
+            clazz,
+            appView.options(),
+            appView.appInfo().getStartupOrder(),
+            appView.getSyntheticItems());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 428d0fd..43bbfa2 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.experimental.startup.StartupOrder;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 
 /**
@@ -24,6 +25,7 @@
         clazz,
         context,
         appView.appInfo().getClassToFeatureSplitMap(),
+        appView.options(),
         appView.appInfo().getStartupOrder(),
         appView.getSyntheticItems());
   }
@@ -32,6 +34,7 @@
       DexClass clazz,
       Definition context,
       ClassToFeatureSplitMap classToFeatureSplitMap,
+      InternalOptions options,
       StartupOrder startupOrder,
       SyntheticItems syntheticItems) {
     if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
@@ -40,7 +43,11 @@
     if (clazz.isProgramClass()
         && context.isProgramDefinition()
         && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
-            clazz.asProgramClass(), context.asProgramDefinition(), startupOrder, syntheticItems)) {
+            clazz.asProgramClass(),
+            context.asProgramDefinition(),
+            options,
+            startupOrder,
+            syntheticItems)) {
       return OptionalBool.UNKNOWN;
     }
     return OptionalBool.TRUE;
@@ -78,6 +85,7 @@
             initialResolutionHolder,
             context,
             appInfo.getClassToFeatureSplitMap(),
+            appInfo.options(),
             appInfo.getStartupOrder(),
             appInfo.getSyntheticItems());
     if (classAccessibility.isFalse()) {
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 5b43e8d..94bb52d 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -1272,6 +1272,7 @@
                                       clazz,
                                       context,
                                       appInfo.getClassToFeatureSplitMap(),
+                                      appInfo.options(),
                                       appInfo.getStartupOrder(),
                                       appInfo.getSyntheticItems())
                                   .isPossiblyFalse())),
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 f4a6d2a..dad5fd5 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LibraryValidator;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.SemanticVersion;
 import com.android.tools.r8.utils.Timing;
@@ -225,6 +226,7 @@
   @Override
   public MachineDesugaredLibrarySpecification toMachineSpecification(
       DexApplication app, Timing timing) throws IOException {
+    LibraryValidator.validate(app, libraryCompilation, getRequiredCompilationApiLevel());
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/PositionRangeAllocator.java b/src/main/java/com/android/tools/r8/naming/PositionRangeAllocator.java
new file mode 100644
index 0000000..957e660
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/PositionRangeAllocator.java
@@ -0,0 +1,71 @@
+// 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;
+
+public abstract class PositionRangeAllocator {
+
+  private static final int MAX_POSITION = 256;
+  private static final int MAX_DELTA = 1;
+
+  final Range[] cache = new Range[MAX_POSITION];
+
+  public Range get(int index) {
+    return (index >= 0 && index < MAX_POSITION) ? cache[index] : new Range(index);
+  }
+
+  public static CardinalPositionRangeAllocator createCardinalPositionRangeAllocator() {
+    return new CardinalPositionRangeAllocator();
+  }
+
+  public static NonCardinalPositionRangeAllocator createNonCardinalPositionRangeAllocator() {
+    return new NonCardinalPositionRangeAllocator();
+  }
+
+  public static class CardinalPositionRangeAllocator extends PositionRangeAllocator {
+
+    private CardinalPositionRangeAllocator() {
+      super();
+      for (int i = 0; i < MAX_POSITION; i++) {
+        cache[i] = new Range(i);
+      }
+    }
+  }
+
+  public static class NonCardinalPositionRangeFixedDeltaCache extends PositionRangeAllocator {
+
+    public NonCardinalPositionRangeFixedDeltaCache(int delta) {
+      super();
+      for (int i = 0; i < MAX_POSITION; i++) {
+        cache[i] = new Range(i, i + delta);
+      }
+    }
+  }
+
+  public static class NonCardinalPositionRangeAllocator extends PositionRangeAllocator {
+
+    private final NonCardinalPositionRangeFixedDeltaCache[] cache =
+        new NonCardinalPositionRangeFixedDeltaCache[MAX_DELTA + 1];
+
+    private NonCardinalPositionRangeAllocator() {
+      for (int i = 0; i <= MAX_DELTA; i++) {
+        cache[i] = new NonCardinalPositionRangeFixedDeltaCache(i);
+      }
+    }
+
+    public Range get(int from, int to) {
+      if (from >= MAX_POSITION) {
+        return new Range(from, to);
+      }
+      int thisDelta = to - from;
+      if (thisDelta < 0) {
+        return new Range(from, to);
+      }
+      if (thisDelta > MAX_DELTA) {
+        return new Range(from, to);
+      }
+      return cache[thisDelta].get(from);
+    }
+  }
+}
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 6bde14bf..a3a3aa0 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.PositionRangeAllocator.CardinalPositionRangeAllocator;
+import com.android.tools.r8.naming.PositionRangeAllocator.NonCardinalPositionRangeAllocator;
 import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
@@ -68,6 +70,11 @@
   private final boolean allowEmptyMappedRanges;
   private final boolean allowExperimentalMapping;
 
+  private final CardinalPositionRangeAllocator cardinalRangeCache =
+      PositionRangeAllocator.createCardinalPositionRangeAllocator();
+  private final NonCardinalPositionRangeAllocator nonCardinalRangeCache =
+      PositionRangeAllocator.createNonCardinalPositionRangeAllocator();
+
   @Override
   public void close() throws IOException {
     reader.close();
@@ -623,12 +630,12 @@
     int from = parseNumber();
     skipWhitespace();
     if (peekChar(0) != ':') {
-      return new Range(from);
+      return cardinalRangeCache.get(from);
     }
     expect(':');
     skipWhitespace();
     int to = parseNumber();
-    return new Range(from, to);
+    return nonCardinalRangeCache.get(from, to);
   }
 
   private int parseNumber() {
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 8545462..f292409 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -347,7 +347,16 @@
           DescriptorUtils.getBinaryNameFromJavaType(proguardConfiguration.getPackagePrefix());
       PackageObfuscationMode packageObfuscationMode =
           proguardConfiguration.getPackageObfuscationMode();
-      if (packageObfuscationMode.isRepackageClasses()) {
+      if (!appView.options().isMinifying()) {
+        // Preserve full package name under destination package when not minifying
+        // (no matter which package obfuscation mode is used).
+        if (newPackageDescriptor.isEmpty()
+            || proguardConfiguration.getKeepPackageNamesPatterns().matches(pkg)
+            || mayHavePinnedPackagePrivateOrProtectedItem(pkg)) {
+          return pkg.getPackageDescriptor();
+        }
+        return newPackageDescriptor + DESCRIPTOR_PACKAGE_SEPARATOR + pkg.getPackageDescriptor();
+      } else if (packageObfuscationMode.isRepackageClasses()) {
         return newPackageDescriptor;
       } else if (packageObfuscationMode.isMinification()) {
         // Always keep top-level classes since their packages can never be minified.
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index 7199fcb..9785028 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -61,7 +61,7 @@
   public boolean isRepackagingAllowed(
       ProgramDefinition definition, GlobalKeepInfoConfiguration configuration) {
     return configuration.isRepackagingEnabled()
-        && internalIsMinificationAllowed()
+        && internalIsRepackagingAllowed()
         && (definition.getAccessFlags().isPublic()
             || !internalIsAccessModificationRequiredForRepackaging());
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 9595b06..d9708ce 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -22,6 +22,7 @@
   private final boolean allowAnnotationRemoval;
   private final boolean allowMinification;
   private final boolean allowOptimization;
+  private final boolean allowRepackaging;
   private final boolean allowShrinking;
   private final boolean allowSignatureRemoval;
   private final boolean checkDiscarded;
@@ -32,6 +33,7 @@
       boolean allowAnnotationRemoval,
       boolean allowMinification,
       boolean allowOptimization,
+      boolean allowRepackaging,
       boolean allowShrinking,
       boolean allowSignatureRemoval,
       boolean checkDiscarded,
@@ -40,6 +42,7 @@
     this.allowAnnotationRemoval = allowAnnotationRemoval;
     this.allowMinification = allowMinification;
     this.allowOptimization = allowOptimization;
+    this.allowRepackaging = allowRepackaging;
     this.allowShrinking = allowShrinking;
     this.allowSignatureRemoval = allowSignatureRemoval;
     this.checkDiscarded = checkDiscarded;
@@ -52,6 +55,7 @@
         builder.isAnnotationRemovalAllowed(),
         builder.isMinificationAllowed(),
         builder.isOptimizationAllowed(),
+        builder.isRepackagingAllowed(),
         builder.isShrinkingAllowed(),
         builder.isSignatureRemovalAllowed(),
         builder.isCheckDiscardedEnabled(),
@@ -168,6 +172,10 @@
   public abstract boolean isRepackagingAllowed(
       ProgramDefinition definition, GlobalKeepInfoConfiguration configuration);
 
+  boolean internalIsRepackagingAllowed() {
+    return allowRepackaging;
+  }
+
   boolean internalIsAccessModificationRequiredForRepackaging() {
     return requireAccessModificationForRepackaging;
   }
@@ -234,6 +242,7 @@
         && (allowAnnotationRemoval || !other.internalIsAnnotationRemovalAllowed())
         && (allowMinification || !other.internalIsMinificationAllowed())
         && (allowOptimization || !other.internalIsOptimizationAllowed())
+        && (allowRepackaging || !other.internalIsRepackagingAllowed())
         && (allowShrinking || !other.internalIsShrinkingAllowed())
         && (allowSignatureRemoval || !other.internalIsSignatureRemovalAllowed())
         && (!checkDiscarded || other.internalIsCheckDiscardedEnabled());
@@ -256,6 +265,7 @@
     private boolean allowAccessModification;
     private boolean allowAnnotationRemoval;
     private boolean allowMinification;
+    private boolean allowRepackaging;
     private boolean allowOptimization;
     private boolean allowShrinking;
     private boolean allowSignatureRemoval;
@@ -272,6 +282,7 @@
       allowAnnotationRemoval = original.internalIsAnnotationRemovalAllowed();
       allowMinification = original.internalIsMinificationAllowed();
       allowOptimization = original.internalIsOptimizationAllowed();
+      allowRepackaging = original.internalIsRepackagingAllowed();
       allowShrinking = original.internalIsShrinkingAllowed();
       allowSignatureRemoval = original.internalIsSignatureRemovalAllowed();
       checkDiscarded = original.internalIsCheckDiscardedEnabled();
@@ -284,6 +295,7 @@
       disallowAnnotationRemoval();
       disallowMinification();
       disallowOptimization();
+      disallowRepackaging();
       disallowShrinking();
       disallowSignatureRemoval();
       unsetCheckDiscarded();
@@ -296,6 +308,7 @@
       allowAnnotationRemoval();
       allowMinification();
       allowOptimization();
+      allowRepackaging();
       allowShrinking();
       allowSignatureRemoval();
       unsetCheckDiscarded();
@@ -323,6 +336,7 @@
           && isAnnotationRemovalAllowed() == other.internalIsAnnotationRemovalAllowed()
           && isMinificationAllowed() == other.internalIsMinificationAllowed()
           && isOptimizationAllowed() == other.internalIsOptimizationAllowed()
+          && isRepackagingAllowed() == other.internalIsRepackagingAllowed()
           && isShrinkingAllowed() == other.internalIsShrinkingAllowed()
           && isSignatureRemovalAllowed() == other.internalIsSignatureRemovalAllowed()
           && isCheckDiscardedEnabled() == other.internalIsCheckDiscardedEnabled()
@@ -354,6 +368,10 @@
       return allowOptimization;
     }
 
+    public boolean isRepackagingAllowed() {
+      return allowRepackaging;
+    }
+
     public boolean isShrinkingAllowed() {
       return allowShrinking;
     }
@@ -375,6 +393,19 @@
       return setAllowMinification(false);
     }
 
+    public B setAllowRepackaging(boolean allowRepackaging) {
+      this.allowRepackaging = allowRepackaging;
+      return self();
+    }
+
+    public B allowRepackaging() {
+      return setAllowRepackaging(true);
+    }
+
+    public B disallowRepackaging() {
+      return setAllowRepackaging(false);
+    }
+
     public B setAllowOptimization(boolean allowOptimization) {
       this.allowOptimization = allowOptimization;
       return self();
@@ -577,6 +608,11 @@
       return self();
     }
 
+    public J disallowRepackaging() {
+      builder.disallowRepackaging();
+      return self();
+    }
+
     public J disallowOptimization() {
       builder.disallowOptimization();
       return self();
@@ -608,6 +644,7 @@
       applyIf(!builder.isAnnotationRemovalAllowed(), Joiner::disallowAnnotationRemoval);
       applyIf(!builder.isMinificationAllowed(), Joiner::disallowMinification);
       applyIf(!builder.isOptimizationAllowed(), Joiner::disallowOptimization);
+      applyIf(!builder.isRepackagingAllowed(), Joiner::disallowRepackaging);
       applyIf(!builder.isShrinkingAllowed(), Joiner::disallowShrinking);
       applyIf(!builder.isSignatureRemovalAllowed(), Joiner::disallowSignatureRemoval);
       applyIf(builder.isCheckDiscardedEnabled(), Joiner::setCheckDiscarded);
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 d804fa6..7275e72 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -1133,7 +1133,8 @@
       if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
         dependentMinimumKeepInfo
             .getOrCreateMinimumKeepInfoFor(preconditionEvent, clazz.getReference())
-            .disallowMinification();
+            .disallowMinification()
+            .disallowRepackaging();
       }
     }
 
@@ -1635,7 +1636,15 @@
       if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
         dependentMinimumKeepInfo
             .getOrCreateMinimumKeepInfoFor(preconditionEvent, item.getReference())
-            .disallowMinification();
+            .disallowMinification()
+            .disallowRepackaging();
+        context.markAsUsed();
+      }
+
+      if (appView.options().isRepackagingEnabled() && !modifiers.allowsObfuscation) {
+        dependentMinimumKeepInfo
+            .getOrCreateMinimumKeepInfoFor(preconditionEvent, item.getReference())
+            .disallowRepackaging();
         context.markAsUsed();
       }
 
@@ -1971,7 +1980,8 @@
     void shouldNotBeMinified(ProgramDefinition definition) {
       getDependentMinimumKeepInfo()
           .getOrCreateUnconditionalMinimumKeepInfoFor(definition.getReference())
-          .disallowMinification();
+          .disallowMinification()
+          .disallowRepackaging();
     }
 
     public boolean verifyKeptFieldsAreAccessedAndLive(AppView<AppInfoWithLiveness> appView) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 843a0f3..323c745 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
@@ -488,6 +489,22 @@
     return oracle.getSynthesizingContexts(clazz);
   }
 
+  public Map<DexType, List<DexProgramClass>> computeSyntheticContextsToSyntheticClasses(
+      AppView<?> appView) {
+    Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
+        new IdentityHashMap<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (isSyntheticClass(clazz)) {
+        for (DexType synthesizingContextType : getSynthesizingContextTypes(clazz.getType())) {
+          syntheticContextsToSyntheticClasses
+              .computeIfAbsent(synthesizingContextType, ignoreKey -> new ArrayList<>())
+              .add(clazz);
+        }
+      }
+    }
+    return syntheticContextsToSyntheticClasses;
+  }
+
   public interface SynthesizingContextOracle {
 
     Set<DexReference> getSynthesizingContexts(DexProgramClass clazz);
@@ -528,18 +545,25 @@
 
   private SynthesizingContext getSynthesizingContext(
       ProgramDefinition context, AppView<?> appView) {
+    InternalOptions options = appView.options();
     if (appView.hasClassHierarchy()) {
       AppInfoWithClassHierarchy appInfo = appView.appInfoWithClassHierarchy();
       return getSynthesizingContext(
-          context, appInfo.getClassToFeatureSplitMap(), appInfo.getStartupOrder());
+          context, appInfo.getClassToFeatureSplitMap(), options, appInfo.getStartupOrder());
     }
     return getSynthesizingContext(
-        context, ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(), StartupOrder.empty());
+        context,
+        ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
+        options,
+        StartupOrder.empty());
   }
 
   /** Used to find the synthesizing context for a new synthetic that is about to be created. */
   private SynthesizingContext getSynthesizingContext(
-      ProgramDefinition context, ClassToFeatureSplitMap featureSplits, StartupOrder startupOrder) {
+      ProgramDefinition context,
+      ClassToFeatureSplitMap featureSplits,
+      InternalOptions options,
+      StartupOrder startupOrder) {
     DexType contextType = context.getContextType();
     SyntheticDefinition<?, ?, ?> existingDefinition = pending.definitions.get(contextType);
     if (existingDefinition != null) {
@@ -555,7 +579,7 @@
           .getContext();
     }
     // This context is not nested in an existing synthetic context so create a new "leaf" context.
-    FeatureSplit featureSplit = featureSplits.getFeatureSplit(context, startupOrder, this);
+    FeatureSplit featureSplit = featureSplits.getFeatureSplit(context, options, startupOrder, this);
     return SynthesizingContext.fromNonSyntheticInputContext(context, featureSplit);
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index fbf344b..14b4d67 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -37,7 +37,6 @@
 import com.android.tools.r8.experimental.startup.StartupOrder;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
@@ -466,7 +465,7 @@
     return programResourcesMainDescriptor.get(resource);
   }
 
-  public void dump(Path output, DumpOptions options, Reporter reporter, DexItemFactory factory) {
+  public void dump(Path output, DumpOptions dumpOptions, InternalOptions options) {
     int nextDexIndex = 0;
     OpenOption[] openOptions =
         new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
@@ -476,26 +475,26 @@
       writeToZipStream(
           out,
           dumpBuildPropertiesFileName,
-          options.getBuildPropertiesFileContent().getBytes(),
+          dumpOptions.getBuildPropertiesFileContent().getBytes(),
           ZipEntry.DEFLATED);
-      if (options.getDesugaredLibraryJsonSource() != null) {
+      if (dumpOptions.getDesugaredLibraryJsonSource() != null) {
         writeToZipStream(
             out,
             dumpDesugaredLibraryFileName,
-            options.getDesugaredLibraryJsonSource().getBytes(),
+            dumpOptions.getDesugaredLibraryJsonSource().getBytes(),
             ZipEntry.DEFLATED);
-        if (options.dumpInputToFile()) {
-          reporter.warning(
+        if (dumpOptions.dumpInputToFile()) {
+          options.reporter.warning(
               "Dumping a compilation with desugared library on a file may prevent reproduction,"
                   + " use dumpInputToDirectory property instead.");
         }
       }
-      if (options.getParsedProguardConfiguration() != null) {
-        String proguardConfig = options.getParsedProguardConfiguration();
+      if (dumpOptions.getParsedProguardConfiguration() != null) {
+        String proguardConfig = dumpOptions.getParsedProguardConfiguration();
         writeToZipStream(out, dumpConfigFileName, proguardConfig.getBytes(), ZipEntry.DEFLATED);
       }
       if (proguardMapInputData != null) {
-        reporter.warning(
+        options.reporter.warning(
             "Dumping proguard map input data may have side effects due to I/O on Paths.");
         writeToZipStream(
             out,
@@ -506,7 +505,7 @@
       if (hasMainDexList()) {
         List<String> mainDexList = new ArrayList<>();
         if (hasMainDexListResources()) {
-          reporter.warning(
+          options.reporter.warning(
               "Dumping main dex list resources may have side effects due to I/O on Paths.");
           for (StringResource mainDexListResource : getMainDexListResources()) {
             mainDexList.add(mainDexListResource.getString());
@@ -518,25 +517,24 @@
         String join = StringUtils.join("\n", mainDexList);
         writeToZipStream(out, dumpMainDexListResourceFileName, join.getBytes(), ZipEntry.DEFLATED);
       }
-      if (options.hasMainDexKeepRules()) {
+      if (dumpOptions.hasMainDexKeepRules()) {
         writeToZipStream(
             out,
             dumpMainDexRulesResourceFileName,
-            StringUtils.joinLines(options.getMainDexKeepRules()).getBytes(),
+            StringUtils.joinLines(dumpOptions.getMainDexKeepRules()).getBytes(),
             ZipEntry.DEFLATED);
       }
       nextDexIndex =
           dumpProgramResources(
               dumpProgramFileName,
-              options.getFeatureSplitConfiguration(),
+              dumpOptions.getFeatureSplitConfiguration(),
               nextDexIndex,
               out,
-              reporter,
-              factory);
+              options);
       nextDexIndex = dumpClasspathResources(nextDexIndex, out);
       nextDexIndex = dumpLibraryResources(nextDexIndex, out);
     } catch (IOException | ResourceException e) {
-      throw reporter.fatalError(new ExceptionDiagnostic(e));
+      throw options.reporter.fatalError(new ExceptionDiagnostic(e));
     }
   }
 
@@ -584,10 +582,8 @@
       FeatureSplitConfiguration featureSplitConfiguration,
       int nextDexIndex,
       ZipOutputStream out,
-      Reporter reporter,
-      DexItemFactory dexItemFactory)
+      InternalOptions options)
       throws IOException, ResourceException {
-
     Map<FeatureSplit, String> featureSplitArchiveNames =
         dumpFeatureSplitFileNames(featureSplitConfiguration);
     Map<FeatureSplit, ByteArrayOutputStream> featureSplitArchiveByteStreams =
@@ -596,7 +592,7 @@
     try {
       ClassToFeatureSplitMap classToFeatureSplitMap =
           ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(
-              dexItemFactory, featureSplitConfiguration, reporter);
+              options.dexItemFactory(), featureSplitConfiguration, options.reporter);
       if (featureSplitConfiguration != null) {
         for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
           ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream();
@@ -624,11 +620,11 @@
                       nextDexIndex,
                       classDescriptor -> {
                         if (featureSplitConfiguration != null) {
-                          DexType type = dexItemFactory.createType(classDescriptor);
+                          DexType type = options.dexItemFactory().createType(classDescriptor);
                           SyntheticItems syntheticItems = null;
                           FeatureSplit featureSplit =
                               classToFeatureSplitMap.getFeatureSplit(
-                                  type, StartupOrder.empty(), syntheticItems);
+                                  type, options, StartupOrder.empty(), syntheticItems);
                           if (featureSplit != null && !featureSplit.isBase()) {
                             return featureSplitArchiveOutputStreams.get(featureSplit);
                           }
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 0c68c28..4653b96 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -443,9 +443,6 @@
 
   // Compute the marker to be placed in the main dex file.
   private Marker createMarker(Tool tool) {
-    if (tool == Tool.D8 && testing.dontCreateMarkerInD8) {
-      return null;
-    }
     Marker marker =
         new Marker(tool)
             .setVersion(Version.LABEL)
@@ -699,7 +696,10 @@
 
   @Override
   public boolean isRepackagingEnabled() {
-    return proguardConfiguration.getPackageObfuscationMode().isSome() && isMinifying();
+    return !debug
+        && proguardConfiguration != null
+        && proguardConfiguration.getPackageObfuscationMode().isSome()
+        && (isMinifying() || !isForceProguardCompatibilityEnabled());
   }
 
   @Override
@@ -1772,6 +1772,7 @@
 
   public static class TestingOptions {
 
+    public boolean neverReuseCfLocalRegisters = false;
     private boolean hasReadCheckDeterminism = false;
     private DeterminismChecker determinismChecker = null;
 
@@ -1892,7 +1893,6 @@
     public boolean enableExperimentalDesugaredLibraryKeepRuleGenerator = false;
     public boolean invertConditionals = false;
     public boolean placeExceptionalBlocksLast = false;
-    public boolean dontCreateMarkerInD8 = false;
     public boolean forceJumboStringProcessing = false;
     public boolean forcePcBasedEncoding = false;
     public int pcBasedDebugEncodingOverheadThreshold =
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 74b7b06..a0a2a74 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -57,6 +57,9 @@
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.PositionRangeAllocator;
+import com.android.tools.r8.naming.PositionRangeAllocator.CardinalPositionRangeAllocator;
+import com.android.tools.r8.naming.PositionRangeAllocator.NonCardinalPositionRangeAllocator;
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
 import com.android.tools.r8.naming.Range;
@@ -509,6 +512,11 @@
             ? new NativePcSupport()
             : new Pc2PcMappingSupport(appView.options().allowDiscardingResidualDebugInfo());
 
+    CardinalPositionRangeAllocator cardinalRangeCache =
+        PositionRangeAllocator.createCardinalPositionRangeAllocator();
+    NonCardinalPositionRangeAllocator nonCardinalRangeCache =
+        PositionRangeAllocator.createNonCardinalPositionRangeAllocator();
+
     // Collect which files contain which classes that need to have their line numbers optimized.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       boolean isSyntheticClass = appView.getSyntheticItems().isSyntheticClass(clazz);
@@ -718,10 +726,11 @@
                 && definition.getCode().asDexCode().getDebugInfo()
                     == DexDebugInfoForSingleLineMethod.getInstance()) {
               assert firstPosition.originalLine == lastPosition.originalLine;
-              obfuscatedRange = new Range(0, MAX_LINE_NUMBER);
+              obfuscatedRange = nonCardinalRangeCache.get(0, MAX_LINE_NUMBER);
             } else {
               obfuscatedRange =
-                  new Range(firstPosition.obfuscatedLine, lastPosition.obfuscatedLine);
+                  nonCardinalRangeCache.get(
+                      firstPosition.obfuscatedLine, lastPosition.obfuscatedLine);
             }
             ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.computeIfAbsent();
             MappedRange lastMappedRange =
@@ -732,9 +741,11 @@
                     firstPosition.method,
                     obfuscatedName,
                     obfuscatedRange,
-                    new Range(firstPosition.originalLine, lastPosition.originalLine),
+                    nonCardinalRangeCache.get(
+                        firstPosition.originalLine, lastPosition.originalLine),
                     firstPosition.caller,
-                    prunedInlinedClasses);
+                    prunedInlinedClasses,
+                    cardinalRangeCache);
             for (MappingInformation info : methodMappingInfo) {
               lastMappedRange.addMappingInformation(info, Unreachable::raise);
             }
@@ -758,10 +769,12 @@
                         classNamingBuilder,
                         position.getMethod(),
                         obfuscatedName,
-                        new Range(placeHolderLineToBeFixed, placeHolderLineToBeFixed),
-                        new Range(position.getLine(), position.getLine()),
+                        nonCardinalRangeCache.get(
+                            placeHolderLineToBeFixed, placeHolderLineToBeFixed),
+                        nonCardinalRangeCache.get(position.getLine(), position.getLine()),
                         position.getCallerPosition(),
-                        prunedInlinedClasses);
+                        prunedInlinedClasses,
+                        cardinalRangeCache);
                   });
               outlinesToFix
                   .computeIfAbsent(
@@ -838,7 +851,8 @@
       Range obfuscatedRange,
       Range originalLine,
       Position caller,
-      Map<DexType, String> prunedInlineHolder) {
+      Map<DexType, String> prunedInlineHolder,
+      CardinalPositionRangeAllocator cardinalRangeCache) {
     MappedRange lastMappedRange =
         classNamingBuilder.addMappedRange(
             obfuscatedRange,
@@ -859,7 +873,8 @@
           classNamingBuilder.addMappedRange(
               obfuscatedRange,
               getOriginalMethodSignature.apply(caller.getMethod()),
-              new Range(Math.max(caller.getLine(), 0)), // Prevent against "no-position".
+              cardinalRangeCache.get(
+                  Math.max(caller.getLine(), 0)), // Prevent against "no-position".
               obfuscatedName);
       if (caller.isRemoveInnerFramesIfThrowingNpe()) {
         lastMappedRange.addMappingInformation(
diff --git a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
index 28be79a..4fda093 100644
--- a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
+++ b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
@@ -68,7 +68,7 @@
             .build();
 
     Path dumpFile = temp.newFolder().toPath().resolve("dump.zip");
-    appIn.dump(dumpFile, options.dumpOptions, options.reporter, options.dexItemFactory());
+    appIn.dump(dumpFile, options.dumpOptions, options);
 
     AndroidApp appOut = AndroidApp.builder(options.reporter).addDump(dumpFile).build();
     assertEquals(1, appOut.getClassProgramResourcesForTesting().size());
diff --git a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
index 001c239..3cd934b 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
@@ -75,6 +75,7 @@
                                   .setProguardMapProducer(
                                       ProguardMapProducer.fromPath(
                                           dependencyRoot.resolve("r8lib.jar.map")))
+                                  .setLoadAllDefinitions(false)
                                   .build())
                           .setStackTrace(stackTrace)
                           .setRetracedStackTraceConsumer(retraced::addAll)
diff --git a/src/test/java/com/android/tools/r8/cf/CfDebugLocalStackMapVerificationTest.java b/src/test/java/com/android/tools/r8/cf/CfDebugLocalStackMapVerificationTest.java
index 32ce06a..5108c8d 100644
--- a/src/test/java/com/android/tools/r8/cf/CfDebugLocalStackMapVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/cf/CfDebugLocalStackMapVerificationTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.cf;
 
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertThrows;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -11,6 +12,7 @@
 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;
@@ -23,8 +25,8 @@
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
+/** Regression tests for b/237567012 */
 @RunWith(Parameterized.class)
-/** This is a regresson test for b/237567012 */
 public class CfDebugLocalStackMapVerificationTest extends TestBase {
 
   @Parameter() public TestParameters parameters;
@@ -34,6 +36,43 @@
     return getTestParameters().withCfRuntimes().build();
   }
 
+  static class SmallRepro {
+
+    public static void main(String[] args) {
+      RuntimeException x = new RuntimeException("FOO");
+      RuntimeException c = null;
+      try {
+        c = x;
+        throw c;
+      } catch (RuntimeException e) {
+        System.out.println(c);
+      }
+    }
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForJvm()
+        .addProgramClasses(SmallRepro.class)
+        .run(parameters.getRuntime(), SmallRepro.class)
+        .assertSuccessWithOutputThatMatches(containsString("FOO"));
+  }
+
+  @Test
+  public void testSmallReproD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(SmallRepro.class)
+        .setMinApi(AndroidApiLevel.B)
+        .addOptionsModification(
+            options -> {
+              options.testing.forceIRForCfToCfDesugar = true;
+              options.testing.neverReuseCfLocalRegisters = true;
+            })
+        .run(parameters.getRuntime(), SmallRepro.class)
+        // TODO(b/237567012): Run should succeed with printing of FOO.
+        .assertFailureWithErrorThatThrows(VerifyError.class);
+  }
+
   @Test
   public void testR8() throws Exception {
     // TODO(b/237567012): We should not fail compilation.
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 2a325ad..30b9700 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
@@ -10,6 +10,7 @@
 import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import java.util.List;
@@ -26,14 +27,18 @@
   public TestParameters parameters;
 
   @Parameter(1)
-  public List<Class<?>> startupClasses;
+  public boolean includeStartupClasses;
 
-  @Parameters(name = "{0}, startup classes: {1}")
+  @Parameters(name = "{0}, include startup classes: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(),
-        ImmutableList.of(
-            Collections.emptyList(), ImmutableList.of(StartupA.class, StartupB.class)));
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  private List<Class<?>> getStartupClasses() {
+    return includeStartupClasses
+        ? Collections.emptyList()
+        : ImmutableList.of(StartupA.class, StartupB.class);
   }
 
   @Test
@@ -50,20 +55,21 @@
                       StartupConfiguration.builder()
                           .apply(
                               builder ->
-                                  startupClasses.forEach(
-                                      startupClass ->
-                                          builder.addStartupClass(
-                                              StartupClass.dexBuilder()
-                                                  .setClassReference(
-                                                      toDexType(startupClass, dexItemFactory))
-                                                  .build())))
+                                  getStartupClasses()
+                                      .forEach(
+                                          startupClass ->
+                                              builder.addStartupClass(
+                                                  StartupClass.dexBuilder()
+                                                      .setClassReference(
+                                                          toDexType(startupClass, dexItemFactory))
+                                                      .build())))
                           .build());
             })
         .addHorizontallyMergedClassesInspector(
             inspector ->
                 inspector
                     .applyIf(
-                        startupClasses.isEmpty(),
+                        getStartupClasses().isEmpty(),
                         i ->
                             i.assertIsCompleteMergeGroup(
                                 StartupA.class,
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index f6f59e8..7d13c36 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
@@ -204,7 +205,7 @@
 
   @Test
   public void testPerFileIntermediate() throws Exception {
-    ProcessResult result = runDoublePerFileCompilation(true);
+    ProcessResult result = runDoublePerFileCompilation(Backend.CF, true);
     assertEquals(result.toString(), 0, result.exitCode);
     assertEquals(EXPECTED, result.stdout);
   }
@@ -212,7 +213,7 @@
   @Test
   public void testPerFileNonIntermediate() throws Exception {
     try {
-      runDoublePerFileCompilation(false);
+      runDoublePerFileCompilation(Backend.CF, false);
       fail("Should expect the compilation to fail.");
     } catch (CompilationFailedException e) {
       assertThat(
@@ -221,26 +222,60 @@
     }
   }
 
-  public ProcessResult runDoublePerFileCompilation(boolean intermediate) throws Exception {
+  @Test
+  public void testPerFileNonIntermediateDex() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    try {
+      runDoublePerFileCompilation(Backend.DEX, false);
+      fail("Should expect the compilation to fail.");
+    } catch (CompilationFailedException e) {
+      assertThat(
+          e.getCause().getMessage(),
+          containsString("Attempt at compiling intermediate artifact without its context"));
+    }
+  }
+
+  public ProcessResult runDoublePerFileCompilation(Backend firstRoundOutput, boolean intermediate)
+      throws Exception {
     List<byte[]> outputsRoundOne = new ArrayList<>();
-    testForD8(Backend.CF)
+    testForD8(firstRoundOutput)
         .addProgramClasses(CLASSES)
         .setMinApi(parameters.getApiLevel())
         .setIntermediate(true /* First round is always intermediate. */)
         .setProgramConsumer(
-            new ClassFileConsumer.ForwardingConsumer(null) {
-              @Override
-              public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
-                outputsRoundOne.add(data.copyByteData());
-              }
-            })
+            firstRoundOutput.isCf()
+                ? new ClassFileConsumer.ForwardingConsumer(null) {
+                  @Override
+                  public void accept(
+                      ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                    outputsRoundOne.add(data.copyByteData());
+                  }
+                }
+                : new DexFilePerClassFileConsumer.ForwardingConsumer(null) {
+                  @Override
+                  public void accept(
+                      String primaryClassDescriptor,
+                      ByteDataView data,
+                      Set<String> descriptors,
+                      DiagnosticsHandler handler) {
+                    outputsRoundOne.add(data.copyByteData());
+                  }
+
+                  @Override
+                  public boolean combineSyntheticClassesWithPrimaryClass() {
+                    return false;
+                  }
+                })
         .compile();
 
     List<Path> outputsRoundTwo = new ArrayList<>();
     for (byte[] bytes : outputsRoundOne) {
       outputsRoundTwo.add(
           testForD8(parameters.getBackend())
-              .addProgramClassFileData(bytes)
+              .applyIf(
+                  firstRoundOutput.isCf(),
+                  b -> b.addProgramClassFileData(bytes),
+                  b -> b.addProgramDexFileData(bytes))
               .setMinApi(parameters.getApiLevel())
               .setIntermediate(intermediate)
               .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassV2Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassV2Test.java
new file mode 100644
index 0000000..20f26fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassV2Test.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.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.R8_L8SHRINK;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+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 ConcurrentHashMapSubclassV2Test extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT = StringUtils.lines("foo");
+
+  private final TestParameters parameters;
+  private final CompilationSpecification compilationSpecification;
+  private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+  private final CompilationMode compilationMode;
+
+  @Parameters(name = "{0}, spec: {1}, {2}, {3}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        getJdk8Jdk11(),
+        DEFAULT_SPECIFICATIONS,
+        CompilationMode.values());
+  }
+
+  public ConcurrentHashMapSubclassV2Test(
+      TestParameters parameters,
+      LibraryDesugaringSpecification libraryDesugaringSpecification,
+      CompilationSpecification compilationSpecification,
+      CompilationMode compilationMode) {
+    this.parameters = parameters;
+    this.compilationSpecification = compilationSpecification;
+    this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+    this.compilationMode = compilationMode;
+  }
+
+  @Test
+  public void test() throws Exception {
+    Assume.assumeFalse(
+        "b/237701688", compilationMode.isDebug() && compilationSpecification == R8_L8SHRINK);
+    testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+        .addInnerClasses(getClass())
+        .setMode(compilationMode)
+        .addKeepMainRule(Executor.class)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @SuppressWarnings("unchecked")
+  static class Executor {
+    public static void main(String[] args) {
+      StringListConcurrentHashMap<String> map = new StringListConcurrentHashMap<>();
+      map.putKeyAndArray("foo");
+      System.out.println(map.keys().nextElement());
+    }
+  }
+
+  static class StringListConcurrentHashMap<K> extends ConcurrentHashMap<K, List<String>> {
+    StringListConcurrentHashMap() {
+      super();
+    }
+
+    void putKeyAndArray(K key) {
+      this.putIfAbsent(key, new ArrayList<>());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
index 5a2c8b0..28d4db2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.desugar.desugaredlibrary.test;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.L8TestBuilder;
@@ -164,6 +165,11 @@
     return this;
   }
 
+  public DesugaredLibraryTestBuilder<T> setMode(CompilationMode mode) {
+    builder.setMode(mode);
+    return this;
+  }
+
   private void withR8TestBuilder(Consumer<R8TestBuilder<?>> consumer) {
     if (!builder.isTestShrinkerBuilder()) {
       return;
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageClassWithKeepPackageNameOnTargetTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageClassWithDontObfuscateKeepPackageNameOnTargetTest.java
similarity index 67%
copy from src/test/java/com/android/tools/r8/repackage/RepackageClassWithKeepPackageNameOnTargetTest.java
copy to src/test/java/com/android/tools/r8/repackage/RepackageClassWithDontObfuscateKeepPackageNameOnTargetTest.java
index ce8088f..6fa0787 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageClassWithKeepPackageNameOnTargetTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageClassWithDontObfuscateKeepPackageNameOnTargetTest.java
@@ -5,9 +5,10 @@
 package com.android.tools.r8.repackage;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
@@ -18,11 +19,11 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class RepackageClassWithKeepPackageNameOnTargetTest extends RepackageTestBase {
+public class RepackageClassWithDontObfuscateKeepPackageNameOnTargetTest extends RepackageTestBase {
 
   private static final String DESTINATION_PACKAGE = "other.package";
 
-  public RepackageClassWithKeepPackageNameOnTargetTest(
+  public RepackageClassWithDontObfuscateKeepPackageNameOnTargetTest(
       String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
     super(flattenPackageHierarchyOrRepackageClasses, parameters);
   }
@@ -34,29 +35,25 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/241220445): Should be able to relocate when having -dontobfuscate in some way to
-    //  support mainline.
+    String originalPackage = DescriptorUtils.getPackageNameFromBinaryName(binaryName(Foo.class));
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .apply(this::configureRepackaging)
+        .addDontObfuscate()
+        .addKeepPackageNamesRule(originalPackage)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Foo::foo()")
         .inspect(
             inspector -> {
               ClassSubject clazz = inspector.clazz(Foo.class);
               assertThat(clazz, isPresent());
-              assertThat(clazz.getFinalName(), startsWith(DESTINATION_PACKAGE));
-              String relocatedPackageSuffix =
-                  DescriptorUtils.getPackageBinaryNameFromJavaType(
-                      clazz.getFinalName().substring(DESTINATION_PACKAGE.length() + 1));
-              String originalPackage =
-                  DescriptorUtils.getPackageBinaryNameFromJavaType(clazz.getOriginalName());
-              // TODO(b/241220445): Have a configuration where the suffix is identicial to the
-              // original.
-              assertNotEquals(relocatedPackageSuffix, originalPackage);
+              assertThat(clazz.getFinalName(), not(startsWith(DESTINATION_PACKAGE)));
+              String finalPackage =
+                  DescriptorUtils.getPackageNameFromBinaryName(clazz.getFinalBinaryName());
+              assertEquals(originalPackage, finalPackage);
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageClassWithKeepPackageNameOnTargetTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageClassesWithDontObfuscateTest.java
similarity index 81%
rename from src/test/java/com/android/tools/r8/repackage/RepackageClassWithKeepPackageNameOnTargetTest.java
rename to src/test/java/com/android/tools/r8/repackage/RepackageClassesWithDontObfuscateTest.java
index ce8088f..3a36aea 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageClassWithKeepPackageNameOnTargetTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageClassesWithDontObfuscateTest.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
@@ -18,11 +18,11 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class RepackageClassWithKeepPackageNameOnTargetTest extends RepackageTestBase {
+public class RepackageClassesWithDontObfuscateTest extends RepackageTestBase {
 
   private static final String DESTINATION_PACKAGE = "other.package";
 
-  public RepackageClassWithKeepPackageNameOnTargetTest(
+  public RepackageClassesWithDontObfuscateTest(
       String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
     super(flattenPackageHierarchyOrRepackageClasses, parameters);
   }
@@ -34,29 +34,26 @@
 
   @Test
   public void testR8() throws Exception {
-    // TODO(b/241220445): Should be able to relocate when having -dontobfuscate in some way to
-    //  support mainline.
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .apply(this::configureRepackaging)
+        .addDontObfuscate()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Foo::foo()")
         .inspect(
             inspector -> {
               ClassSubject clazz = inspector.clazz(Foo.class);
               assertThat(clazz, isPresent());
-              assertThat(clazz.getFinalName(), startsWith(DESTINATION_PACKAGE));
+              assertThat(clazz.getFinalName(), startsWith(DESTINATION_PACKAGE + "."));
               String relocatedPackageSuffix =
                   DescriptorUtils.getPackageBinaryNameFromJavaType(
                       clazz.getFinalName().substring(DESTINATION_PACKAGE.length() + 1));
               String originalPackage =
                   DescriptorUtils.getPackageBinaryNameFromJavaType(clazz.getOriginalName());
-              // TODO(b/241220445): Have a configuration where the suffix is identicial to the
-              // original.
-              assertNotEquals(relocatedPackageSuffix, originalPackage);
+              assertEquals(relocatedPackageSuffix, originalPackage);
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageDontObfuscateTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageDontObfuscateTest.java
index 5ece41d..39b3d43 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageDontObfuscateTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageDontObfuscateTest.java
@@ -7,11 +7,14 @@
 import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
 import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableList;
@@ -70,15 +73,23 @@
   }
 
   @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(Main.class)
-        .enableInliningAnnotations()
-        .apply(this::configureRepackaging)
-        .noMinification()
-        .compile()
+  public void testR8Full() throws Exception {
+    setup(testForR8(parameters.getBackend()))
+        .inspect(
+            inspector -> {
+              ClassSubject aClass = inspector.clazz(A.class);
+              assertThat(aClass, isPresentAndRenamed());
+            })
+        .run(
+            parameters.getRuntime(),
+            Main.class,
+            getRepackagePackage() + "." + A.class.getTypeName())
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8Compat() throws Exception {
+    setup(testForR8Compat(parameters.getBackend()))
         .inspect(
             inspector -> {
               ClassSubject aClass = inspector.clazz(A.class);
@@ -88,6 +99,17 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
+  private R8TestCompileResult setup(R8TestBuilder<?> r8TestBuilder) throws Exception {
+    return r8TestBuilder
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .apply(this::configureRepackaging)
+        .noMinification()
+        .compile();
+  }
+
   public static class A {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
index d57a5ab..a433265 100644
--- a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
+++ b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
@@ -12,12 +12,18 @@
 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.StartupClass;
-import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.experimental.startup.StartupItem;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.startup.StartupSyntheticPlacementTest.Main;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
 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.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -40,6 +46,18 @@
 
   @Test
   public void test() throws Exception {
+    List<StartupItem<ClassReference, MethodReference, ?>> startupList = new ArrayList<>();
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .apply(StartupTestingUtils.enableStartupInstrumentationUsingLogcat(parameters))
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(StartupTestingUtils.removeStartupListFromStdout(startupList::add))
+        .assertSuccessWithOutputLines(getExpectedOutput());
+
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepClassAndMembersRules(Main.class)
@@ -48,25 +66,27 @@
                 options
                     .getStartupOptions()
                     .setEnableMinimalStartupDex(true)
-                    .setEnableStartupCompletenessCheckForTesting()
-                    .setStartupConfiguration(
-                        StartupConfiguration.builder()
-                            .addStartupClass(
-                                StartupClass.dexBuilder()
-                                    .setClassReference(
-                                        toDexType(Main.class, options.dexItemFactory()))
-                                    .build())
-                            .addStartupClass(
-                                StartupClass.dexBuilder()
-                                    .setClassReference(
-                                        toDexType(AStartupClass.class, options.dexItemFactory()))
-                                    .build())
-                            .build()))
+                    .setEnableStartupCompletenessCheckForTesting())
         .enableInliningAnnotations()
+        .apply(testBuilder -> StartupTestingUtils.setStartupConfiguration(testBuilder, startupList))
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspectMultiDex(
             primaryDexInspector -> {
+              // Main should be in the primary dex.
+              ClassSubject mainClassSubject = primaryDexInspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+              assertThat(mainMethodSubject, isPresent());
+              assertTrue(
+                  mainMethodSubject.streamInstructions().noneMatch(InstructionSubject::isThrow));
+
+              MethodSubject onClickMethodSubject = mainClassSubject.uniqueMethodWithName("onClick");
+              assertThat(onClickMethodSubject, isPresent());
+              assertTrue(
+                  onClickMethodSubject.streamInstructions().anyMatch(InstructionSubject::isThrow));
+
               // StartupClass should be in the primary dex.
               ClassSubject startupClassSubject = primaryDexInspector.clazz(AStartupClass.class);
               assertThat(startupClassSubject, isPresent());
@@ -99,7 +119,11 @@
                       .anyMatch(InstructionSubject::isThrow));
             })
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("foo");
+        .assertSuccessWithOutputLines(getExpectedOutput());
+  }
+
+  private List<String> getExpectedOutput() {
+    return ImmutableList.of("foo");
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
index 7ae582f..0cce865 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
@@ -48,14 +48,18 @@
   public boolean enableMinimalStartupDex;
 
   @Parameter(2)
+  public boolean enableStartupCompletenessCheck;
+
+  @Parameter(3)
   public boolean useLambda;
 
-  @Parameters(name = "{0}, minimal startup dex: {1}, use lambda: {2}")
+  @Parameters(name = "{0}, minimal startup dex: {1}, completeness check: {2}, use lambda: {3}")
   public static List<Object[]> data() {
     return buildParameters(
         // N so that java.util.function.Consumer is present.
         getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.N).build(),
         BooleanUtils.values(),
+        BooleanUtils.values(),
         BooleanUtils.values());
   }
 
@@ -80,7 +84,10 @@
         .addKeepClassAndMembersRules(A.class, B.class, C.class)
         .addOptionsModification(
             options -> {
-              options.getStartupOptions().setEnableMinimalStartupDex(enableMinimalStartupDex);
+              options
+                  .getStartupOptions()
+                  .setEnableMinimalStartupDex(enableMinimalStartupDex)
+                  .setEnableStartupCompletenessCheckForTesting(enableStartupCompletenessCheck);
               options
                   .getTestingOptions()
                   .setMixedSectionLayoutStrategyInspector(getMixedSectionLayoutInspector());
@@ -90,7 +97,10 @@
         .compile()
         .inspectMultiDex(this::inspectPrimaryDex, this::inspectSecondaryDex)
         .run(parameters.getRuntime(), Main.class, Boolean.toString(useLambda))
-        .assertSuccessWithOutputLines(getExpectedOutput());
+        .applyIf(
+            enableStartupCompletenessCheck && useLambda,
+            runResult -> runResult.assertFailureWithErrorThatThrows(NullPointerException.class),
+            runResult -> runResult.assertSuccessWithOutputLines(getExpectedOutput()));
   }
 
   private List<String> getExpectedOutput() {
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
index 3087653..13c7ca7 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
@@ -47,11 +47,15 @@
   @Parameter(1)
   public boolean enableMinimalStartupDex;
 
-  @Parameters(name = "{0}, minimal startup dex: {1}")
+  @Parameter(2)
+  public boolean enableStartupCompletenessCheck;
+
+  @Parameters(name = "{0}, minimal startup dex: {1}, completeness check: {2}")
   public static List<Object[]> data() {
     return buildParameters(
         // N so that java.util.function.Consumer is present.
         getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.N).build(),
+        BooleanUtils.values(),
         BooleanUtils.values());
   }
 
@@ -76,7 +80,10 @@
         .addKeepClassAndMembersRules(A.class, C.class)
         .addOptionsModification(
             options -> {
-              options.getStartupOptions().setEnableMinimalStartupDex(enableMinimalStartupDex);
+              options
+                  .getStartupOptions()
+                  .setEnableMinimalStartupDex(enableMinimalStartupDex)
+                  .setEnableStartupCompletenessCheckForTesting(enableStartupCompletenessCheck);
               options
                   .getTestingOptions()
                   .setMixedSectionLayoutStrategyInspector(getMixedSectionLayoutInspector());
diff --git a/tools/r8_release.py b/tools/r8_release.py
index c00c15c..56828c0 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -392,7 +392,7 @@
       if match_count != 3:
         print(("Could not find the previous -dev release string to replace in " +
             "METADATA. Expected to find is mentioned 3 times. Please update %s " +
-            "manually and run again with options --google " +
+            "manually and run again with options --google3 " +
             "--use-existing-work-branch.") % metadata_path)
         sys.exit(1)
       sed(version_match_regexp, options.version, metadata_path)
diff --git a/tools/startup/adb_utils.py b/tools/startup/adb_utils.py
index 56ad643..2369aa7 100755
--- a/tools/startup/adb_utils.py
+++ b/tools/startup/adb_utils.py
@@ -254,9 +254,12 @@
     args.append('-W')
   cmd = create_adb_cmd(args, device_id)
   stdout = subprocess.check_output(cmd).decode('utf-8').strip()
-  expected_stdout = (
-      'Starting: Intent { cmp=%s/.%s }' % (app_id, activity[len(app_id)+1:]))
-  assert stdout.startswith(expected_stdout), 'was %s' % stdout
+  if activity.startswith(app_id):
+    expected_stdout = (
+        'Starting: Intent { cmp=%s/.%s }' % (app_id, activity[len(app_id)+1:]))
+  else:
+    expected_stdout = 'Starting: Intent { cmp=%s/%s }' % (app_id, activity)
+  assert stdout.startswith(expected_stdout), 'was %s, expected %s' % (stdout, expected_stdout)
   lines = stdout.splitlines()
   result = {}
   for line in lines:
@@ -337,11 +340,16 @@
   stderr = process_result.stderr.decode('utf-8')
   if process_result.returncode == 0:
     assert 'Success' in stdout
+  elif stdout.startswith('cmd: Failure calling service package: Broken pipe'):
+    assert app_id == 'com.google.android.youtube'
+    print('Waiting after broken pipe')
+    time.sleep(15)
   else:
     expected_error = (
         'java.lang.IllegalArgumentException: Unknown package: %s' % app_id)
     assert 'Failure [DELETE_FAILED_INTERNAL_ERROR]' in stdout \
-        or expected_error in stderr
+        or expected_error in stderr, \
+        'stdout: %s, stderr: %s' % (stdout, stderr)
 
 def unlock(device_id=None, device_pin=None):
   ensure_screen_on(device_id)
diff --git a/tools/startup/generate_startup_descriptors.py b/tools/startup/generate_startup_descriptors.py
index 26846bc..d4a33d0 100755
--- a/tools/startup/generate_startup_descriptors.py
+++ b/tools/startup/generate_startup_descriptors.py
@@ -25,13 +25,14 @@
         transform_classes_and_methods_to_r8_startup_descriptors(
             profile_classes_and_methods, options)
   write_tmp_startup_descriptors(current_startup_descriptors, iteration, options)
-  number_of_new_startup_descriptors = add_r8_startup_descriptors(
+  new_startup_descriptors = add_r8_startup_descriptors(
       startup_descriptors, current_startup_descriptors)
+  number_of_new_startup_descriptors = len(new_startup_descriptors) - len(startup_descriptors)
   if options.out is not None:
     print(
         'Found %i new startup descriptors in iteration %i'
             % (number_of_new_startup_descriptors, iteration + 1))
-  return number_of_new_startup_descriptors
+  return new_startup_descriptors
 
 def generate_startup_profile(options):
   logcat = None
@@ -96,7 +97,7 @@
       if message.startswith('START') \
           or message.startswith('Activity pause timeout for') \
           or message.startswith('Activity top resumed state loss timeout for') \
-          or message.startswith('Force removing')
+          or message.startswith('Force removing') \
           or message.startswith(
               'Launch timeout has expired, giving up wake lock!'):
         continue
@@ -166,37 +167,61 @@
   for class_or_method in classes_and_methods:
     descriptor = class_or_method.get('descriptor')
     flags = class_or_method.get('flags')
-    if flags.get('conditional_startup') \
-        and not options.include_conditional_startup:
-      continue
-    if flags.get('post_startup') \
-        and not flags.get('startup') \
-        and not options.include_post_startup:
-      continue
-    startup_descriptors.append(descriptor)
+    if should_include_startup_descriptor(descriptor, flags, options):
+      startup_descriptors.append(descriptor)
   return startup_descriptors
 
-def add_r8_startup_descriptors(startup_descriptors, startup_descriptors_to_add):
-  previous_number_of_startup_descriptors = len(startup_descriptors)
-  if previous_number_of_startup_descriptors == 0:
+def add_r8_startup_descriptors(old_startup_descriptors, startup_descriptors_to_add):
+  new_startup_descriptors = {}
+  if len(old_startup_descriptors) == 0:
     for startup_descriptor, flags in startup_descriptors_to_add.items():
-      startup_descriptors[startup_descriptor] = flags.copy()
+      new_startup_descriptors[startup_descriptor] = flags.copy()
   else:
+    # Merge the new startup descriptors with the old descriptors in a way so
+    # that new startup descriptors are added next to the startup descriptors
+    # they are close to in the newly generated list of startup descriptors.
+    startup_descriptors_to_add_after_key = {}
+    startup_descriptors_to_add_in_the_end = {}
+    closest_seen_startup_descriptor = None
     for startup_descriptor, flags in startup_descriptors_to_add.items():
-      if startup_descriptor in startup_descriptors:
-        # Merge flags.
-        existing_flags = startup_descriptors[startup_descriptor]
-        assert not flags['conditional_startup']
-        if flags['post_startup']:
-          existing_flags['post_startup'] = True
+      if startup_descriptor in old_startup_descriptors:
+        closest_seen_startup_descriptor = startup_descriptor
       else:
-        # Add new startup descriptor.
-        new_flags = flags.copy()
-        new_flags['conditional_startup'] = True
-        startup_descriptors[startup_descriptor] = new_flags
-  new_number_of_startup_descriptors = len(startup_descriptors)
-  return new_number_of_startup_descriptors \
-      - previous_number_of_startup_descriptors
+        if closest_seen_startup_descriptor is None:
+          # Insert this new startup descriptor in the end of the result.
+          startup_descriptors_to_add_in_the_end[startup_descriptor] = flags
+        else:
+          # Record that this should be inserted after
+          # closest_seen_startup_descriptor.
+          pending_startup_descriptors = \
+              startup_descriptors_to_add_after_key.setdefault(
+                  closest_seen_startup_descriptor, {})
+          pending_startup_descriptors[startup_descriptor] = flags
+    for startup_descriptor, flags in old_startup_descriptors.items():
+      # Merge flags if this also exists in startup_descriptors_to_add.
+      if startup_descriptor in startup_descriptors_to_add:
+        merged_flags = flags.copy()
+        other_flags = startup_descriptors_to_add[startup_descriptor]
+        assert not other_flags['conditional_startup']
+        if other_flags['post_startup']:
+          merged_flags['post_startup'] = True
+        new_startup_descriptors[startup_descriptor] = merged_flags
+      else:
+        new_startup_descriptors[startup_descriptor] = flags.copy()
+      # Flush startup descriptors that followed this item in the new trace.
+      if startup_descriptor in startup_descriptors_to_add_after_key:
+        pending_startup_descriptors = \
+            startup_descriptors_to_add_after_key[startup_descriptor]
+        for pending_startup_descriptor, pending_flags \
+            in pending_startup_descriptors.items():
+          new_startup_descriptors[pending_startup_descriptor] = \
+              pending_flags.copy()
+    # Insert remaining new startup descriptors in the end.
+    for startup_descriptor, flags \
+        in startup_descriptors_to_add_in_the_end.items():
+      assert startup_descriptor not in new_startup_descriptors
+      new_startup_descriptors[startup_descriptor] = flags.copy()
+  return new_startup_descriptors
 
 def write_tmp_binary_artifact(artifact, iteration, options, name):
   if not options.tmp_dir:
@@ -257,6 +282,16 @@
   result += startup_descriptor
   return result
 
+def should_include_startup_descriptor(descriptor, flags, options):
+  if flags.get('conditional_startup') \
+      and not options.include_conditional_startup:
+    return False
+  if flags.get('post_startup') \
+      and not flags.get('startup') \
+      and not options.include_post_startup:
+    return False
+  return True
+
 def parse_options(argv):
   result = argparse.ArgumentParser(
       description='Generate a perfetto trace file.')
@@ -329,7 +364,9 @@
     iteration = 0
     stable_iterations = 0
     while True:
-      diff = extend_startup_descriptors(startup_descriptors, iteration, options)
+      old_startup_descriptors = startup_descriptors
+      startup_descriptors = extend_startup_descriptors(old_startup_descriptors, iteration, options)
+      diff = len(startup_descriptors) - len(old_startup_descriptors)
       if diff == 0:
         stable_iterations = stable_iterations + 1
         if stable_iterations == options.until_stable_iterations:
@@ -339,15 +376,17 @@
       iteration = iteration + 1
   else:
     for iteration in range(options.iterations):
-      extend_startup_descriptors(startup_descriptors, iteration, options)
+      startup_descriptors = extend_startup_descriptors(startup_descriptors, iteration, options)
   if options.out is not None:
     with open(options.out, 'w') as f:
       for startup_descriptor, flags in startup_descriptors.items():
-        f.write(startup_descriptor_to_string(startup_descriptor, flags))
-        f.write('\n')
+        if should_include_startup_descriptor(startup_descriptor, flags, options):
+          f.write(startup_descriptor_to_string(startup_descriptor, flags))
+          f.write('\n')
   else:
     for startup_descriptor, flags in startup_descriptors.items():
-      print(startup_descriptor_to_string(startup_descriptor, flags))
+      if should_include_startup_descriptor(startup_descriptor, flags, options):
+        print(startup_descriptor_to_string(startup_descriptor, flags))
 
 if __name__ == '__main__':
   sys.exit(main(sys.argv[1:]))