Merge commit '151b9a12b0b81a300c9801b567e0424ae61b55a4' into dev-release
diff --git a/.gitignore b/.gitignore
index bd447f4..b3f67f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,8 +94,6 @@
 third_party/gradle/gradle
 third_party/gradle/gradle.tar.gz
 third_party/internal/*
-third_party/internal-apps/youtube_15_33
-third_party/internal-apps/youtube_15_33.tar.gz
 third_party/iosched_2019
 third_party/iosched_2019.tar.gz
 third_party/jacoco/0.8.2/*
@@ -250,8 +248,6 @@
 third_party/tachiyomi
 third_party/tachiyomi.tar.gz
 third_party/youtube/*
-third_party/youtube-developer/20200415
-third_party/youtube-developer/20200415.tar.gz
 tmp/
 tools/*.pyc
 tools/__pycache__
diff --git a/build.gradle b/build.gradle
index 9b2772c..5d4868a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -460,7 +460,6 @@
         "proto",
         "protobuf-lite",
         "retrace_internal",
-        "youtube/youtube.android_15.33",
         "youtube/youtube.android_16.20",
         "youtube/youtube.android_17.19"
     ],
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index b2e0f6d..7e93145 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -54,6 +54,7 @@
   private final DumpInputFlags dumpInputFlags;
   private final MapIdProvider mapIdProvider;
   private final SourceFileProvider sourceFileProvider;
+  private final boolean isAndroidPlatformBuild;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -72,6 +73,7 @@
     dumpInputFlags = DumpInputFlags.noDump();
     mapIdProvider = null;
     sourceFileProvider = null;
+    isAndroidPlatformBuild = false;
   }
 
   BaseCompilerCommand(
@@ -90,7 +92,8 @@
       int threadCount,
       DumpInputFlags dumpInputFlags,
       MapIdProvider mapIdProvider,
-      SourceFileProvider sourceFileProvider) {
+      SourceFileProvider sourceFileProvider,
+      boolean isAndroidPlatformBuild) {
     super(app);
     assert minApiLevel > 0;
     assert mode != null;
@@ -109,6 +112,7 @@
     this.dumpInputFlags = dumpInputFlags;
     this.mapIdProvider = mapIdProvider;
     this.sourceFileProvider = sourceFileProvider;
+    this.isAndroidPlatformBuild = isAndroidPlatformBuild;
   }
 
   /**
@@ -131,6 +135,9 @@
         .setOptimizeMultidexForLinearAlloc(isOptimizeMultidexForLinearAlloc())
         .setThreadCount(getThreadCount())
         .setDesugarState(getDesugarState());
+    if (getAndroidPlatformBuild()) {
+      builder.setAndroidPlatformBuild(true);
+    }
   }
 
   /**
@@ -197,6 +204,10 @@
     return threadCount;
   }
 
+  public boolean getAndroidPlatformBuild() {
+    return isAndroidPlatformBuild;
+  }
+
   DumpInputFlags getDumpInputFlags() {
     return dumpInputFlags;
   }
@@ -237,6 +248,7 @@
     private DumpInputFlags dumpInputFlags = DumpInputFlags.noDump();
     private MapIdProvider mapIdProvider = null;
     private SourceFileProvider sourceFileProvider = null;
+    private boolean isAndroidPlatformBuild = false;
 
     abstract CompilationMode defaultCompilationMode();
 
@@ -635,6 +647,23 @@
       return self();
     }
 
+    /**
+     * Configure the present build as a "Android platform build".
+     *
+     * <p>A platform build, is a build where the runtime "bootclasspath" is known at compile time.
+     * In other words, the specified <i>min-api</i> is also known to be the <i>max-api</i>. In this
+     * mode the compiler will disable various features that provide support for newer runtimes as
+     * well as disable workarounds for older runtimes.
+     */
+    public B setAndroidPlatformBuild(boolean isAndroidPlatformBuild) {
+      this.isAndroidPlatformBuild = isAndroidPlatformBuild;
+      return self();
+    }
+
+    public boolean getAndroidPlatformBuild() {
+      return isAndroidPlatformBuild;
+    }
+
     B dumpInputToFile(Path file) {
       dumpInputFlags = DumpInputFlags.dumpToFile(file);
       return self();
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 1495071..203b1d6 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -425,6 +425,7 @@
           getMapIdProvider(),
           proguardMapConsumer,
           enableMissingLibraryApiModeling,
+          getAndroidPlatformBuild(),
           factory);
     }
   }
@@ -516,6 +517,7 @@
       MapIdProvider mapIdProvider,
       StringConsumer proguardMapConsumer,
       boolean enableMissingLibraryApiModeling,
+      boolean isAndroidPlatformBuild,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -533,7 +535,8 @@
         threadCount,
         dumpInputFlags,
         mapIdProvider,
-        null);
+        null,
+        isAndroidPlatformBuild);
     this.intermediate = intermediate;
     this.globalSyntheticsConsumer = globalSyntheticsConsumer;
     this.desugarGraphConsumer = desugarGraphConsumer;
@@ -654,6 +657,8 @@
       horizontalClassMergerOptions.disable();
     }
 
+    internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
+
     internal.setDumpInputFlags(getDumpInputFlags(), skipDump);
     internal.dumpOptions = dumpOptions();
 
@@ -667,6 +672,7 @@
         .setIntermediate(intermediate)
         .setDesugaredLibraryConfiguration(desugaredLibrarySpecification)
         .setMainDexKeepRules(mainDexKeepRules)
+        .setEnableMissingLibraryApiModeling(enableMissingLibraryApiModeling)
         .build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
deleted file mode 100644
index 43b8e9a..0000000
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2018, 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;
-
-import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
-
-import com.android.tools.r8.DexIndexedConsumer.DirectoryConsumer;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.dex.ApplicationWriter;
-import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.LazyLoadedDexApplication;
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
-import com.android.tools.r8.utils.ExceptionUtils;
-import com.android.tools.r8.utils.FeatureClassMapping;
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.Timing;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-@Keep
-public final class DexSplitterHelper {
-
-  public static void run(
-      D8Command command, FeatureClassMapping featureClassMapping, String output, String proguardMap)
-      throws CompilationFailedException {
-    ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
-    try {
-      ExceptionUtils.withCompilationHandler(
-          command.getReporter(),
-          () -> run(command, featureClassMapping, output, proguardMap, executor));
-    } finally {
-      executor.shutdown();
-    }
-  }
-
-  public static void run(
-      D8Command command,
-      FeatureClassMapping featureClassMapping,
-      String output,
-      String proguardMap,
-      ExecutorService executor)
-      throws IOException {
-    InternalOptions options = command.getInternalOptions();
-    options.desugarState = DesugarState.OFF;
-    options.enableMainDexListCheck = false;
-    options.ignoreMainDexMissingClasses = true;
-    options.minimalMainDex = false;
-    assert !options.isMinifying();
-    options.inlinerOptions().enableInlining = false;
-    options.outline.enabled = false;
-
-    try {
-      Timing timing = new Timing("DexSplitter");
-      ApplicationReader applicationReader =
-          new ApplicationReader(command.getInputApp(), options, timing);
-      DexApplication app = applicationReader.read(executor);
-      MainDexInfo mainDexInfo = applicationReader.readMainDexClasses(app);
-
-      List<Marker> markers = app.dexItemFactory.extractMarkers();
-
-      ClassNameMapper mapper = null;
-      if (proguardMap != null) {
-        mapper = ClassNameMapper.mapperFromFile(Paths.get(proguardMap));
-      }
-      Map<String, LazyLoadedDexApplication.Builder> applications =
-          getDistribution(app, featureClassMapping, mapper);
-      for (Entry<String, LazyLoadedDexApplication.Builder> entry : applications.entrySet()) {
-        String feature = entry.getKey();
-        timing.begin("Feature " + feature);
-        DexApplication featureApp = entry.getValue().build();
-        assert !options.hasMethodsFilter();
-
-        // If this is the base, we add the main dex list.
-        AppInfo appInfo =
-            feature.equals(featureClassMapping.getBaseName())
-                ? AppInfo.createInitialAppInfo(
-                    featureApp, GlobalSyntheticsStrategy.forNonSynthesizing(), mainDexInfo)
-                : AppInfo.createInitialAppInfo(
-                    featureApp, GlobalSyntheticsStrategy.forNonSynthesizing());
-        AppView<AppInfo> appView = AppView.createForD8(appInfo);
-
-        // Run d8 optimize to ensure jumbo strings are handled.
-        D8.optimize(appView, options, timing, executor);
-
-        // We create a specific consumer for each split.
-        Path outputDir = Paths.get(output).resolve(entry.getKey());
-        if (!Files.exists(outputDir)) {
-          Files.createDirectory(outputDir);
-        }
-        DexIndexedConsumer consumer = new DirectoryConsumer(outputDir);
-
-        try {
-          new ApplicationWriter(
-                  appView,
-                  markers,
-                  consumer)
-              .write(executor);
-          options.printWarnings();
-        } finally {
-          consumer.finished(options.reporter);
-        }
-        timing.end();
-      }
-    } catch (ExecutionException e) {
-      throw unwrapExecutionException(e);
-    } catch (FeatureMappingException e) {
-      options.reporter.error(e.getMessage());
-    } finally {
-      options.signalFinishedToConsumers();
-    }
-  }
-
-  private static Map<String, LazyLoadedDexApplication.Builder> getDistribution(
-      DexApplication app, FeatureClassMapping featureClassMapping, ClassNameMapper mapper)
-      throws FeatureMappingException {
-    Map<String, LazyLoadedDexApplication.Builder> applications = new HashMap<>();
-    for (DexProgramClass clazz : app.classes()) {
-      String clazzName =
-          mapper != null ? mapper.deobfuscateClassName(clazz.toString()) : clazz.toString();
-      String feature = featureClassMapping.featureForClass(clazzName);
-      LazyLoadedDexApplication.Builder featureApplication = applications.get(feature);
-      if (featureApplication == null) {
-        featureApplication = DexApplication.builder(app.options, app.timing);
-        applications.put(feature, featureApplication);
-      }
-      featureApplication.addProgramClass(clazz);
-    }
-    return applications;
-  }
-
-  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/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 2b50253..dce9604 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -116,7 +116,8 @@
         threadCount,
         dumpInputFlags,
         mapIdProvider,
-        null);
+        null,
+        false);
     this.d8Command = d8Command;
     this.r8Command = r8Command;
     this.desugaredLibrarySpecification = desugaredLibrarySpecification;
@@ -302,6 +303,11 @@
     }
 
     @Override
+    public Builder setAndroidPlatformBuild(boolean isAndroidPlatformBuild) {
+      throw getReporter().fatalError("L8 does not support configuring Android platform builds.");
+    }
+
+    @Override
     void validate() {
       if (isPrintHelp()) {
         return;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d70426f..2fd907e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -441,7 +441,7 @@
       assert appView.appInfo().hasLiveness();
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
 
-      new StartupInstrumentation(appView).instrumentClasses(executorService);
+      new StartupInstrumentation(appView).instrumentAllClasses(executorService);
 
       assert verifyNoJarApplicationReaders(appView.appInfo().classes());
       assert appView.checkForTesting(() -> allReferencesAssignedApiLevel(appViewWithLiveness));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 8d323ab..309a475 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -634,7 +634,8 @@
               getDumpInputFlags(),
               getMapIdProvider(),
               getSourceFileProvider(),
-              enableMissingLibraryApiModeling);
+              enableMissingLibraryApiModeling,
+              getAndroidPlatformBuild());
 
       if (inputDependencyGraphConsumer != null) {
         inputDependencyGraphConsumer.finished();
@@ -821,7 +822,8 @@
       DumpInputFlags dumpInputFlags,
       MapIdProvider mapIdProvider,
       SourceFileProvider sourceFileProvider,
-      boolean enableMissingLibraryApiModeling) {
+      boolean enableMissingLibraryApiModeling,
+      boolean isAndroidPlatformBuild) {
     super(
         inputApp,
         mode,
@@ -838,7 +840,8 @@
         threadCount,
         dumpInputFlags,
         mapIdProvider,
-        sourceFileProvider);
+        sourceFileProvider,
+        isAndroidPlatformBuild);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
@@ -1038,6 +1041,8 @@
         SourceFileRewriter.computeSourceFileProvider(
             getSourceFileProvider(), proguardConfiguration, internal);
 
+    internal.configureAndroidPlatformBuild(getAndroidPlatformBuild());
+
     if (!DETERMINISTIC_DEBUGGING) {
       assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
       internal.threadCount = getThreadCount();
@@ -1086,6 +1091,7 @@
         .setProguardConfiguration(proguardConfiguration)
         .setMainDexKeepRules(mainDexKeepRules)
         .setDesugaredLibraryConfiguration(desugaredLibrarySpecification)
+        .setEnableMissingLibraryApiModeling(enableMissingLibraryApiModeling)
         .build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index dadb96a..3b80233 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -165,6 +165,7 @@
     Path outputPath = state.outputPath != null ? state.outputPath : Paths.get(".");
     OutputMode outputMode = state.outputMode != null ? state.outputMode : OutputMode.DexIndexed;
     builder.setOutput(outputPath, outputMode, state.includeDataResources);
+    builder.setEnableExperimentalMissingLibraryApiModeling(true);
     return builder;
   }
 
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index 2d836ae..2e44e14 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.bisect.Bisect;
 import com.android.tools.r8.cf.CfVerifierTool;
 import com.android.tools.r8.compatproguard.CompatProguard;
-import com.android.tools.r8.dexsplitter.DexSplitter;
 import com.android.tools.r8.relocator.RelocatorCommandLine;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import java.util.Arrays;
@@ -41,9 +40,6 @@
       case "dexsegments":
         DexSegments.main(shift(args));
         break;
-      case "dexsplitter":
-        DexSplitter.main(shift(args));
-        break;
       case "disasm":
         Disassemble.main(shift(args));
         break;
diff --git a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
index 19bf467..967453b 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -8,20 +8,24 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfo.PcBasedDebugInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.LebUtils;
 import com.android.tools.r8.utils.LineNumberOptimizer;
 import com.android.tools.r8.utils.StringUtils;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.ints.IntIterators;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -34,12 +38,12 @@
 
   public interface DebugRepresentationPredicate {
 
-    int getDexPcEncodingCutoff(DexProgramClass holder, DexEncodedMethod method);
+    int getDexPcEncodingCutoff(ProgramMethod method);
   }
 
   public static DebugRepresentationPredicate none(InternalOptions options) {
     assert !options.canUseDexPc2PcAsDebugInformation();
-    return (holder, method) -> NO_PC_ENCODING;
+    return method -> NO_PC_ENCODING;
   }
 
   public static DebugRepresentationPredicate fromFiles(
@@ -48,29 +52,33 @@
       return none(options);
     }
     if (options.canUseNativeDexPcInsteadOfDebugInfo()) {
-      return (holder, method) -> ALWAYS_PC_ENCODING;
+      return method -> ALWAYS_PC_ENCODING;
     }
     // TODO(b/220999985): Avoid the need to maintain a class-to-file map.
     Map<DexProgramClass, VirtualFile> classMapping = new IdentityHashMap<>();
     for (VirtualFile file : files) {
+      if (options.testing.debugRepresentationCallback != null) {
+        options.testing.debugRepresentationCallback.accept(file.getDebugRepresentation());
+      }
       file.classes().forEach(c -> classMapping.put(c, file));
     }
-    return (holder, method) -> {
-      if (!isPcCandidate(method, options)) {
+    return method -> {
+      if (!isPcCandidate(method.getDefinition(), options)) {
         return NO_PC_ENCODING;
       }
-      VirtualFile file = classMapping.get(holder);
+      VirtualFile file = classMapping.get(method.getHolder());
       DebugRepresentation cutoffs = file.getDebugRepresentation();
       int maxPc = cutoffs.getDexPcEncodingCutoff(method);
       assert maxPc == NO_PC_ENCODING
-          || verifyLastExecutableInstructionWithinBound(method.getCode().asDexCode(), maxPc);
+          || verifyLastExecutableInstructionWithinBound(
+              method.getDefinition().getCode().asDexCode(), maxPc);
       return maxPc;
     };
   }
 
-  private final Int2ReferenceMap<CostSummary> paramToInfo;
+  private final Int2ReferenceMap<ConversionInfo> paramToInfo;
 
-  private DebugRepresentation(Int2ReferenceMap<CostSummary> paramToInfo) {
+  private DebugRepresentation(Int2ReferenceMap<ConversionInfo> paramToInfo) {
     this.paramToInfo = paramToInfo;
   }
 
@@ -113,23 +121,31 @@
         int lastPc = lastInstruction.getOffset();
         int debugInfoCost = estimatedDebugInfoSize(debugInfo);
         paramCountToCosts
-            .computeIfAbsent(debugInfo.getParameterCount(), DebugRepresentation.CostSummary::new)
+            .computeIfAbsent(debugInfo.getParameterCount(), CostSummary::new)
             .addCost(lastPc, debugInfoCost);
       }
     }
     // Second compute the cost of converting to a pc encoding.
-    paramCountToCosts.forEach((ignored, summary) -> summary.computeConversionCosts(appView));
+    Int2ReferenceMap<ConversionInfo> conversions =
+        new Int2ReferenceOpenHashMap<>(paramCountToCosts.size());
+    paramCountToCosts.forEach(
+        (param, summary) -> conversions.put(param, summary.computeConversionCosts(appView)));
     // The result is stored on the virtual files for thread safety.
     // TODO(b/220999985): Consider just passing this to the line number optimizer once fixed.
-    file.setDebugRepresentation(new DebugRepresentation(paramCountToCosts));
+    file.setDebugRepresentation(new DebugRepresentation(conversions));
   }
 
-  private int getDexPcEncodingCutoff(DexEncodedMethod method) {
-    DexCode code = method.getCode().asDexCode();
+  private int getDexPcEncodingCutoff(ProgramMethod method) {
+    if (paramToInfo.isEmpty()) {
+      // This should only be the case if the method has overloads and thus *cannot* use pc encoding.
+      assert verifyMethodHasOverloads(method);
+      return NO_PC_ENCODING;
+    }
+    DexCode code = method.getDefinition().getCode().asDexCode();
     int paramCount = method.getParameters().size();
     assert code.getDebugInfo() == null || code.getDebugInfo().getParameterCount() == paramCount;
-    CostSummary conversionInfo = paramToInfo.get(paramCount);
-    if (conversionInfo == null || conversionInfo.cutoff < 0) {
+    ConversionInfo conversionInfo = paramToInfo.get(paramCount);
+    if (conversionInfo == null || !conversionInfo.hasConversions()) {
       // We expect all methods calling this to have computed conversion info.
       assert conversionInfo != null;
       return NO_PC_ENCODING;
@@ -139,14 +155,24 @@
       return NO_PC_ENCODING;
     }
     int maxPc = lastInstruction.getOffset();
-    return maxPc <= conversionInfo.cutoff ? conversionInfo.cutoff : NO_PC_ENCODING;
+    return conversionInfo.getConversionPointFor(maxPc);
+  }
+
+  private boolean verifyMethodHasOverloads(ProgramMethod method) {
+    assert 1
+        < IterableUtils.size(method.getHolder().methods(m -> m.getName().equals(method.getName())));
+    return true;
   }
 
   @Override
   public String toString() {
-    List<CostSummary> sorted = new ArrayList<>(paramToInfo.values());
+    return toString(false);
+  }
+
+  public String toString(boolean printCostSummary) {
+    List<ConversionInfo> sorted = new ArrayList<>(paramToInfo.values());
     sorted.sort(Comparator.comparing(i -> i.paramCount));
-    return StringUtils.join("\n", sorted, CostSummary::toString);
+    return StringUtils.join("\n", sorted, c -> c.toString(printCostSummary));
   }
 
   private static boolean isPcCandidate(DexEncodedMethod method, InternalOptions options) {
@@ -157,14 +183,32 @@
     return LineNumberOptimizer.mustHaveResidualDebugInfo(code, options);
   }
 
-  /** The cost of representing normal debug info for all methods with this max pc value. */
-  private static class PcNormalCost {
-
+  /** Cost information for debug info at a given PC. */
+  private static class PcCostInfo {
+    // PC point for which the information pertains to.
     final int pc;
-    int cost;
-    int methods;
 
-    public PcNormalCost(int pc) {
+    // Normal debug-info encoding cost.
+    int cost = 0;
+
+    // Number of methods this information pertains to.
+    int methods = 0;
+
+    @Override
+    public String toString() {
+      return "pc="
+          + pc
+          + ", cost="
+          + cost
+          + ", methods="
+          + methods
+          + ", saved="
+          + (cost - pc)
+          + ", overhead="
+          + getExpansionOverhead(pc, methods, cost);
+    }
+
+    public PcCostInfo(int pc) {
       assert pc >= 0;
       this.pc = pc;
     }
@@ -176,20 +220,76 @@
     }
   }
 
+  /** Conversion judgment up to a given PC bound (the lower bound is context dependent). */
+  private static class PcConversionInfo {
+    static final PcConversionInfo NO_CONVERSION = new PcConversionInfo(-1, false, 0, 0);
+
+    // PC bound for which the information pertains to.
+    final int pc;
+
+    // Judgment of whether the methods in this grouping should be converted to pc2pc encoding.
+    final boolean converted;
+
+    // Info for debugging.
+    private final int methods;
+    private final int normalCost;
+
+    public PcConversionInfo(int pc, boolean converted, int methods, int normalCost) {
+      this.pc = pc;
+      this.converted = converted;
+      this.methods = methods;
+      this.normalCost = normalCost;
+    }
+
+    @Override
+    public String toString() {
+      return "pc="
+          + pc
+          + ", converted="
+          + converted
+          + ", cost="
+          + normalCost
+          + ", methods="
+          + methods
+          + ", saved="
+          + (normalCost - pc)
+          + ", overhead="
+          + getExpansionOverhead(pc, methods, normalCost);
+    }
+  }
+
+  // A pc2pc stream is approximately one event more than the pc.
+  private static int pcEventCount(int pc) {
+    return pc + 1;
+  }
+
+  /**
+   * Figure for the overhead that the pc2pc encoding can result in.
+   *
+   * <p>This overhead is not in the encoding size but rather if the encoding is expanded at each
+   * method referencing the shared PC encoding.
+   */
+  private static int getExpansionOverhead(int currentPc, int methodCount, int normalCost) {
+    long expansion = ((long) pcEventCount(currentPc)) * methodCount;
+    long cost = expansion - normalCost;
+    return cost > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) cost;
+  }
+
+  private static boolean isWithinExpansionThreshold(
+      int threshold, int currentPc, int methodCount, int normalCost) {
+    // A negative threshold denotes unbounded.
+    if (threshold < 0) {
+      return true;
+    }
+    return getExpansionOverhead(currentPc, methodCount, normalCost) <= threshold;
+  }
+
   /** The summary of normal costs for all debug info with a particular parameter size. */
   private static class CostSummary {
-
     private final int paramCount;
 
     // Values for the normal encoding costs per-pc.
-    private final Int2ReferenceMap<PcNormalCost> pcToCost = new Int2ReferenceOpenHashMap<>();
-    private int minPc = Integer.MAX_VALUE;
-    private int maxPc = Integer.MIN_VALUE;
-
-    // Values for the conversion costs. These are computed only after all per-pc costs are known.
-    private int cutoff;
-    private int normalPreCutoffCost;
-    private int normalPostCutoffCost;
+    private Int2ReferenceMap<PcCostInfo> pcToCost = new Int2ReferenceOpenHashMap<>();
 
     private CostSummary(int paramCount) {
       assert paramCount >= 0;
@@ -198,82 +298,166 @@
 
     private void addCost(int pc, int cost) {
       assert pc >= 0;
-      pcToCost.computeIfAbsent(pc, PcNormalCost::new).add(cost);
-      minPc = Math.min(minPc, pc);
-      maxPc = Math.max(maxPc, pc);
+      pcToCost.computeIfAbsent(pc, PcCostInfo::new).add(cost);
     }
 
-    private void computeConversionCosts(AppView<?> appView) {
+    private static class ConversionState {
+      Int2ReferenceSortedMap<PcConversionInfo> groups = new Int2ReferenceAVLTreeMap<>();
+      PcConversionInfo converted = PcConversionInfo.NO_CONVERSION;
+      int flushedPc = 0;
+      int unconvertedPc = 0;
+      int normalCost = 0;
+      int methods = 0;
+
+      void reset() {
+        converted = PcConversionInfo.NO_CONVERSION;
+        unconvertedPc = 0;
+        normalCost = 0;
+        methods = 0;
+      }
+
+      void add(PcCostInfo costInfo) {
+        methods += costInfo.methods;
+        normalCost += costInfo.cost;
+      }
+
+      void flush() {
+        if (flushedPc < converted.pc) {
+          groups.put(converted.pc, converted);
+          flushedPc = converted.pc;
+        }
+        if (flushedPc < unconvertedPc) {
+          if (0 < flushedPc && !groups.get(flushedPc).converted) {
+            groups.remove(flushedPc);
+          }
+          PcConversionInfo unconverted =
+              new PcConversionInfo(unconvertedPc, false, methods, normalCost);
+          groups.put(unconvertedPc, unconverted);
+          flushedPc = unconvertedPc;
+        }
+        reset();
+      }
+
+      void update(int currentPc, boolean convertToPc) {
+        if (convertToPc) {
+          converted = new PcConversionInfo(currentPc, true, methods, normalCost);
+          unconvertedPc = 0;
+        } else {
+          unconvertedPc = currentPc;
+        }
+      }
+
+      public Int2ReferenceSortedMap<PcConversionInfo> getFinalConversions() {
+        // If there is only a single group check it is actually a converted range.
+        if (groups.size() > 1
+            || (groups.size() == 1 && groups.values().iterator().next().converted)) {
+          return groups;
+        }
+        return null;
+      }
+    }
+
+    private ConversionInfo computeConversionCosts(AppView<?> appView) {
+      int threshold = appView.options().testing.pcBasedDebugEncodingOverheadThreshold;
       boolean forcePcBasedEncoding = appView.options().testing.forcePcBasedEncoding;
       assert !pcToCost.isEmpty();
-      // Point at which it is estimated that conversion to PC-encoding is viable.
-      int currentConvertedPc = -1;
-      // The normal cost of the part that is viable for conversion (this is just for debugging).
-      int normalConvertedCost = 0;
-      // The normal cost of the part that is not yet part of the converted range.
-      int normalOutstandingCost = 0;
       // Iterate in ascending order as the point conversion cost is the sum of the preceding costs.
       int[] sortedPcs = new int[pcToCost.size()];
       IntIterators.unwrap(pcToCost.keySet().iterator(), sortedPcs);
       Arrays.sort(sortedPcs);
+      ConversionState state = new ConversionState();
       for (int currentPc : sortedPcs) {
-        PcNormalCost pcSummary = pcToCost.get(currentPc);
-        // The cost of the debug info unconverted is the sum of the unconverted up to this point.
-        normalOutstandingCost += pcSummary.cost;
-        // The cost of the conversion is the delta between the already converted and the current.
-        // This does not account for the header overhead on converting the first point. However,
-        // the few bytes overhead per param-count should not affect much.
-        int costToConvert = currentPc - currentConvertedPc;
-        // If the estimated cost is larger we convert. The order here could be either way as
-        // both the normal cost and converted cost are estimates. Canonicalization could reduce
-        // the former and compaction could reduce the latter.
-        if (forcePcBasedEncoding || normalOutstandingCost > costToConvert) {
-          normalConvertedCost += normalOutstandingCost;
-          normalOutstandingCost = 0;
-          currentConvertedPc = currentPc;
+        PcCostInfo current = pcToCost.get(currentPc);
+        assert currentPc == current.pc;
+        // Don't extend the conversion group as it could potentially become too large if expanded.
+        // Any pending conversion can be flushed now.
+        if (!isWithinExpansionThreshold(
+            threshold,
+            currentPc,
+            state.methods + current.methods,
+            state.normalCost + current.cost)) {
+          state.flush();
         }
+        state.add(current);
+        int costToConvert = pcEventCount(currentPc);
+        boolean canExpand =
+            isWithinExpansionThreshold(threshold, currentPc, state.methods, state.normalCost);
+        state.update(
+            currentPc, canExpand && (forcePcBasedEncoding || state.normalCost > costToConvert));
       }
-      cutoff = currentConvertedPc;
-      normalPreCutoffCost = normalConvertedCost;
-      normalPostCutoffCost = normalOutstandingCost;
-      assert cutoff >= -1;
-      assert normalPreCutoffCost >= 0;
-      assert normalPostCutoffCost >= 0;
-      assert preCutoffPcCost() >= 0;
-      assert postCutoffPcCost() >= 0;
-    }
-
-    private int preCutoffPcCost() {
-      return cutoff > 0 ? PcBasedDebugInfo.estimatedWriteSize(paramCount, cutoff) : 0;
-    }
-
-    private int postCutoffPcCost() {
-      return cutoff < maxPc ? PcBasedDebugInfo.estimatedWriteSize(paramCount, maxPc - cutoff) : 0;
+      state.flush();
+      return new ConversionInfo(
+          paramCount,
+          state.getFinalConversions(),
+          appView.options().testing.debugRepresentationCallback != null ? this : null);
     }
 
     @Override
     public String toString() {
       StringBuilder builder = new StringBuilder();
-      builder
-          .append("p:")
-          .append(paramCount)
-          .append(", c:")
-          .append(cutoff)
-          .append(", m:")
-          .append(maxPc);
-      if (cutoff > 0) {
-        builder
-            .append(", preCutNormal:")
-            .append(normalPreCutoffCost)
-            .append(", preCutPC:")
-            .append(preCutoffPcCost());
+      builder.append("params:").append(paramCount).append('\n');
+      Collection<Integer> keys = CollectionUtils.sort(pcToCost.keySet(), Integer::compareTo);
+      for (int key : keys) {
+        builder.append(pcToCost.get(key)).append('\n');
       }
-      if (cutoff < maxPc) {
-        builder
-            .append(", postCutNormal:")
-            .append(normalPostCutoffCost)
-            .append(", postCutPC:")
-            .append(postCutoffPcCost());
+      return builder.toString();
+    }
+  }
+
+  /** The computed conversion points all debug info with a particular parameter size. */
+  private static class ConversionInfo {
+    private final int paramCount;
+    private final Int2ReferenceSortedMap<PcConversionInfo> conversions;
+
+    // For debugging purposes.
+    private final CostSummary costSummary;
+
+    private ConversionInfo(
+        int paramCount,
+        Int2ReferenceSortedMap<PcConversionInfo> conversions,
+        CostSummary costSummary) {
+      assert paramCount >= 0;
+      this.paramCount = paramCount;
+      this.conversions = conversions;
+      this.costSummary = costSummary;
+    }
+
+    boolean hasConversions() {
+      return conversions != null;
+    }
+
+    int getConversionPointFor(int pc) {
+      Int2ReferenceSortedMap<PcConversionInfo> tailMap = conversions.tailMap(pc);
+      if (tailMap.isEmpty()) {
+        return -1;
+      }
+      int pcGroupBound = tailMap.firstIntKey();
+      PcConversionInfo entryUpToIncludingMax = conversions.get(pcGroupBound);
+      if (entryUpToIncludingMax.converted) {
+        assert pcGroupBound == entryUpToIncludingMax.pc;
+        return pcGroupBound;
+      }
+      return -1;
+    }
+
+    @Override
+    public String toString() {
+      return toString(false);
+    }
+
+    public String toString(boolean printCostSummaries) {
+      StringBuilder builder = new StringBuilder();
+      builder.append("params:").append(paramCount).append('\n');
+      if (conversions != null) {
+        for (PcConversionInfo group : conversions.values()) {
+          builder.append(group).append('\n');
+        }
+      } else {
+        builder.append(" no conversions").append('\n');
+      }
+      if (printCostSummaries && costSummary != null) {
+        builder.append("Cost summaries:\n");
+        builder.append(costSummary);
       }
       return builder.toString();
     }
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 d4f1195..3e59e50 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -727,16 +727,18 @@
               options.itemFactory));
     }
 
-    if (clazz.isNestHost()) {
-      annotations.add(
-          DexAnnotation.createNestMembersAnnotation(
-              clazz.getNestMembersClassAttributes(), options.itemFactory));
-    }
+    if (options.emitNestAnnotationsInDex) {
+      if (clazz.isNestHost()) {
+        annotations.add(
+            DexAnnotation.createNestMembersAnnotation(
+                clazz.getNestMembersClassAttributes(), options.itemFactory));
+      }
 
-    if (clazz.isNestMember()) {
-      annotations.add(
-          DexAnnotation.createNestHostAnnotation(
-              clazz.getNestHostClassAttribute(), options.itemFactory));
+      if (clazz.isNestMember()) {
+        annotations.add(
+            DexAnnotation.createNestHostAnnotation(
+                clazz.getNestHostClassAttribute(), options.itemFactory));
+      }
     }
 
     if (clazz.hasPermittedSubclassAttributes() && options.canUseSealedClasses()) {
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 5454b0f..7143d6b 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionLayoutStrategy.java
@@ -29,11 +29,22 @@
                 .getStartupOrder()
                 .toStartupOrderForWriting(appView)
             : StartupOrder.empty();
-    if (startupOrderForWriting.isEmpty()) {
-      return new DefaultMixedSectionLayoutStrategy(appView, mixedSectionOffsets);
-    }
-    return new StartupMixedSectionLayoutStrategy(
-        appView, mixedSectionOffsets, startupOrderForWriting, virtualFile);
+    MixedSectionLayoutStrategy mixedSectionLayoutStrategy =
+        startupOrderForWriting.isEmpty()
+            ? new DefaultMixedSectionLayoutStrategy(appView, mixedSectionOffsets)
+            : new StartupMixedSectionLayoutStrategy(
+                appView, mixedSectionOffsets, startupOrderForWriting, virtualFile);
+    return wrapForTesting(appView, mixedSectionLayoutStrategy, virtualFile);
+  }
+
+  private static MixedSectionLayoutStrategy wrapForTesting(
+      AppView<?> appView,
+      MixedSectionLayoutStrategy mixedSectionLayoutStrategy,
+      VirtualFile virtualFile) {
+    return appView
+        .testing()
+        .mixedSectionLayoutStrategyInspector
+        .apply(mixedSectionLayoutStrategy, virtualFile);
   }
 
   public abstract Collection<DexAnnotation> getAnnotationLayout();
diff --git a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
index b5ccc6b..eb03451 100644
--- a/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
+++ b/src/main/java/com/android/tools/r8/dex/StartupMixedSectionLayoutStrategy.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.dex.FileWriter.MixedSectionOffsets;
+import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupOrder;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -30,7 +31,6 @@
 import java.util.Collection;
 import java.util.LinkedHashSet;
 import java.util.Map;
-import java.util.Set;
 
 public class StartupMixedSectionLayoutStrategy extends DefaultMixedSectionLayoutStrategy {
 
@@ -79,8 +79,9 @@
             virtualFile.classes().size());
     LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView, true);
     StartupIndexedItemCollection indexedItemCollection = new StartupIndexedItemCollection();
-    for (DexType startupClass : startupOrderForWriting.getClasses()) {
-      DexProgramClass definition = virtualFileDefinitions.get(startupClass);
+    for (StartupClass<DexType> startupClass : startupOrderForWriting.getClasses()) {
+      assert !startupClass.isSynthetic();
+      DexProgramClass definition = virtualFileDefinitions.get(startupClass.getReference());
       if (definition != null) {
         definition.collectIndexedItems(appView, indexedItemCollection, rewriter);
       }
@@ -120,10 +121,7 @@
 
   @Override
   public Collection<ProgramMethod> getCodeLayout() {
-    Set<DexProgramClass> nonStartupClasses =
-        new LinkedHashSet<>(mixedSectionOffsets.getClassesWithData());
-    nonStartupClasses.removeIf(clazz -> startupOrderForWriting.contains(clazz.getType()));
-    return amendStartupLayout(codeLayout, super.getCodeLayoutForClasses(nonStartupClasses));
+    return amendStartupLayout(codeLayout, super.getCodeLayout());
   }
 
   @Override
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 80b9a6c..839545d 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -10,7 +10,10 @@
 import com.android.tools.r8.debuginfo.DebugRepresentation;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.experimental.startup.StartupOrder;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
+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;
@@ -44,6 +47,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
@@ -407,15 +411,17 @@
         return ImmutableMap.of();
       }
 
+      AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+          appView.withClassHierarchy();
       ClassToFeatureSplitMap classToFeatureSplitMap =
-          appView.appInfo().withClassHierarchy().getClassToFeatureSplitMap();
+          appViewWithClassHierarchy.appInfo().getClassToFeatureSplitMap();
       if (classToFeatureSplitMap.isEmpty()) {
         return ImmutableMap.of();
       }
 
       // Pull out the classes that should go into feature splits.
       Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses =
-          classToFeatureSplitMap.getFeatureSplitClasses(classes, appView.getSyntheticItems());
+          classToFeatureSplitMap.getFeatureSplitClasses(classes, appViewWithClassHierarchy);
       if (featureSplitClasses.size() > 0) {
         for (Set<DexProgramClass> featureClasses : featureSplitClasses.values()) {
           classes.removeAll(featureClasses);
@@ -939,11 +945,19 @@
         if (!appView.hasClassHierarchy()) {
           return alwaysFalse();
         }
-        ClassToFeatureSplitMap classToFeatureSplitMap =
-            appView.appInfo().withClassHierarchy().getClassToFeatureSplitMap();
+        StartupOrder startupOrder = appView.appInfoWithClassHierarchy().getStartupOrder();
         SyntheticItems syntheticItems = appView.getSyntheticItems();
-        return clazz ->
-            classToFeatureSplitMap.getFeatureSplit(clazz, syntheticItems).isStartupBase();
+        return clazz -> {
+          if (syntheticItems.isSyntheticClass(clazz)) {
+            return Iterables.any(
+                syntheticItems.getSynthesizingContextTypes(clazz.getType()),
+                startupOrder::containsSyntheticClassesSynthesizedFrom);
+          } else {
+            StartupClass<DexType> startupClass =
+                StartupClass.<DexType>builder().setReference(clazz.getType()).build();
+            return startupOrder.contains(startupClass);
+          }
+        };
       }
 
       public List<DexProgramClass> getStartupClasses() {
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
deleted file mode 100644
index 4ee416c..0000000
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ /dev/null
@@ -1,377 +0,0 @@
-// Copyright (c) 2018, 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.dexsplitter;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.DexSplitterHelper;
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.AbortException;
-import com.android.tools.r8.utils.ExceptionDiagnostic;
-import com.android.tools.r8.utils.ExceptionUtils;
-import com.android.tools.r8.utils.FeatureClassMapping;
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.android.tools.r8.utils.OptionsParsing;
-import com.android.tools.r8.utils.OptionsParsing.ParseContext;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-@Keep
-public final class DexSplitter {
-
-  private static final String DEFAULT_OUTPUT_DIR = "output";
-  private static final String DEFAULT_BASE_NAME = "base";
-
-  private static final boolean PRINT_ARGS = false;
-
-  public static class FeatureJar {
-    private String jar;
-    private String outputName;
-
-    public FeatureJar(String jar, String outputName) {
-      this.jar = jar;
-      this.outputName = outputName;
-    }
-
-    public FeatureJar(String jar) {
-      this(jar, featureNameFromJar(jar));
-    }
-
-    public String getJar() {
-      return jar;
-    }
-
-    public String getOutputName() {
-      return outputName;
-    }
-
-    private static String featureNameFromJar(String jar) {
-      Path jarPath = Paths.get(jar);
-      String featureName = jarPath.getFileName().toString();
-      if (featureName.endsWith(".jar") || featureName.endsWith(".zip")) {
-        featureName = featureName.substring(0, featureName.length() - 4);
-      }
-      return featureName;
-    }
-  }
-
-  private static class ZipFileOrigin extends PathOrigin {
-
-    public ZipFileOrigin(Path path) {
-      super(path);
-    }
-
-    @Override
-    public String part() {
-      return "splitting of file '" + super.part() + "'";
-    }
-  }
-
-  @Keep
-  public static final class Options {
-    private final DiagnosticsHandler diagnosticsHandler;
-    private List<String> inputArchives = new ArrayList<>();
-    private List<FeatureJar> featureJars = new ArrayList<>();
-    private List<String> baseJars = new ArrayList<>();
-    private String baseOutputName = DEFAULT_BASE_NAME;
-    private String output = DEFAULT_OUTPUT_DIR;
-    private String featureSplitMapping;
-    private String proguardMap;
-    private String mainDexList;
-    private boolean splitNonClassResources = false;
-
-    public Options() {
-      this(new DiagnosticsHandler() {});
-    }
-
-    public Options(DiagnosticsHandler diagnosticsHandler) {
-      this.diagnosticsHandler = diagnosticsHandler;
-    }
-
-    public DiagnosticsHandler getDiagnosticsHandler() {
-      return diagnosticsHandler;
-    }
-
-    public String getMainDexList() {
-      return mainDexList;
-    }
-
-    public void setMainDexList(String mainDexList) {
-      this.mainDexList = mainDexList;
-    }
-
-    public String getOutput() {
-      return output;
-    }
-
-    public void setOutput(String output) {
-      this.output = output;
-    }
-
-    public String getFeatureSplitMapping() {
-      return featureSplitMapping;
-    }
-
-    public void setFeatureSplitMapping(String featureSplitMapping) {
-      this.featureSplitMapping = featureSplitMapping;
-    }
-
-    public String getProguardMap() {
-      return proguardMap;
-    }
-
-    public void setProguardMap(String proguardMap) {
-      this.proguardMap = proguardMap;
-    }
-
-    public String getBaseOutputName() {
-      return baseOutputName;
-    }
-
-    public void setBaseOutputName(String baseOutputName) {
-      this.baseOutputName = baseOutputName;
-    }
-
-    public void addInputArchive(String inputArchive) {
-      inputArchives.add(inputArchive);
-    }
-
-    public void addBaseJar(String baseJar) {
-      baseJars.add(baseJar);
-    }
-
-    private void addFeatureJar(FeatureJar featureJar) {
-      featureJars.add(featureJar);
-    }
-
-    public void addFeatureJar(String jar) {
-      featureJars.add(new FeatureJar(jar));
-    }
-
-    public void addFeatureJar(String jar, String outputName) {
-      featureJars.add(new FeatureJar(jar, outputName));
-    }
-
-    public void setSplitNonClassResources(boolean value) {
-      splitNonClassResources = value;
-    }
-
-    public ImmutableList<String> getInputArchives() {
-      return ImmutableList.copyOf(inputArchives);
-    }
-
-    ImmutableList<FeatureJar> getFeatureJars() {
-      return ImmutableList.copyOf(featureJars);
-    }
-
-    ImmutableList<String> getBaseJars() {
-      return ImmutableList.copyOf(baseJars);
-    }
-
-    // Shorthand error messages.
-    public Diagnostic error(String msg) {
-      StringDiagnostic error = new StringDiagnostic(msg);
-      diagnosticsHandler.error(error);
-      return error;
-    }
-  }
-
-  /**
-   * Parse a feature jar argument and return the corresponding FeatureJar representation.
-   * Default to use the name of the jar file if the argument contains no ':', if the argument
-   * contains ':', then use the value after the ':' as the name.
-   * @param argument
-   */
-  private static FeatureJar parseFeatureJarArgument(String argument) {
-    if (argument.contains(":")) {
-      String[] parts = argument.split(":");
-      if (parts.length > 2) {
-        throw new RuntimeException("--feature-jar argument contains more than one :");
-      }
-      return new FeatureJar(parts[0], parts[1]);
-    }
-    return new FeatureJar(argument);
-  }
-
-  private static Options parseArguments(String[] args) {
-    Options options = new Options();
-    ParseContext context = new ParseContext(args);
-    while (context.head() != null) {
-      List<String> inputs = OptionsParsing.tryParseMulti(context, "--input");
-      if (inputs != null) {
-        inputs.forEach(options::addInputArchive);
-        continue;
-      }
-      List<String> featureJars = OptionsParsing.tryParseMulti(context, "--feature-jar");
-      if (featureJars != null) {
-        featureJars.forEach((feature) -> options.addFeatureJar(parseFeatureJarArgument(feature)));
-        continue;
-      }
-      List<String> baseJars = OptionsParsing.tryParseMulti(context, "--base-jar");
-      if (baseJars != null) {
-        baseJars.forEach(options::addBaseJar);
-        continue;
-      }
-      String output = OptionsParsing.tryParseSingle(context, "--output", "-o");
-      if (output != null) {
-        options.setOutput(output);
-        continue;
-      }
-
-      String mainDexList= OptionsParsing.tryParseSingle(context, "--main-dex-list", null);
-      if (mainDexList!= null) {
-        options.setMainDexList(mainDexList);
-        continue;
-      }
-
-      String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null);
-      if (proguardMap != null) {
-        options.setProguardMap(proguardMap);
-        continue;
-      }
-      String baseOutputName = OptionsParsing.tryParseSingle(context, "--base-output-name", null);
-      if (baseOutputName != null) {
-        options.setBaseOutputName(baseOutputName);
-        continue;
-      }
-      String featureSplit = OptionsParsing.tryParseSingle(context, "--feature-splits", null);
-      if (featureSplit != null) {
-        options.setFeatureSplitMapping(featureSplit);
-        continue;
-      }
-      Boolean b = OptionsParsing.tryParseBoolean(context, "--split-non-class-resources");
-      if (b != null) {
-        options.setSplitNonClassResources(b);
-        continue;
-      }
-      throw new RuntimeException(String.format("Unknown options: '%s'.", context.head()));
-    }
-    return options;
-  }
-
-  private static FeatureClassMapping createFeatureClassMapping(Options options)
-      throws FeatureMappingException {
-    if (options.getFeatureSplitMapping() != null) {
-      return FeatureClassMapping.fromSpecification(
-          Paths.get(options.getFeatureSplitMapping()), options.getDiagnosticsHandler());
-    }
-    assert !options.getFeatureJars().isEmpty();
-    return FeatureClassMapping.Internal.fromJarFiles(options.getFeatureJars(),
-        options.getBaseJars(), options.getBaseOutputName(), options.getDiagnosticsHandler());
-  }
-
-  private static void run(String[] args)
-      throws CompilationFailedException, FeatureMappingException {
-    Options options = parseArguments(args);
-    run(options);
-  }
-
-  public static void run(Options options)
-      throws FeatureMappingException, CompilationFailedException {
-    Diagnostic error = null;
-    if (options.getInputArchives().isEmpty()) {
-      error = options.error("Need at least one --input");
-    }
-    if (options.getFeatureSplitMapping() == null && options.getFeatureJars().isEmpty()) {
-      error = options.error("You must supply a feature split mapping or feature jars");
-    }
-    if (options.getFeatureSplitMapping() != null && !options.getFeatureJars().isEmpty()) {
-      error = options.error("You can't supply both a feature split mapping and feature jars");
-    }
-    if (error != null) {
-      throw new AbortException(error);
-    }
-
-    D8Command.Builder builder = D8Command.builder(options.diagnosticsHandler);
-
-
-    for (String s : options.inputArchives) {
-      builder.addProgramFiles(Paths.get(s));
-    }
-    // We set the actual consumer on the ApplicationWriter when we have calculated the distribution
-    // since we don't yet know the distribution.
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    if (options.getMainDexList() != null) {
-      builder.addMainDexListFiles(Paths.get(options.getMainDexList()));
-    }
-
-    FeatureClassMapping featureClassMapping = createFeatureClassMapping(options);
-
-    DexSplitterHelper.run(
-        builder.build(), featureClassMapping, options.getOutput(), options.getProguardMap());
-
-    if (options.splitNonClassResources) {
-      splitNonClassResources(options, featureClassMapping);
-    }
-  }
-
-  private static void splitNonClassResources(Options options,
-      FeatureClassMapping featureClassMapping) {
-    for (String s : options.inputArchives) {
-      try (ZipFile zipFile = new ZipFile(s, StandardCharsets.UTF_8)) {
-        Enumeration<? extends ZipEntry> entries = zipFile.entries();
-        while (entries.hasMoreElements()) {
-          ZipEntry entry = entries.nextElement();
-          String name = entry.getName();
-          if (!ZipUtils.isDexFile(name) && !ZipUtils.isClassFile(name)) {
-            String feature = featureClassMapping.featureForNonClass(name);
-            Path outputDir = Paths.get(options.getOutput()).resolve(feature);
-            try (InputStream stream = zipFile.getInputStream(entry)) {
-              Path outputFile = outputDir.resolve(name);
-              Path parent = outputFile.getParent();
-              if (parent != null) {
-                Files.createDirectories(parent);
-              }
-              Files.copy(stream, outputFile);
-            }
-          }
-        }
-      } catch (IOException e) {
-        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s)));
-        options.getDiagnosticsHandler().error(error);
-        throw new AbortException(error);
-      }
-    }
-  }
-
-  public static void main(String[] args) {
-    if (PRINT_ARGS) {
-      printArgs(args);
-    }
-    ExceptionUtils.withMainProgramHandler(
-        () -> {
-          try {
-            run(args);
-          } catch (FeatureMappingException e) {
-            // TODO(ricow): Report feature mapping errors via the reporter.
-            throw new RuntimeException("Splitting failed: " + e.getMessage());
-          }
-        });
-  }
-
-  private static void printArgs(String[] args) {
-    System.err.printf("r8.DexSplitter");
-    for (String s : args) {
-      System.err.printf(" %s", s);
-    }
-    System.err.println("");
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index 224d24a..a604608 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -40,6 +40,9 @@
   private static final String MINIFICATION_KEY = "minification";
   private static final String FORCE_PROGUARD_COMPATIBILITY_KEY = "force-proguard-compatibility";
   private static final String SYSTEM_PROPERTY_PREFIX = "system-property-";
+  private static final String ENABLE_MISSING_LIBRARY_API_MODELING =
+      "enable-missing-library-api-modeling";
+  private static final String ANDROID_PLATFORM_BUILD = "android-platform-build";
 
   private final Tool tool;
   private final CompilationMode compilationMode;
@@ -58,6 +61,8 @@
   private final FeatureSplitConfiguration featureSplitConfiguration;
   private final ProguardConfiguration proguardConfiguration;
   private final List<ProguardConfigurationRule> mainDexKeepRules;
+  private final boolean enableMissingLibraryApiModeling;
+  private final boolean isAndroidPlatformBuild;
 
   private final Map<String, String> systemProperties;
 
@@ -80,6 +85,8 @@
       FeatureSplitConfiguration featureSplitConfiguration,
       ProguardConfiguration proguardConfiguration,
       List<ProguardConfigurationRule> mainDexKeepRules,
+      boolean enableMissingLibraryApiModeling,
+      boolean isAndroidPlatformBuild,
       Map<String, String> systemProperties,
       boolean dumpInputToFile) {
     this.tool = tool;
@@ -97,6 +104,8 @@
     this.featureSplitConfiguration = featureSplitConfiguration;
     this.proguardConfiguration = proguardConfiguration;
     this.mainDexKeepRules = mainDexKeepRules;
+    this.enableMissingLibraryApiModeling = enableMissingLibraryApiModeling;
+    this.isAndroidPlatformBuild = isAndroidPlatformBuild;
     this.systemProperties = systemProperties;
     this.dumpInputToFile = dumpInputToFile;
   }
@@ -115,6 +124,10 @@
       addDumpEntry(builder, THREAD_COUNT_KEY, threadCount);
     }
     addDumpEntry(builder, DESUGAR_STATE_KEY, desugarState);
+    addDumpEntry(builder, ENABLE_MISSING_LIBRARY_API_MODELING, enableMissingLibraryApiModeling);
+    if (isAndroidPlatformBuild) {
+      addDumpEntry(builder, ANDROID_PLATFORM_BUILD, isAndroidPlatformBuild);
+    }
     addOptionalDumpEntry(builder, INTERMEDIATE_KEY, intermediate);
     addOptionalDumpEntry(builder, INCLUDE_DATA_RESOURCES_KEY, includeDataResources);
     addOptionalDumpEntry(builder, TREE_SHAKING_KEY, treeShaking);
@@ -273,6 +286,9 @@
     private ProguardConfiguration proguardConfiguration;
     private List<ProguardConfigurationRule> mainDexKeepRules;
 
+    private boolean enableMissingLibraryApiModeling = false;
+    private boolean isAndroidPlatformBuild = false;
+
     private Map<String, String> systemProperties = new HashMap<>();
 
     // Reporting only.
@@ -362,6 +378,16 @@
       return this;
     }
 
+    public Builder setEnableMissingLibraryApiModeling(boolean value) {
+      enableMissingLibraryApiModeling = value;
+      return this;
+    }
+
+    public Builder setAndroidPlatformBuild(boolean value) {
+      isAndroidPlatformBuild = value;
+      return this;
+    }
+
     public Builder setSystemProperty(String key, String value) {
       this.systemProperties.put(key, value);
       return this;
@@ -398,6 +424,8 @@
           featureSplitConfiguration,
           proguardConfiguration,
           mainDexKeepRules,
+          enableMissingLibraryApiModeling,
+          isAndroidPlatformBuild,
           systemProperties,
           dumpInputToFile);
     }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
index 7279ee7..00f0db2 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import java.util.Collection;
 import java.util.Collections;
 
@@ -16,12 +17,17 @@
   EmptyStartupOrder() {}
 
   @Override
-  public boolean contains(DexType type) {
+  public boolean contains(StartupClass<DexType> startupClass) {
     return false;
   }
 
   @Override
-  public Collection<DexType> getClasses() {
+  public boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType) {
+    return false;
+  }
+
+  @Override
+  public Collection<StartupClass<DexType>> getClasses() {
     return Collections.emptyList();
   }
 
@@ -41,7 +47,8 @@
   }
 
   @Override
-  public EmptyStartupOrder withoutPrunedItems(PrunedItems prunedItems) {
+  public EmptyStartupOrder withoutPrunedItems(
+      PrunedItems prunedItems, SyntheticItems syntheticItems) {
     return this;
   }
 }
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 0427fc3..1b8eab9 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
@@ -5,33 +5,48 @@
 package com.android.tools.r8.experimental.startup;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.LazyBox;
+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;
+import java.util.Set;
 
 public class NonEmptyStartupOrder extends StartupOrder {
 
-  private final LinkedHashSet<DexType> startupClasses;
+  private final LinkedHashSet<StartupClass<DexType>> startupClasses;
 
-  NonEmptyStartupOrder(LinkedHashSet<DexType> startupClasses) {
+  NonEmptyStartupOrder(LinkedHashSet<StartupClass<DexType>> startupClasses) {
     assert !startupClasses.isEmpty();
     this.startupClasses = startupClasses;
   }
 
   @Override
-  public boolean contains(DexType type) {
-    return startupClasses.contains(type);
+  public boolean contains(StartupClass<DexType> startupClass) {
+    return startupClasses.contains(startupClass);
   }
 
   @Override
-  public Collection<DexType> getClasses() {
+  public boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType) {
+    return contains(
+        StartupClass.<DexType>builder()
+            .setReference(synthesizingContextType)
+            .setSynthetic()
+            .build());
+  }
+
+  @Override
+  public Collection<StartupClass<DexType>> getClasses() {
     return startupClasses;
   }
 
@@ -42,17 +57,22 @@
 
   @Override
   public StartupOrder rewrittenWithLens(GraphLens graphLens) {
-    LinkedHashSet<DexType> rewrittenStartupClasses = new LinkedHashSet<>(startupClasses.size());
-    for (DexType startupClass : startupClasses) {
-      DexType rewrittenStartupClass = graphLens.lookupType(startupClass);
-      rewrittenStartupClasses.add(rewrittenStartupClass);
+    LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses =
+        new LinkedHashSet<>(startupClasses.size());
+    for (StartupClass<DexType> startupClass : startupClasses) {
+      rewrittenStartupClasses.add(
+          StartupClass.<DexType>builder()
+              .setFlags(startupClass.getFlags())
+              .setReference(graphLens.lookupType(startupClass.getReference()))
+              .build());
     }
     return createNonEmpty(rewrittenStartupClasses);
   }
 
   @Override
   public StartupOrder toStartupOrderForWriting(AppView<?> appView) {
-    LinkedHashSet<DexType> rewrittenStartupClasses = new LinkedHashSet<>(startupClasses.size());
+    LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses =
+        new LinkedHashSet<>(startupClasses.size());
     Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses =
         new IdentityHashMap<>();
     for (DexProgramClass clazz : appView.appInfo().classes()) {
@@ -65,81 +85,98 @@
         }
       }
     }
-    for (DexType startupClass : startupClasses) {
-      addClassAndParentClasses(
+    for (StartupClass<DexType> startupClass : startupClasses) {
+      addStartupClass(
           startupClass, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
     }
+    assert rewrittenStartupClasses.stream().noneMatch(StartupClass::isSynthetic);
     return createNonEmpty(rewrittenStartupClasses);
   }
 
+  private static void addStartupClass(
+      StartupClass<DexType> startupClass,
+      LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
+      Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
+      AppView<?> appView) {
+    if (startupClass.isSynthetic()) {
+      List<DexProgramClass> syntheticClassesForContext =
+          syntheticContextsToSyntheticClasses.getOrDefault(
+              startupClass.getReference(), Collections.emptyList());
+      for (DexProgramClass clazz : syntheticClassesForContext) {
+        addClassAndParentClasses(clazz, rewrittenStartupClasses, appView);
+      }
+    } else {
+      addClassAndParentClasses(startupClass.getReference(), rewrittenStartupClasses, appView);
+    }
+  }
+
   private static boolean addClass(
-      DexProgramClass clazz, LinkedHashSet<DexType> rewrittenStartupClasses) {
-    return rewrittenStartupClasses.add(clazz.getType());
+      DexProgramClass clazz, LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses) {
+    return rewrittenStartupClasses.add(
+        StartupClass.<DexType>builder().setReference(clazz.getType()).build());
   }
 
   private static void addClassAndParentClasses(
       DexType type,
-      LinkedHashSet<DexType> rewrittenStartupClasses,
-      Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
+      LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
       AppView<?> appView) {
     DexProgramClass definition = appView.app().programDefinitionFor(type);
     if (definition != null) {
-      addClassAndParentClasses(
-          definition, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
+      addClassAndParentClasses(definition, rewrittenStartupClasses, appView);
     }
   }
 
   private static void addClassAndParentClasses(
       DexProgramClass clazz,
-      LinkedHashSet<DexType> rewrittenStartupClasses,
-      Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
+      LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
       AppView<?> appView) {
     if (addClass(clazz, rewrittenStartupClasses)) {
-      addSyntheticClassesAndParentClasses(
-          clazz, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
-      addParentClasses(
-          clazz, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
-    }
-  }
-
-  private static void addSyntheticClassesAndParentClasses(
-      DexProgramClass clazz,
-      LinkedHashSet<DexType> rewrittenStartupClasses,
-      Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
-      AppView<?> appView) {
-    List<DexProgramClass> derivedClasses =
-        syntheticContextsToSyntheticClasses.remove(clazz.getType());
-    if (derivedClasses != null) {
-      for (DexProgramClass derivedClass : derivedClasses) {
-        addClassAndParentClasses(
-            derivedClass, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView);
-      }
+      addParentClasses(clazz, rewrittenStartupClasses, appView);
     }
   }
 
   private static void addParentClasses(
       DexProgramClass clazz,
-      LinkedHashSet<DexType> rewrittenStartupClasses,
-      Map<DexType, List<DexProgramClass>> syntheticContextsToSyntheticClasses,
+      LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses,
       AppView<?> appView) {
     clazz.forEachImmediateSupertype(
-        supertype ->
-            addClassAndParentClasses(
-                supertype, rewrittenStartupClasses, syntheticContextsToSyntheticClasses, appView));
+        supertype -> addClassAndParentClasses(supertype, rewrittenStartupClasses, appView));
   }
 
   @Override
-  public StartupOrder withoutPrunedItems(PrunedItems prunedItems) {
-    LinkedHashSet<DexType> rewrittenStartupClasses = new LinkedHashSet<>(startupClasses.size());
-    for (DexType startupClass : startupClasses) {
-      if (!prunedItems.isRemoved(startupClass)) {
+  public StartupOrder withoutPrunedItems(PrunedItems prunedItems, SyntheticItems syntheticItems) {
+    LinkedHashSet<StartupClass<DexType>> rewrittenStartupClasses =
+        new LinkedHashSet<>(startupClasses.size());
+    LazyBox<Set<DexType>> contextsOfLiveSynthetics =
+        new LazyBox<>(
+            () -> computeContextsOfLiveSynthetics(prunedItems.getPrunedApp(), syntheticItems));
+    for (StartupClass<DexType> startupClass : startupClasses) {
+      // Only prune non-synthetic classes, since the pruning of a class does not imply that all
+      // classes synthesized from it have been pruned.
+      if (startupClass.isSynthetic()) {
+        if (contextsOfLiveSynthetics.computeIfAbsent().contains(startupClass.getReference())) {
+          rewrittenStartupClasses.add(startupClass);
+        }
+      } else if (!prunedItems.isRemoved(startupClass.getReference())) {
         rewrittenStartupClasses.add(startupClass);
       }
     }
     return createNonEmpty(rewrittenStartupClasses);
   }
 
-  private StartupOrder createNonEmpty(LinkedHashSet<DexType> startupClasses) {
+  private Set<DexType> computeContextsOfLiveSynthetics(
+      DexApplication app, SyntheticItems syntheticItems) {
+    Set<DexType> contextsOfLiveSynthetics = Sets.newIdentityHashSet();
+    for (DexProgramClass clazz : app.classes()) {
+      if (syntheticItems.isSyntheticClass(clazz)) {
+        contextsOfLiveSynthetics.addAll(
+            syntheticItems.getSynthesizingContextTypes(clazz.getType()));
+      }
+    }
+    return contextsOfLiveSynthetics;
+  }
+
+  private StartupOrder createNonEmpty(LinkedHashSet<StartupClass<DexType>> startupClasses) {
     if (startupClasses.isEmpty()) {
       assert false;
       return empty();
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
new file mode 100644
index 0000000..9338d5f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupClass.java
@@ -0,0 +1,87 @@
+// 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;
+
+public class StartupClass<T> {
+
+  private static final int FLAG_SYNTHETIC = 1;
+
+  private final int flags;
+  private final T reference;
+
+  public StartupClass(int flags, T reference) {
+    this.flags = flags;
+    this.reference = reference;
+  }
+
+  public static <T> Builder<T> builder() {
+    return new Builder<>();
+  }
+
+  public int getFlags() {
+    return flags;
+  }
+
+  public T getReference() {
+    return reference;
+  }
+
+  public boolean isSynthetic() {
+    return (flags & FLAG_SYNTHETIC) != 0;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
+    }
+    StartupClass<?> startupClass = (StartupClass<?>) obj;
+    return flags == startupClass.flags && reference.equals(startupClass.reference);
+  }
+
+  @Override
+  public int hashCode() {
+    assert flags <= 1;
+    return (reference.hashCode() << 1) | flags;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    if (isSynthetic()) {
+      builder.append('S');
+    }
+    builder.append(reference);
+    return builder.toString();
+  }
+
+  public static class Builder<T> {
+
+    private int flags;
+    private T reference;
+
+    public Builder<T> setFlags(int flags) {
+      this.flags = flags;
+      return this;
+    }
+
+    public Builder<T> setReference(T reference) {
+      this.reference = reference;
+      return this;
+    }
+
+    public Builder<T> setSynthetic() {
+      this.flags |= FLAG_SYNTHETIC;
+      return this;
+    }
+
+    public StartupClass<T> build() {
+      return new StartupClass<>(flags, reference);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
index 7e78009..9f16dce 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupConfiguration.java
@@ -13,22 +13,29 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class StartupConfiguration {
 
-  private final List<DexType> startupClasses;
+  private final List<StartupClass<DexType>> startupClasses;
   private final List<DexMethod> startupMethods;
 
-  public StartupConfiguration(List<DexType> startupClasses, List<DexMethod> startupMethods) {
+  public StartupConfiguration(
+      List<StartupClass<DexType>> startupClasses, List<DexMethod> startupMethods) {
     this.startupClasses = startupClasses;
     this.startupMethods = startupMethods;
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   /**
    * Parses the supplied startup configuration, if any. The startup configuration is a list of class
    * and method descriptors.
@@ -67,18 +74,26 @@
       return null;
     }
 
-    List<DexType> startupClasses = new ArrayList<>();
+    return createStartupConfigurationFromLines(dexItemFactory, reporter, startupDescriptors);
+  }
+
+  public static StartupConfiguration createStartupConfigurationFromLines(
+      DexItemFactory dexItemFactory, Reporter reporter, List<String> startupDescriptors) {
+    List<StartupClass<DexType>> startupClasses = new ArrayList<>();
     List<DexMethod> startupMethods = new ArrayList<>();
     for (String startupDescriptor : startupDescriptors) {
       if (startupDescriptor.isEmpty()) {
         continue;
       }
+      StartupClass.Builder<DexType> startupClassBuilder = StartupClass.builder();
+      startupDescriptor = parseSyntheticFlag(startupDescriptor, startupClassBuilder);
       int methodNameStartIndex = getMethodNameStartIndex(startupDescriptor);
       if (methodNameStartIndex >= 0) {
         DexMethod startupMethod =
             parseStartupMethodDescriptor(startupDescriptor, methodNameStartIndex, dexItemFactory);
         if (startupMethod != null) {
-          startupClasses.add(startupMethod.getHolderType());
+          startupClasses.add(
+              startupClassBuilder.setReference(startupMethod.getHolderType()).build());
           startupMethods.add(startupMethod);
         } else {
           reporter.warning(
@@ -87,7 +102,7 @@
       } else {
         DexType startupClass = parseStartupClassDescriptor(startupDescriptor, dexItemFactory);
         if (startupClass != null) {
-          startupClasses.add(startupClass);
+          startupClasses.add(startupClassBuilder.setReference(startupClass).build());
         } else {
           reporter.warning(
               new StringDiagnostic("Invalid descriptor for startup class: " + startupDescriptor));
@@ -97,6 +112,15 @@
     return new StartupConfiguration(startupClasses, startupMethods);
   }
 
+  public static String parseSyntheticFlag(
+      String startupDescriptor, StartupClass.Builder<?> startupClassBuilder) {
+    if (!startupDescriptor.isEmpty() && startupDescriptor.charAt(0) == 'S') {
+      startupClassBuilder.setSynthetic();
+      return startupDescriptor.substring(1);
+    }
+    return startupDescriptor;
+  }
+
   private static int getMethodNameStartIndex(String startupDescriptor) {
     int arrowIndex = startupDescriptor.indexOf("->");
     return arrowIndex >= 0 ? arrowIndex + 2 : arrowIndex;
@@ -147,7 +171,28 @@
     return !startupClasses.isEmpty();
   }
 
-  public List<DexType> getStartupClasses() {
+  public List<StartupClass<DexType>> getStartupClasses() {
     return startupClasses;
   }
+
+  public static class Builder {
+
+    private final ImmutableList.Builder<StartupClass<DexType>> startupClassesBuilder =
+        ImmutableList.builder();
+    private final ImmutableList.Builder<DexMethod> startupMethodsBuilder = ImmutableList.builder();
+
+    public Builder addStartupClass(StartupClass<DexType> startupClass) {
+      this.startupClassesBuilder.add(startupClass);
+      return this;
+    }
+
+    public Builder apply(Consumer<Builder> consumer) {
+      consumer.accept(this);
+      return this;
+    }
+
+    public StartupConfiguration build() {
+      return new StartupConfiguration(startupClassesBuilder.build(), startupMethodsBuilder.build());
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
index 8995dbc..aa97fa6 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupInstrumentation.java
@@ -19,11 +19,15 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+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.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -41,12 +45,18 @@
     this.options = appView.options().getStartupOptions();
   }
 
-  public void instrumentClasses(ExecutorService executorService) throws ExecutionException {
+  public void instrumentAllClasses(ExecutorService executorService) throws ExecutionException {
+    instrumentClasses(appView.appInfo().classes(), executorService);
+  }
+
+  public boolean instrumentClasses(
+      Collection<DexProgramClass> classes, ExecutorService executorService)
+      throws ExecutionException {
     if (!appView.options().getStartupOptions().isStartupInstrumentationEnabled()) {
-      return;
+      return false;
     }
-    ThreadUtils.processItems(
-        appView.appInfo().classes(), this::internalInstrumentClass, executorService);
+    ThreadUtils.processItems(classes, this::internalInstrumentClass, executorService);
+    return true;
   }
 
   public void instrumentClass(DexProgramClass clazz) {
@@ -90,20 +100,31 @@
       return;
     }
 
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    DexString message;
+    if (syntheticItems.isSyntheticClass(classInitializer.getHolder())) {
+      Collection<DexType> synthesizingContexts =
+          syntheticItems.getSynthesizingContextTypes(classInitializer.getHolderType());
+      assert synthesizingContexts.size() == 1;
+      message = synthesizingContexts.iterator().next().getDescriptor().prepend("S", dexItemFactory);
+    } else {
+      message = classInitializer.getHolderType().getDescriptor();
+    }
+
     CfCode cfCode = code.asCfCode();
     List<CfInstruction> instructions;
     if (options.hasStartupInstrumentationTag()) {
       instructions = new ArrayList<>(4 + cfCode.getInstructions().size());
       instructions.add(
           new CfConstString(dexItemFactory.createString(options.getStartupInstrumentationTag())));
-      instructions.add(new CfConstString(classInitializer.getHolderType().getDescriptor()));
+      instructions.add(new CfConstString(message));
       instructions.add(
           new CfInvoke(Opcodes.INVOKESTATIC, dexItemFactory.androidUtilLogMembers.i, false));
       instructions.add(new CfStackInstruction(Opcode.Pop));
     } else {
       instructions = new ArrayList<>(3 + cfCode.getInstructions().size());
       instructions.add(new CfStaticFieldRead(dexItemFactory.javaLangSystemMembers.out));
-      instructions.add(new CfConstString(classInitializer.getHolderType().getDescriptor()));
+      instructions.add(new CfConstString(message));
       instructions.add(
           new CfInvoke(
               Opcodes.INVOKEVIRTUAL,
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 c633722..15fbf6c 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
@@ -30,12 +30,17 @@
     return startupInstrumentationTag;
   }
 
+  public StartupOptions setStartupInstrumentationTag(String startupInstrumentationTag) {
+    this.startupInstrumentationTag = startupInstrumentationTag;
+    return this;
+  }
+
   public boolean isMinimalStartupDexEnabled() {
     return enableMinimalStartupDex;
   }
 
-  public StartupOptions setEnableMinimalStartupDex() {
-    enableMinimalStartupDex = true;
+  public StartupOptions setEnableMinimalStartupDex(boolean enableMinimalStartupDex) {
+    this.enableMinimalStartupDex = enableMinimalStartupDex;
     return this;
   }
 
@@ -65,7 +70,8 @@
     return startupConfiguration;
   }
 
-  public void setStartupConfiguration(StartupConfiguration startupConfiguration) {
+  public StartupOptions setStartupConfiguration(StartupConfiguration startupConfiguration) {
     this.startupConfiguration = startupConfiguration;
+    return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
index da68a15..78126fd 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collection;
 import java.util.LinkedHashSet;
@@ -32,9 +33,11 @@
     return new EmptyStartupOrder();
   }
 
-  public abstract boolean contains(DexType type);
+  public abstract boolean contains(StartupClass<DexType> startupClass);
 
-  public abstract Collection<DexType> getClasses();
+  public abstract boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType);
+
+  public abstract Collection<StartupClass<DexType>> getClasses();
 
   public abstract boolean isEmpty();
 
@@ -42,5 +45,6 @@
 
   public abstract StartupOrder toStartupOrderForWriting(AppView<?> appView);
 
-  public abstract StartupOrder withoutPrunedItems(PrunedItems prunedItems);
+  public abstract StartupOrder withoutPrunedItems(
+      PrunedItems prunedItems, SyntheticItems syntheticItems);
 }
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 82b0243..b721f6f 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -8,7 +8,10 @@
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
+import com.android.tools.r8.experimental.startup.StartupOrder;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -93,20 +96,25 @@
 
     FeatureSplit baseStartup;
     if (startupConfiguration != null && startupConfiguration.hasStartupClasses()) {
-      DexType representativeType = null;
-      for (DexType startupClass : startupConfiguration.getStartupClasses()) {
-        if (classToFeatureSplitMap.containsKey(startupClass)) {
+      StartupClass<DexType> representativeStartupClass = null;
+      for (StartupClass<DexType> startupClass : startupConfiguration.getStartupClasses()) {
+        if (startupClass.isSynthetic()
+            || classToFeatureSplitMap.containsKey(startupClass.getReference())) {
           continue;
         }
-        classToFeatureSplitMap.put(startupClass, FeatureSplit.BASE_STARTUP);
-        if (representativeType == null
-            || startupClass.getDescriptor().compareTo(representativeType.getDescriptor()) > 0) {
-          representativeType = startupClass;
+        classToFeatureSplitMap.put(startupClass.getReference(), FeatureSplit.BASE_STARTUP);
+        if (representativeStartupClass == null
+            || startupClass
+                    .getReference()
+                    .getDescriptor()
+                    .compareTo(representativeStartupClass.getReference().getDescriptor())
+                > 0) {
+          representativeStartupClass = startupClass;
         }
       }
       baseStartup = FeatureSplit.BASE_STARTUP;
       representativeStringsForFeatureSplit.put(
-          baseStartup, representativeType.toDescriptorString());
+          baseStartup, representativeStartupClass.getReference().toDescriptorString());
     } else {
       baseStartup = FeatureSplit.BASE;
     }
@@ -141,10 +149,16 @@
   }
 
   public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
-      Set<DexProgramClass> classes, SyntheticItems syntheticItems) {
+      Set<DexProgramClass> classes, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return getFeatureSplitClasses(
+        classes, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+  }
+
+  public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
+      Set<DexProgramClass> classes, StartupOrder startupOrder, SyntheticItems syntheticItems) {
     Map<FeatureSplit, Set<DexProgramClass>> result = new IdentityHashMap<>();
     for (DexProgramClass clazz : classes) {
-      FeatureSplit featureSplit = getFeatureSplit(clazz, syntheticItems);
+      FeatureSplit featureSplit = getFeatureSplit(clazz, startupOrder, syntheticItems);
       if (featureSplit != null && !featureSplit.isBase()) {
         result.computeIfAbsent(featureSplit, ignore -> Sets.newIdentityHashSet()).add(clazz);
       }
@@ -152,18 +166,39 @@
     return result;
   }
 
-  public FeatureSplit getFeatureSplit(ProgramDefinition clazz, SyntheticItems syntheticItems) {
-    return getFeatureSplit(clazz.getContextType(), syntheticItems);
+  public FeatureSplit getFeatureSplit(
+      ProgramDefinition definition, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return getFeatureSplit(
+        definition, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
   }
 
-  public FeatureSplit getFeatureSplit(DexType type, SyntheticItems syntheticItems) {
+  public FeatureSplit getFeatureSplit(
+      ProgramDefinition definition, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+    return getFeatureSplit(definition.getContextType(), startupOrder, syntheticItems);
+  }
+
+  public FeatureSplit getFeatureSplit(
+      DexType type, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return getFeatureSplit(type, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+  }
+
+  public FeatureSplit getFeatureSplit(
+      DexType type, StartupOrder startupOrder, SyntheticItems syntheticItems) {
     FeatureSplit feature = classToFeatureSplitMap.get(type);
     if (feature != null) {
+      assert !syntheticItems.isSyntheticClass(type);
       return feature;
     }
-    feature = syntheticItems.getContextualFeatureSplit(type, this);
-    if (feature != null) {
-      return feature;
+    if (syntheticItems != null) {
+      feature = syntheticItems.getContextualFeatureSplit(type, this);
+      if (feature != null && !feature.isBase()) {
+        return feature;
+      }
+      for (DexType context : syntheticItems.getSynthesizingContextTypes(type)) {
+        if (startupOrder.containsSyntheticClassesSynthesizedFrom(context)) {
+          return FeatureSplit.BASE_STARTUP;
+        }
+      }
     }
     return FeatureSplit.BASE;
   }
@@ -175,33 +210,79 @@
     return classToFeatureSplitMap.isEmpty();
   }
 
-  public boolean isInBase(DexProgramClass clazz, SyntheticItems syntheticItems) {
-    return getFeatureSplit(clazz, syntheticItems).isBase();
+  public boolean isInBase(
+      DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isInBase(clazz, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+  }
+
+  public boolean isInBase(
+      DexProgramClass clazz, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+    return getFeatureSplit(clazz, startupOrder, syntheticItems).isBase();
   }
 
   public boolean isInBaseOrSameFeatureAs(
-      DexProgramClass clazz, ProgramDefinition context, SyntheticItems syntheticItems) {
-    return isInBaseOrSameFeatureAs(clazz.getContextType(), context, syntheticItems);
+      DexProgramClass clazz,
+      ProgramDefinition context,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isInBaseOrSameFeatureAs(
+        clazz, context, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
   }
 
   public boolean isInBaseOrSameFeatureAs(
-      DexType clazz, ProgramDefinition context, SyntheticItems syntheticItems) {
-    FeatureSplit split = getFeatureSplit(clazz, syntheticItems);
-    return split.isBase() || split == getFeatureSplit(context, syntheticItems);
+      DexProgramClass clazz,
+      ProgramDefinition context,
+      StartupOrder startupOrder,
+      SyntheticItems syntheticItems) {
+    return isInBaseOrSameFeatureAs(clazz.getContextType(), context, startupOrder, syntheticItems);
   }
 
-  public boolean isInFeature(DexProgramClass clazz, SyntheticItems syntheticItems) {
-    return !isInBase(clazz, syntheticItems);
+  public boolean isInBaseOrSameFeatureAs(
+      DexType clazz,
+      ProgramDefinition context,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isInBaseOrSameFeatureAs(
+        clazz, context, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+  }
+
+  public boolean isInBaseOrSameFeatureAs(
+      DexType clazz,
+      ProgramDefinition context,
+      StartupOrder startupOrder,
+      SyntheticItems syntheticItems) {
+    FeatureSplit split = getFeatureSplit(clazz, startupOrder, syntheticItems);
+    return split.isBase() || split == getFeatureSplit(context, startupOrder, syntheticItems);
+  }
+
+  public boolean isInFeature(
+      DexProgramClass clazz, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+    return !isInBase(clazz, startupOrder, syntheticItems);
   }
 
   public boolean isInSameFeatureOrBothInSameBase(
-      ProgramMethod a, ProgramMethod b, SyntheticItems syntheticItems) {
-    return isInSameFeatureOrBothInSameBase(a.getHolder(), b.getHolder(), syntheticItems);
+      ProgramMethod a, ProgramMethod b, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isInSameFeatureOrBothInSameBase(
+        a, b, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
   }
 
   public boolean isInSameFeatureOrBothInSameBase(
-      DexProgramClass a, DexProgramClass b, SyntheticItems syntheticItems) {
-    return getFeatureSplit(a, syntheticItems) == getFeatureSplit(b, syntheticItems);
+      ProgramMethod a, ProgramMethod b, StartupOrder startupOrder, SyntheticItems syntheticItems) {
+    return isInSameFeatureOrBothInSameBase(
+        a.getHolder(), b.getHolder(), startupOrder, syntheticItems);
+  }
+
+  public boolean isInSameFeatureOrBothInSameBase(
+      DexProgramClass a, DexProgramClass b, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isInSameFeatureOrBothInSameBase(
+        a, b, appView.appInfo().getStartupOrder(), appView.getSyntheticItems());
+  }
+
+  public boolean isInSameFeatureOrBothInSameBase(
+      DexProgramClass a,
+      DexProgramClass b,
+      StartupOrder startupOrder,
+      SyntheticItems syntheticItems) {
+    return getFeatureSplit(a, startupOrder, syntheticItems)
+        == getFeatureSplit(b, startupOrder, syntheticItems);
   }
 
   public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens) {
@@ -240,6 +321,7 @@
   }
 
   public static boolean isInFeature(DexProgramClass clazz, AppView<AppInfoWithLiveness> appView) {
-    return getMap(appView).isInFeature(clazz, appView.getSyntheticItems());
+    return getMap(appView)
+        .isInFeature(clazz, 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 cf2c789..428d0fd 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+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.OptionalBool;
@@ -20,13 +21,18 @@
       ProgramDefinition context,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
     return isClassAccessible(
-        clazz, context, appView.appInfo().getClassToFeatureSplitMap(), appView.getSyntheticItems());
+        clazz,
+        context,
+        appView.appInfo().getClassToFeatureSplitMap(),
+        appView.appInfo().getStartupOrder(),
+        appView.getSyntheticItems());
   }
 
   public static OptionalBool isClassAccessible(
       DexClass clazz,
       Definition context,
       ClassToFeatureSplitMap classToFeatureSplitMap,
+      StartupOrder startupOrder,
       SyntheticItems syntheticItems) {
     if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
       return OptionalBool.FALSE;
@@ -34,7 +40,7 @@
     if (clazz.isProgramClass()
         && context.isProgramDefinition()
         && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
-            clazz.asProgramClass(), context.asProgramDefinition(), syntheticItems)) {
+            clazz.asProgramClass(), context.asProgramDefinition(), startupOrder, syntheticItems)) {
       return OptionalBool.UNKNOWN;
     }
     return OptionalBool.TRUE;
@@ -72,6 +78,7 @@
             initialResolutionHolder,
             context,
             appInfo.getClassToFeatureSplitMap(),
+            appInfo.getStartupOrder(),
             appInfo.getSyntheticItems());
     if (classAccessibility.isFalse()) {
       return OptionalBool.FALSE;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 836f1a5..1a029f8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -140,7 +140,7 @@
         getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
         getMainDexInfo().withoutPrunedItems(prunedItems),
         getMissingClasses(),
-        getStartupOrder().withoutPrunedItems(prunedItems));
+        getStartupOrder().withoutPrunedItems(prunedItems, getSyntheticItems()));
   }
 
   public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index b5b0c60..6776d2c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -110,8 +110,7 @@
     assert featureImplementations.size() <= 2;
     // Check if service is defined feature
     DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
-    if (serviceClass != null
-        && classToFeatureSplitMap.isInFeature(serviceClass, appView.getSyntheticItems())) {
+    if (serviceClass != null && classToFeatureSplitMap.isInFeature(serviceClass, appView)) {
       return true;
     }
     for (Entry<FeatureSplit, List<DexType>> entry : featureImplementations.entrySet()) {
@@ -121,8 +120,7 @@
       for (DexType implementationType : implementationTypes) {
         DexProgramClass implementationClass = appView.definitionForProgramType(implementationType);
         if (implementationClass != null
-            && classToFeatureSplitMap.isInFeature(
-                implementationClass, appView.getSyntheticItems())) {
+            && classToFeatureSplitMap.isInFeature(implementationClass, appView)) {
           return true;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index f6d0d19..2a9db3a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -492,6 +492,20 @@
     return true;
   }
 
+  public DexString prepend(String prefix, DexItemFactory dexItemFactory) {
+    return prepend(dexItemFactory.createString(prefix), dexItemFactory);
+  }
+
+  public DexString prepend(DexString prefix, DexItemFactory dexItemFactory) {
+    int newSize = prefix.size + this.size;
+    // Each string ends with a 0 terminating byte, hence the +/- 1.
+    byte[] newContent = new byte[prefix.content.length + this.content.length - 1];
+    System.arraycopy(prefix.content, 0, newContent, 0, prefix.content.length - 1);
+    System.arraycopy(
+        this.content, 0, newContent, prefix.content.length - 1, this.content.length - 1);
+    return dexItemFactory.createString(newSize, newContent);
+  }
+
   public DexString withNewPrefix(
       DexString prefix, DexString rewrittenPrefix, DexItemFactory factory) {
     // Copy bytes over to avoid decoding/encoding cost.
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 c56866e..8730c35 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -1253,25 +1253,26 @@
       }
       BooleanBox seenNoAccess = new BooleanBox(false);
       forEachFailureDependency(
-          type -> {
-            appInfo
-                .contextIndependentDefinitionForWithResolutionResult(type)
-                .forEachClassResolutionResult(
-                    clazz -> {
-                      AccessControl.isClassAccessible(
-                          clazz,
-                          context,
-                          appInfo.getClassToFeatureSplitMap(),
-                          appInfo.getSyntheticItems());
-                    });
-          },
+          type ->
+              appInfo
+                  .contextIndependentDefinitionForWithResolutionResult(type)
+                  .forEachClassResolutionResult(
+                      clazz ->
+                          seenNoAccess.or(
+                              AccessControl.isClassAccessible(
+                                      clazz,
+                                      context,
+                                      appInfo.getClassToFeatureSplitMap(),
+                                      appInfo.getStartupOrder(),
+                                      appInfo.getSyntheticItems())
+                                  .isPossiblyFalse())),
           method -> {
             DexClass holder = appInfo.definitionFor(method.getHolderType());
             DexClassAndMethod classAndMethod = DexClassAndMethod.create(holder, method);
             seenNoAccess.or(
                 AccessControl.isMemberAccessible(
                         classAndMethod, initialResolutionHolder, context, appInfo)
-                    .isFalse());
+                    .isPossiblyFalse());
           });
       return seenNoAccess.get();
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
index 58054a2..73dfa2a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
@@ -19,10 +19,7 @@
 
   @Override
   public FeatureSplit getMergeKey(DexProgramClass clazz) {
-    return appView
-        .appInfo()
-        .getClassToFeatureSplitMap()
-        .getFeatureSplit(clazz, appView.getSyntheticItems());
+    return appView.appInfo().getClassToFeatureSplitMap().getFeatureSplit(clazz, appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index fe41e44..80718a7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticItems;
 
 public class SingleConstClassValue extends SingleConstValue {
 
@@ -110,9 +109,8 @@
         return false;
       }
       ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-      SyntheticItems syntheticItems = appView.getSyntheticItems();
       if (clazz.isProgramClass()
-          && classToFeatureSplitMap.isInFeature(clazz.asProgramClass(), syntheticItems)) {
+          && classToFeatureSplitMap.isInFeature(clazz.asProgramClass(), appView)) {
         return false;
       }
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index d7442d5..da02bae 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -30,7 +30,6 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ObjectAllocationInfoCollectionUtils;
-import com.android.tools.r8.synthesis.SyntheticItems;
 
 public abstract class SingleFieldValue extends SingleValue {
 
@@ -129,9 +128,8 @@
       return false;
     }
     ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    SyntheticItems syntheticItems = appView.getSyntheticItems();
     if (holder.isProgramClass()
-        && classToFeatureSplitMap.isInFeature(holder.asProgramClass(), syntheticItems)) {
+        && classToFeatureSplitMap.isInFeature(holder.asProgramClass(), appView)) {
       return false;
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index d0eab48..e195eac 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -158,7 +158,8 @@
       // Finalize the desugaring of the processed classes. This may require processing (and
       // reprocessing) of some methods.
       List<ProgramMethod> needsProcessing =
-          instructionDesugaringEventConsumer.finalizeDesugaring(appView, resultBuilder);
+          instructionDesugaringEventConsumer.finalizeDesugaring(
+              appView, executorService, resultBuilder);
       if (!needsProcessing.isEmpty()) {
         // Create a new processor context to ensure unique method processing contexts.
         methodProcessor.newWave();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index fcf37fb..15c214a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.experimental.startup.StartupInstrumentation;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClasspathClass;
@@ -40,6 +41,8 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
@@ -87,6 +90,7 @@
 
     private final D8MethodProcessor methodProcessor;
 
+    private final Set<DexProgramClass> synthesizedClasses = Sets.newConcurrentHashSet();
     private final Map<DexReference, InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges =
         new LinkedHashMap<>();
     private final List<LambdaClass> synthesizedLambdaClasses = new ArrayList<>();
@@ -96,9 +100,18 @@
       this.methodProcessor = methodProcessor;
     }
 
+    private void acceptClass(DexProgramClass clazz) {
+      synthesizedClasses.add(clazz);
+    }
+
+    private void acceptMethod(ProgramMethod method) {
+      acceptClass(method.getHolder());
+    }
+
     @Override
     public void acceptCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
       // Intentionally empty. Methods are moved when processing the interface definition.
+      acceptMethod(method);
     }
 
     @Override
@@ -113,21 +126,25 @@
 
     @Override
     public void acceptCollectionConversion(ProgramMethod arrayConversion) {
+      acceptMethod(arrayConversion);
       methodProcessor.scheduleMethodForProcessing(arrayConversion, this);
     }
 
     @Override
     public void acceptCovariantRetargetMethod(ProgramMethod method) {
+      acceptMethod(method);
       methodProcessor.scheduleMethodForProcessing(method, this);
     }
 
     @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
+      acceptMethod(backportedMethod);
       methodProcessor.scheduleMethodForProcessing(backportedMethod, this);
     }
 
     @Override
     public void acceptRecordMethod(ProgramMethod method) {
+      acceptMethod(method);
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
@@ -141,11 +158,13 @@
 
     @Override
     public void acceptRecordClass(DexProgramClass recordClass) {
+      acceptClass(recordClass);
       methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods());
     }
 
     @Override
     public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
+      acceptClass(lambdaClass.getLambdaProgramClass());
       synchronized (synthesizedLambdaClasses) {
         synthesizedLambdaClasses.add(lambdaClass);
       }
@@ -154,6 +173,7 @@
     @Override
     public void acceptConstantDynamicClass(
         ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+      acceptClass(constantDynamicClass.getConstantDynamicProgramClass());
       synchronized (synthesizedConstantDynamicClasses) {
         synthesizedConstantDynamicClasses.add(constantDynamicClass);
       }
@@ -176,17 +196,20 @@
 
     @Override
     public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
+      acceptMethod(closeMethod);
       methodProcessor.scheduleMethodForProcessing(closeMethod, this);
     }
 
     @Override
     public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
+      acceptMethod(method);
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
     @Override
     public void acceptInvokeStaticInterfaceOutliningMethod(
         ProgramMethod method, ProgramMethod context) {
+      acceptMethod(method);
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
@@ -202,20 +225,31 @@
 
     @Override
     public void acceptAPIConversion(ProgramMethod method) {
+      acceptMethod(method);
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
     @Override
     public void acceptCompanionClassClinit(ProgramMethod method) {
+      acceptMethod(method);
       methodProcessor.scheduleDesugaredMethodForProcessing(method);
     }
 
     public List<ProgramMethod> finalizeDesugaring(
-        AppView<?> appView, ClassConverterResult.Builder classConverterResultBuilder) {
+        AppView<?> appView,
+        ExecutorService executorService,
+        ClassConverterResult.Builder classConverterResultBuilder)
+        throws ExecutionException {
       List<ProgramMethod> needsProcessing = new ArrayList<>();
       finalizeInvokeSpecialDesugaring(appView, needsProcessing::add);
       finalizeLambdaDesugaring(classConverterResultBuilder, needsProcessing::add);
       finalizeConstantDynamicDesugaring(needsProcessing::add);
+      if (new StartupInstrumentation(appView)
+          .instrumentClasses(synthesizedClasses, executorService)) {
+        for (DexProgramClass synthesizedClass : synthesizedClasses) {
+          needsProcessing.add(synthesizedClass.getProgramClassInitializer());
+        }
+      }
       return needsProcessing;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 8e8541b..955183a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -593,6 +593,19 @@
     // 1.7 or below, this will make a VerificationError on the input a VerificationError
     // on the output. If the input was 1.8 or above the runtime behaviour (potential ICCE)
     // will remain the same.
+    upgradeCfVersionToSupportInterfaceMethodInvoke(method);
+  }
+
+  private void leavingSuperInvokeToInterface(ProgramMethod method) {
+    // When leaving interface method invokes possibly upgrade the class file
+    // version, but don't go above the initial class file version. If the input was
+    // 1.7 or below, this will make a VerificationError on the input a VerificationError
+    // on the output. If the input was 1.8 or above the runtime behaviour (potential ICCE)
+    // will remain the same.
+    upgradeCfVersionToSupportInterfaceMethodInvoke(method);
+  }
+
+  private void upgradeCfVersionToSupportInterfaceMethodInvoke(ProgramMethod method) {
     if (method.getHolder().hasClassFileVersion()) {
       method
           .getDefinition()
@@ -649,7 +662,7 @@
       return computeInvokeAsThrowRewrite(invoke, resolutionResult, context);
     }
 
-    if (clazz.isInterface() && !clazz.isLibraryClass()) {
+    if (clazz.isInterface() && !resolutionResult.getResolutionPair().getHolder().isLibraryClass()) {
       // NOTE: we intentionally don't desugar super calls into interface methods
       // coming from android.jar since it is only possible in case v24+ version
       // of android.jar is provided.
@@ -714,20 +727,27 @@
 
     DesugarDescription emulatedInterfaceDesugaring =
         computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
-    if (!emulatedInterfaceDesugaring.needsDesugaring() && context.isDefaultMethod()) {
-      return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowNSMERewrite(
-          appView,
-          invoke,
-          () ->
-              appView
-                  .reporter()
-                  .warning(
-                      new StringDiagnostic(
-                          "Interface method desugaring has inserted NoSuchMethodError replacing a"
-                              + " super call in "
-                              + context.toSourceString(),
-                          context.getOrigin())));
+    if (!emulatedInterfaceDesugaring.needsDesugaring()) {
+      if (context.isDefaultMethod()) {
+        return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowNSMERewrite(
+            appView,
+            invoke,
+            () ->
+                appView
+                    .reporter()
+                    .warning(
+                        new StringDiagnostic(
+                            "Interface method desugaring has inserted NoSuchMethodError replacing a"
+                                + " super call in "
+                                + context.toSourceString(),
+                            context.getOrigin())));
+      } else {
+        return DesugarDescription.builder()
+            .addScanEffect(() -> leavingSuperInvokeToInterface(context))
+            .build();
+      }
     }
+
     return emulatedInterfaceDesugaring;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 75c275f..d3d0ece 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -45,7 +45,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.AssumeInfoCollection;
 import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
 import com.google.common.collect.Sets;
@@ -164,12 +163,10 @@
       return false;
     }
 
-    SyntheticItems syntheticItems = appView.getSyntheticItems();
     ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    if (!classToFeatureSplitMap.isInSameFeatureOrBothInSameBase(
-        singleTarget, method, syntheticItems)) {
+    if (!classToFeatureSplitMap.isInSameFeatureOrBothInSameBase(singleTarget, method, appView)) {
       // Still allow inlining if we inline from the base into a feature.
-      if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder(), syntheticItems)) {
+      if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder(), appView)) {
         whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 59e06c4..96b79b1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -386,9 +386,7 @@
         .appInfo()
         .getClassToFeatureSplitMap()
         .isInBaseOrSameFeatureAs(
-            resolvedMember.getHolderType(),
-            context.asProgramMethod(),
-            appView.getSyntheticItems())) {
+            resolvedMember.getHolderType(), context.asProgramMethod(), appView)) {
       // We never inline into the base from a feature (calls should never happen) and we
       // never inline between features, so this check should be sufficient.
       return ConstraintWithTarget.NEVER;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 879646e..be176f2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -264,9 +264,7 @@
 
     // Make sure the (base) type is visible.
     ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    if (AccessControl.isClassAccessible(
-            baseClass, context, classToFeatureSplitMap, appView.getSyntheticItems())
-        .isPossiblyFalse()) {
+    if (AccessControl.isClassAccessible(baseClass, context, appView).isPossiblyFalse()) {
       return;
     }
 
@@ -274,7 +272,7 @@
     // or in the base.
     assert !baseClass.isProgramClass()
         || classToFeatureSplitMap.isInBaseOrSameFeatureAs(
-            baseClass.asProgramClass(), context, appView.getSyntheticItems());
+            baseClass.asProgramClass(), context, appView);
 
     consumer.accept(type, baseClass);
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
index d42da83..0d764a5 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapReaderWithFiltering.java
@@ -18,7 +18,7 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
-import java.util.Set;
+import java.util.function.Predicate;
 
 public abstract class ProguardMapReaderWithFiltering implements LineReader {
 
@@ -188,9 +188,9 @@
   private int startIndex = 0;
   private int endIndex = 0;
 
-  private final Set<String> filter;
+  private final Predicate<String> filter;
 
-  protected ProguardMapReaderWithFiltering(Set<String> filter) {
+  protected ProguardMapReaderWithFiltering(Predicate<String> filter) {
     this.filter = filter;
   }
 
@@ -220,7 +220,7 @@
         seenFirstClass = true;
         String classMapping = getBufferAsString(bytes);
         String obfuscatedClassName = getObfuscatedClassName(classMapping);
-        isInsideClassOfInterest = filter.contains(obfuscatedClassName);
+        isInsideClassOfInterest = filter.test(obfuscatedClassName);
         return classMapping;
       } else if (lineParserState == IS_COMMENT_SOURCE_FILE) {
         return getBufferAsString(bytes);
@@ -284,7 +284,7 @@
     private int temporaryBufferPosition = 0;
 
     public ProguardMapReaderWithFilteringMappedBuffer(
-        Path mappingFile, Set<String> classNamesOfInterest) throws IOException {
+        Path mappingFile, Predicate<String> classNamesOfInterest) throws IOException {
       super(classNamesOfInterest);
       fileChannel = FileChannel.open(mappingFile, StandardOpenOption.READ);
       channelSize = fileChannel.size();
@@ -364,7 +364,7 @@
     private int endReadIndex = 0;
 
     public ProguardMapReaderWithFilteringInputBuffer(
-        InputStream inputStream, Set<String> classNamesOfInterest) {
+        InputStream inputStream, Predicate<String> classNamesOfInterest) {
       super(classNamesOfInterest);
       this.inputStream = inputStream;
     }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
index ebba380..8cf9ace 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringMappedBuffer;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Predicate;
 
 public class ProguardMappingProviderBuilderImpl extends ProguardMappingProvider.Builder {
 
@@ -64,7 +65,7 @@
   @Override
   public ProguardMappingProvider build() {
     try {
-      Set<String> buildForClass = allowLookupAllClasses ? null : allowedLookup;
+      Predicate<String> buildForClass = allowLookupAllClasses ? null : allowedLookup::contains;
       LineReader reader =
           proguardMapProducer.isFileBacked()
               ? new ProguardMapReaderWithFilteringMappedBuffer(
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 677338b..f4e27a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -319,7 +319,7 @@
         previous.getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
         previous.getMainDexInfo().withoutPrunedItems(prunedItems),
         previous.getMissingClasses(),
-        previous.getStartupOrder().withoutPrunedItems(prunedItems),
+        previous.getStartupOrder().withoutPrunedItems(prunedItems, previous.getSyntheticItems()),
         previous.deadProtoTypes,
         pruneClasses(previous.liveTypes, prunedItems, executorService, futures),
         pruneMethods(previous.targetedMethods, prunedItems, executorService, futures),
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 3addb2d..c731f7d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -460,6 +460,8 @@
 
   private final InterfaceProcessor interfaceProcessor;
 
+  private final Thread mainThreadForTesting = Thread.currentThread();
+
   Enqueuer(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ExecutorService executorService,
@@ -686,6 +688,7 @@
       ProgramDerivedContext context,
       BiConsumer<DexClass, ProgramDerivedContext> foundClassConsumer,
       BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer) {
+    assert verifyIsMainThread();
     return internalDefinitionFor(type, context, foundClassConsumer, missingClassConsumer)
         .toSingleClassWithProgramOverLibrary();
   }
@@ -1958,6 +1961,11 @@
   // Actual actions performed.
   //
 
+  private boolean verifyIsMainThread() {
+    assert Thread.currentThread() == mainThreadForTesting;
+    return true;
+  }
+
   private boolean verifyMethodIsTargeted(ProgramMethod method) {
     DexEncodedMethod definition = method.getDefinition();
     assert !definition.isClassInitializer() : "Class initializers are never targeted";
@@ -3943,7 +3951,7 @@
           lambdaCallback.andThen(
               (clazz, context) -> {
                 for (DexType itf : clazz.getLambdaProgramClass().getInterfaces()) {
-                  if (definitionFor(itf, context) == null) {
+                  if (appInfo().definitionFor(itf, context) == null) {
                     for (ProgramMethod method :
                         clazz.getLambdaProgramClass().virtualProgramMethods()) {
                       synchronized (additions) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index b5898c5..37641f0 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -389,7 +389,7 @@
 
     if (!appInfo
         .getClassToFeatureSplitMap()
-        .isInSameFeatureOrBothInSameBase(sourceClass, targetClass, appView.getSyntheticItems())) {
+        .isInSameFeatureOrBothInSameBase(sourceClass, targetClass, appView)) {
       return false;
     }
     if (appView.appServices().allServiceTypes().contains(sourceClass.type)
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index 3b3aeea..8c2a3bf 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
 
 import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -69,11 +70,18 @@
       DexProgramClass clazz, DexType synthesizingContextType, AppView<?> appView) {
     // A context that is itself synthetic must denote a synthesizing context from which to ensure
     // hygiene. This synthesizing context type is encoded on the synthetic for intermediate builds.
-    FeatureSplit featureSplit =
-        appView
-            .appInfoForDesugaring()
-            .getClassToFeatureSplitMap()
-            .getFeatureSplit(clazz, appView.getSyntheticItems());
+    FeatureSplit featureSplit;
+    if (appView.hasClassHierarchy()) {
+      AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+          appView.withClassHierarchy();
+      featureSplit =
+          appViewWithClassHierarchy
+              .appInfo()
+              .getClassToFeatureSplitMap()
+              .getFeatureSplit(clazz, appViewWithClassHierarchy);
+    } else {
+      featureSplit = FeatureSplit.BASE;
+    }
     return new SynthesizingContext(synthesizingContextType, clazz.type, clazz.origin, featureSplit);
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 86206f6..eb764c0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -642,11 +642,13 @@
       boolean mustBeRepresentative = isPinned(appView, synthetic);
       EquivalenceGroup<T> equivalenceGroup = null;
       for (EquivalenceGroup<T> group : groups) {
+        boolean includeContext =
+            intermediate || appView.options().getStartupOptions().isStartupInstrumentationEnabled();
         if (synthetic.isEquivalentTo(
             group.hasRepresentative()
                 ? group.getRepresentative()
                 : group.getFirstNonRepresentativeMember(),
-            intermediate,
+            includeContext,
             appView.graphLens(),
             classToFeatureSplitMap)) {
           if (mustBeRepresentative && group.hasRepresentative()) {
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 e3d52a4..2c2f503 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -9,8 +9,10 @@
 import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
 import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.startup.StartupOrder;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassResolutionResult;
 import com.android.tools.r8.graph.ClasspathMethod;
@@ -209,14 +211,6 @@
     return globalSyntheticsStrategy;
   }
 
-  // Empty collection for use only in tests and utilities.
-  public static SyntheticItems empty() {
-    return new SyntheticItems(
-        State.FINALIZED,
-        CommittedSyntheticsCollection.empty(null),
-        GlobalSyntheticsStrategy.forNonSynthesizing());
-  }
-
   // Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */
   public static CommittedItems createInitialSyntheticItems(
       DexApplication application, GlobalSyntheticsStrategy globalSyntheticsStrategy) {
@@ -534,13 +528,18 @@
 
   private SynthesizingContext getSynthesizingContext(
       ProgramDefinition context, AppView<?> appView) {
+    if (appView.hasClassHierarchy()) {
+      AppInfoWithClassHierarchy appInfo = appView.appInfoWithClassHierarchy();
+      return getSynthesizingContext(
+          context, appInfo.getClassToFeatureSplitMap(), appInfo.getStartupOrder());
+    }
     return getSynthesizingContext(
-        context, appView.appInfoForDesugaring().getClassToFeatureSplitMap());
+        context, ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(), 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) {
+      ProgramDefinition context, ClassToFeatureSplitMap featureSplits, StartupOrder startupOrder) {
     DexType contextType = context.getContextType();
     SyntheticDefinition<?, ?, ?> existingDefinition = pending.definitions.get(contextType);
     if (existingDefinition != null) {
@@ -556,7 +555,7 @@
           .getContext();
     }
     // This context is not nested in an existing synthetic context so create a new "leaf" context.
-    FeatureSplit featureSplit = featureSplits.getFeatureSplit(context, this);
+    FeatureSplit featureSplit = featureSplits.getFeatureSplit(context, startupOrder, this);
     return SynthesizingContext.fromNonSyntheticInputContext(context, featureSplit);
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/AccessUtils.java b/src/main/java/com/android/tools/r8/utils/AccessUtils.java
index 6e54e2a..84f644a 100644
--- a/src/main/java/com/android/tools/r8/utils/AccessUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AccessUtils.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticItems;
 
 public class AccessUtils {
 
@@ -43,13 +42,11 @@
     // If the new class is a program class, we need to check if it is in a feature.
     if (newBaseClass.isProgramClass()) {
       ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-      SyntheticItems syntheticItems = appView.getSyntheticItems();
       if (classToFeatureSplitMap != null) {
         FeatureSplit newFeatureSplit =
-            classToFeatureSplitMap.getFeatureSplit(newBaseClass.asProgramClass(), syntheticItems);
+            classToFeatureSplitMap.getFeatureSplit(newBaseClass.asProgramClass(), appView);
         if (!newFeatureSplit.isBase()
-            && newFeatureSplit
-                != classToFeatureSplitMap.getFeatureSplit(oldBaseType, syntheticItems)) {
+            && newFeatureSplit != classToFeatureSplitMap.getFeatureSplit(oldBaseType, appView)) {
           return false;
         }
       }
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 14d7941..40c5a67 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.startup.StartupConfiguration;
+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;
@@ -627,8 +628,10 @@
                       classDescriptor -> {
                         if (featureSplitConfiguration != null) {
                           DexType type = dexItemFactory.createType(classDescriptor);
+                          SyntheticItems syntheticItems = null;
                           FeatureSplit featureSplit =
-                              classToFeatureSplitMap.getFeatureSplit(type, SyntheticItems.empty());
+                              classToFeatureSplitMap.getFeatureSplit(
+                                  type, StartupOrder.empty(), syntheticItems);
                           if (featureSplit != null && !featureSplit.isBase()) {
                             return featureSplitArchiveOutputStreams.get(featureSplit);
                           }
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
deleted file mode 100644
index 00412d9..0000000
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ /dev/null
@@ -1,313 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import com.android.tools.r8.ArchiveClassFileProvider;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar;
-import com.android.tools.r8.origin.PathOrigin;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- * Provides a mappings of classes to modules. The structure of the input file is as follows:
- * packageOrClass:module
- *
- * <p>Lines with a # prefix are ignored.
- *
- * <p>We will do most specific matching, i.e.,
- * <pre>
- *   com.google.foobar.*:feature2
- *   com.google.*:base
- * </pre>
- * will put everything in the com.google namespace into base, except classes in com.google.foobar
- * that will go to feature2. Class based mappings takes precedence over packages (since they are
- * more specific):
- * <pre>
- *   com.google.A:feature2
- *   com.google.*:base
- *  </pre>
- * Puts A into feature2, and all other classes from com.google into base.
- *
- * <p>Note that this format does not allow specifying inter-module dependencies, this is simply a
- * placement tool.
- */
-@Keep
-public final class FeatureClassMapping {
-
-  Map<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
-  Map<String, String> parseNonClassRules = new HashMap<>();
-  boolean usesOnlyExactMappings = true;
-
-  Set<FeaturePredicate> mappings = new HashSet<>();
-
-  Path mappingFile;
-  String baseName = DEFAULT_BASE_NAME;
-
-  static final String DEFAULT_BASE_NAME = "base";
-
-  static final String COMMENT = "#";
-  static final String SEPARATOR = ":";
-
-  public String getBaseName() {
-    return baseName;
-  }
-
-  private static class SpecificationOrigin extends PathOrigin {
-
-    public SpecificationOrigin(Path path) {
-      super(path);
-    }
-
-    @Override
-    public String part() {
-      return "specification file '" + super.part() + "'";
-    }
-  }
-
-  private static class JarFileOrigin extends PathOrigin {
-
-    public JarFileOrigin(Path path) {
-      super(path);
-    }
-
-    @Override
-    public String part() {
-      return "jar file '" + super.part() + "'";
-    }
-  }
-
-  public static FeatureClassMapping fromSpecification(Path file) throws FeatureMappingException {
-    return fromSpecification(file, new DiagnosticsHandler() {});
-  }
-
-  public static FeatureClassMapping fromSpecification(Path file, DiagnosticsHandler reporter)
-      throws FeatureMappingException {
-    FeatureClassMapping mapping = new FeatureClassMapping();
-    List<String> lines = null;
-    try {
-      lines = FileUtils.readAllLines(file);
-    } catch (IOException e) {
-      ExceptionDiagnostic error = new ExceptionDiagnostic(e, new SpecificationOrigin(file));
-      reporter.error(error);
-      throw new AbortException(error);
-    }
-    for (int i = 0; i < lines.size(); i++) {
-      String line = lines.get(i);
-      mapping.parseAndAdd(line, i);
-    }
-    return mapping;
-  }
-
-  public static class Internal {
-    private static List<String> getClassFileDescriptors(String jar, DiagnosticsHandler reporter) {
-      Path jarPath = Paths.get(jar);
-      try {
-        return new ArchiveClassFileProvider(jarPath).getClassDescriptors()
-            .stream()
-            .map(DescriptorUtils::descriptorToJavaType)
-            .collect(Collectors.toList());
-      } catch (IOException e) {
-        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(jarPath));
-        reporter.error(error);
-        throw new AbortException(error);
-      }
-    }
-
-    private static List<String> getNonClassFiles(String jar, DiagnosticsHandler reporter) {
-      try (ZipFile zipfile = new ZipFile(jar, StandardCharsets.UTF_8)) {
-          return zipfile.stream()
-              .filter(entry -> !ZipUtils.isClassFile(entry.getName()))
-              .map(ZipEntry::getName)
-              .collect(Collectors.toList());
-        } catch (IOException e) {
-        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(Paths.get(jar)));
-        reporter.error(error);
-        throw new AbortException(error);
-        }
-    }
-
-    public static FeatureClassMapping fromJarFiles(
-        List<FeatureJar> featureJars, List<String> baseJars, String baseName,
-        DiagnosticsHandler reporter)
-        throws FeatureMappingException {
-      FeatureClassMapping mapping = new FeatureClassMapping();
-      if (baseName != null) {
-        mapping.baseName = baseName;
-      }
-      for (FeatureJar featureJar : featureJars) {
-        for (String javaType : getClassFileDescriptors(featureJar.getJar(), reporter)) {
-          mapping.addMapping(javaType, featureJar.getOutputName());
-        }
-        for (String nonClass : getNonClassFiles(featureJar.getJar(), reporter)) {
-          mapping.addNonClassMapping(nonClass, featureJar.getOutputName());
-        }
-      }
-      for (String baseJar : baseJars) {
-        for (String javaType : getClassFileDescriptors(baseJar, reporter)) {
-          mapping.addBaseMapping(javaType);
-        }
-        for (String nonClass : getNonClassFiles(baseJar, reporter)) {
-          mapping.addBaseNonClassMapping(nonClass);
-        }
-      }
-      assert mapping.usesOnlyExactMappings;
-      return mapping;
-    }
-
-  }
-
-  private FeatureClassMapping() {}
-
-  public void addBaseMapping(String clazz) throws FeatureMappingException {
-    addMapping(clazz, baseName);
-  }
-
-  public void addBaseNonClassMapping(String name) {
-    addNonClassMapping(name, baseName);
-  }
-
-  public void addMapping(String clazz, String feature) throws FeatureMappingException {
-    addRule(clazz, feature, 0);
-  }
-
-  public void addNonClassMapping(String name, String feature) {
-    // If a non-class file is present in multiple features put the resource in the base.
-    parseNonClassRules.put(name, parseNonClassRules.containsKey(name) ? baseName : feature);
-  }
-
-  FeatureClassMapping(List<String> lines) throws FeatureMappingException {
-    for (int i = 0; i < lines.size(); i++) {
-      String line = lines.get(i);
-      parseAndAdd(line, i);
-    }
-  }
-
-  public String featureForClass(String clazz) {
-    if (usesOnlyExactMappings) {
-      return parsedRules.getOrDefault(clazz, baseName);
-    } else {
-      FeaturePredicate bestMatch = null;
-      for (FeaturePredicate mapping : mappings) {
-        if (mapping.match(clazz)) {
-          if (bestMatch == null || bestMatch.predicate.length() < mapping.predicate.length()) {
-            bestMatch = mapping;
-          }
-        }
-      }
-      if (bestMatch == null) {
-        return baseName;
-      }
-      return bestMatch.feature;
-    }
-  }
-
-  public String featureForNonClass(String nonClass) {
-    return parseNonClassRules.getOrDefault(nonClass, baseName);
-  }
-
-  private void parseAndAdd(String line, int lineNumber) throws FeatureMappingException {
-    if (line.startsWith(COMMENT)) {
-      return; // Ignore comments
-    }
-    if (line.isEmpty()) {
-      return; // Ignore blank lines
-    }
-
-    if (!line.contains(SEPARATOR)) {
-      error("Mapping lines must contain a " + SEPARATOR, lineNumber);
-    }
-    String[] values = line.split(SEPARATOR);
-    if (values.length != 2) {
-      error("Mapping lines can only contain one " + SEPARATOR, lineNumber);
-    }
-
-    String predicate = values[0];
-    String feature = values[1];
-    addRule(predicate, feature, lineNumber);
-  }
-
-  private void addRule(String predicate, String feature, int lineNumber)
-      throws FeatureMappingException {
-    if (parsedRules.containsKey(predicate)) {
-      if (!parsedRules.get(predicate).equals(feature)) {
-        error("Redefinition of predicate " + predicate + "not allowed", lineNumber);
-      }
-      return; // Already have this rule.
-    }
-    parsedRules.put(predicate, feature);
-    FeaturePredicate featurePredicate = new FeaturePredicate(predicate, feature);
-    mappings.add(featurePredicate);
-    usesOnlyExactMappings &= featurePredicate.isExactmapping();
-  }
-
-  private void error(String error, int line) throws FeatureMappingException {
-    throw new FeatureMappingException(
-        "Invalid mappings specification: " + error + "\n in file " + mappingFile + ":" + line);
-  }
-
-  @Keep
-  public static class FeatureMappingException extends Exception {
-    FeatureMappingException(String message) {
-      super(message);
-    }
-  }
-
-  /** A feature predicate can either be a wildcard or class predicate. */
-  private static class FeaturePredicate {
-    private static Pattern identifier = Pattern.compile("[A-Za-z_\\-][A-Za-z0-9_$\\-]*");
-    final String predicate;
-    final String feature;
-    final boolean isCatchAll;
-    // False implies class predicate.
-    final boolean isWildcard;
-
-    FeaturePredicate(String predicate, String feature) throws FeatureMappingException {
-      isWildcard = predicate.endsWith(".*");
-      isCatchAll =  predicate.equals("*");
-      if (isCatchAll) {
-        this.predicate = "";
-      } else if (isWildcard) {
-        String packageName = predicate.substring(0, predicate.length() - 2);
-        if (!DescriptorUtils.isValidJavaType(packageName)) {
-          throw new FeatureMappingException(packageName + " is not a valid identifier");
-        }
-        // Prefix of a fully-qualified class name, including a terminating dot.
-        this.predicate = predicate.substring(0, predicate.length() - 1);
-      } else {
-        if (!DescriptorUtils.isValidJavaType(predicate)) {
-          throw new FeatureMappingException(predicate + " is not a valid identifier");
-        }
-        this.predicate = predicate;
-      }
-      this.feature = feature;
-    }
-
-    boolean match(String className) {
-      if (isCatchAll) {
-        return true;
-      } else if (isWildcard) {
-        return className.startsWith(predicate);
-      } else {
-        return className.equals(predicate);
-      }
-    }
-
-    boolean isExactmapping() {
-      return !isWildcard && !isCatchAll;
-    }
-  }
-}
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 c836c46..ee15be2 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -21,9 +21,12 @@
 import com.android.tools.r8.Version;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.debuginfo.DebugRepresentation;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Backend;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.dex.MixedSectionLayoutStrategy;
+import com.android.tools.r8.dex.VirtualFile;
 import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
@@ -104,6 +107,7 @@
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -259,6 +263,16 @@
     horizontalClassMergerOptions.setRestrictToSynthetics();
   }
 
+  public void configureAndroidPlatformBuild(boolean isAndroidPlatformBuild) {
+    if (!isAndroidPlatformBuild) {
+      return;
+    }
+    // Configure options according to platform build assumptions.
+    // See go/r8platformflag and b/232073181.
+    minApiLevel = ANDROID_PLATFORM;
+    apiModelingOptions().disableMissingApiModeling();
+  }
+
   public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
   // To print memory one also have to enable printtimes.
   public boolean printMemory = System.getProperty("com.android.tools.r8.printmemory") != null;
@@ -853,6 +867,10 @@
     return startupOptions;
   }
 
+  public TestingOptions getTestingOptions() {
+    return testing;
+  }
+
   private static Set<String> getExtensiveLoggingFilter() {
     String property = System.getProperty("com.android.tools.r8.extensiveLoggingFilter");
     if (property != null) {
@@ -1756,6 +1774,15 @@
             ? NondeterministicIROrdering.getInstance()
             : IdentityIROrdering.getInstance();
 
+    public BiFunction<MixedSectionLayoutStrategy, VirtualFile, MixedSectionLayoutStrategy>
+        mixedSectionLayoutStrategyInspector = (strategy, virtualFile) -> strategy;
+
+    public void setMixedSectionLayoutStrategyInspector(
+        BiFunction<MixedSectionLayoutStrategy, VirtualFile, MixedSectionLayoutStrategy>
+            mixedSectionLayoutStrategyInspector) {
+      this.mixedSectionLayoutStrategyInspector = mixedSectionLayoutStrategyInspector;
+    }
+
     public BiConsumer<AppInfoWithLiveness, Enqueuer.Mode> enqueuerInspector = null;
 
     public Consumer<String> processingContextsConsumer = null;
@@ -1778,6 +1805,8 @@
 
     public Consumer<Deque<ProgramMethodSet>> waveModifier = waves -> {};
 
+    public Consumer<DebugRepresentation> debugRepresentationCallback = null;
+
     /**
      * If this flag is enabled, we will also compute the set of possible targets for invoke-
      * interface and invoke-virtual instructions that target a library method, and add the
@@ -1819,6 +1848,10 @@
     public boolean dontCreateMarkerInD8 = false;
     public boolean forceJumboStringProcessing = false;
     public boolean forcePcBasedEncoding = false;
+    public int pcBasedDebugEncodingOverheadThreshold =
+        System.getProperty("com.android.tools.r8.pc2pcOverheadThreshold") != null
+            ? Integer.parseInt(System.getProperty("com.android.tools.r8.pc2pcOverheadThreshold"))
+            : 200000;
     public Set<Inliner.Reason> validInliningReasons = null;
     public boolean noLocalsTableOnInput = false;
     public boolean forceNameReflectionOptimization = false;
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 389fd36..660a5fd 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -76,9 +76,8 @@
 import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
-import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -354,10 +353,10 @@
 
   private interface PcBasedDebugInfoRecorder {
     /** Callback to record a code object with a given max instruction PC and parameter count. */
-    void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc);
+    void recordPcMappingFor(ProgramMethod method, int maxEncodingPc);
 
     /** Callback to record a code object with only a single "line". */
-    void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc);
+    void recordSingleLineFor(ProgramMethod method, int maxEncodingPc);
 
     /**
      * Install the correct debug info objects.
@@ -406,32 +405,44 @@
       singleLineCodesToClear = allowDiscardingSourceFile ? new ArrayList<>() : null;
     }
 
+    private int getLastInstructionOffset(DexCode code) {
+      return DebugRepresentation.getLastExecutableInstruction(code).getOffset();
+    }
+
+    private boolean cantAddToClearSet(ProgramMethod method) {
+      assert method.getDefinition().getCode().isDexCode();
+      if (singleLineCodesToClear == null) {
+        return true;
+      }
+      singleLineCodesToClear.add(method.getDefinition().getCode().asDexCode());
+      return false;
+    }
+
     @Override
-    public void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc) {
+    public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
+      assert method.getDefinition().getCode().isDexCode();
+      int parameterCount = method.getParameters().size();
+      DexCode code = method.getDefinition().getCode().asDexCode();
       assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(code, maxEncodingPc);
       codesToUpdate.add(new UpdateInfo(code, parameterCount, maxEncodingPc));
     }
 
     @Override
-    public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
-      if (singleLineCodesToClear != null) {
-        singleLineCodesToClear.add(code);
-        return;
+    public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
+      if (cantAddToClearSet(method)) {
+        recordPcMappingFor(method, maxEncodingPc);
       }
-      recordPcMappingFor(code, parameterCount, maxEncodingPc);
     }
 
     @Override
     public void updateDebugInfoInCodeObjects() {
-      Object2ReferenceMap<UpdateInfo, DexDebugInfo> debugInfos =
-          new Object2ReferenceOpenHashMap<>();
+      Map<UpdateInfo, DexDebugInfo> debugInfos = new HashMap<>();
       codesToUpdate.forEach(
           entry -> {
             assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(
                 entry.code, entry.maxEncodingPc);
             DexDebugInfo debugInfo =
-                debugInfos.computeIfAbsent(
-                    entry, key -> buildPc2PcDebugInfo(key.maxEncodingPc, key.paramCount));
+                debugInfos.computeIfAbsent(entry, Pc2PcMappingSupport::buildPc2PcDebugInfo);
             assert debugInfo.asPcBasedInfo().getMaxPc() == entry.maxEncodingPc;
             entry.code.setDebugInfo(debugInfo);
           });
@@ -440,23 +451,26 @@
       }
     }
 
-    private static DexDebugInfo buildPc2PcDebugInfo(int lastInstructionPc, int parameterCount) {
-      return new DexDebugInfo.PcBasedDebugInfo(parameterCount, lastInstructionPc);
+    private static DexDebugInfo buildPc2PcDebugInfo(UpdateInfo info) {
+      return new DexDebugInfo.PcBasedDebugInfo(info.paramCount, info.maxEncodingPc);
     }
   }
 
   private static class NativePcSupport implements PcBasedDebugInfoRecorder {
 
-    @Override
-    public void recordPcMappingFor(DexCode code, int length, int maxEncodingPc) {
-      // Strip the info in full as the runtime will emit the PC directly.
-      code.setDebugInfo(null);
+    private void clearDebugInfo(ProgramMethod method) {
+      // Always strip the info in full as the runtime will emit the PC directly.
+      method.getDefinition().getCode().asDexCode().setDebugInfo(null);
     }
 
     @Override
-    public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
-      // Strip the info at once as it does not conflict with any PC mapping update.
-      code.setDebugInfo(null);
+    public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
+      clearDebugInfo(method);
+    }
+
+    @Override
+    public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
+      clearDebugInfo(method);
     }
 
     @Override
@@ -567,7 +581,7 @@
           List<MappedPosition> mappedPositions;
           Code code = definition.getCode();
           int pcEncodingCutoff =
-              methods.size() == 1 ? representation.getDexPcEncodingCutoff(clazz, definition) : -1;
+              methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
           boolean canUseDexPc = pcEncodingCutoff > 0;
           if (code != null) {
             if (code.isDexCode()
@@ -575,7 +589,7 @@
               if (canUseDexPc) {
                 mappedPositions =
                     optimizeDexCodePositionsForPc(
-                        definition, appView, kotlinRemapper, pcBasedDebugInfo, pcEncodingCutoff);
+                        method, pcEncodingCutoff, appView, kotlinRemapper, pcBasedDebugInfo);
               } else {
                 mappedPositions =
                     optimizeDexCodePositions(
@@ -749,8 +763,7 @@
           if (definition.getCode().isDexCode()
               && definition.getCode().asDexCode().getDebugInfo()
                   == DexDebugInfoForSingleLineMethod.getInstance()) {
-            pcBasedDebugInfo.recordSingleLineFor(
-                definition.getCode().asDexCode(), method.getParameters().size(), pcEncodingCutoff);
+            pcBasedDebugInfo.recordSingleLineFor(method, pcEncodingCutoff);
           }
         } // for each method of the group
       } // for each method group, grouped by name
@@ -1194,15 +1207,16 @@
   }
 
   private static List<MappedPosition> optimizeDexCodePositionsForPc(
-      DexEncodedMethod method,
+      ProgramMethod method,
+      int pcEncodingCutoff,
       AppView<?> appView,
       PositionRemapper positionRemapper,
-      PcBasedDebugInfoRecorder debugInfoProvider,
-      int pcEncodingCutoff) {
+      PcBasedDebugInfoRecorder debugInfoProvider) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
-    DexCode dexCode = method.getCode().asDexCode();
-    EventBasedDebugInfo debugInfo = getEventBasedDebugInfo(method, dexCode, appView);
+    DexCode dexCode = method.getDefinition().getCode().asDexCode();
+    EventBasedDebugInfo debugInfo =
+        getEventBasedDebugInfo(method.getDefinition(), dexCode, appView);
     IntBox firstDefaultEventPc = new IntBox(-1);
     BooleanBox singleOriginalLine = new BooleanBox(true);
     Pair<Integer, Position> lastPosition = new Pair<>();
@@ -1269,10 +1283,9 @@
         && lastPosition.getSecond() != null
         && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
       dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
-      debugInfoProvider.recordSingleLineFor(
-          dexCode, method.getParameters().size(), pcEncodingCutoff);
+      debugInfoProvider.recordSingleLineFor(method, pcEncodingCutoff);
     } else {
-      debugInfoProvider.recordPcMappingFor(dexCode, debugInfo.parameters.length, pcEncodingCutoff);
+      debugInfoProvider.recordPcMappingFor(method, pcEncodingCutoff);
     }
     return mappedPositions;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 59011c5..7275934 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -184,7 +184,12 @@
     return join(LINE_SEPARATOR, collection, BraceType.NONE);
   }
 
+
   public static List<String> splitLines(String content) {
+    return splitLines(content, false);
+  }
+
+  public static List<String> splitLines(String content, boolean includeTrailingEmptyLine) {
     int length = content.length();
     List<String> lines = new ArrayList<>();
     int start = 0;
@@ -201,7 +206,7 @@
     }
     if (start < length) {
       String line = content.substring(start);
-      if (!line.isEmpty()) {
+      if (includeTrailingEmptyLine || !line.isEmpty()) {
         lines.add(line);
       }
     }
diff --git a/src/main/keep.txt b/src/main/keep.txt
index e376924..deb8e6d 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -12,7 +12,6 @@
 -keep public class com.android.tools.r8.D8 { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.R8 { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.ExtractMarker { public static void main(java.lang.String[]); }
--keep public class com.android.tools.r8.dexsplitter.DexSplitter { public static void main(java.lang.String[]); }
 
 -keep public class com.android.tools.r8.Version { public static final java.lang.String LABEL; }
 -keep public class com.android.tools.r8.Version { public static java.lang.String getVersionString(); }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 302b84a..8b951c9 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.ApiModelTestingOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
@@ -901,6 +902,14 @@
     numThreadsOptionInvalid("two");
   }
 
+  @Test
+  public void defaultApiModelingState() throws Exception {
+    ApiModelTestingOptions options = parse("").getInternalOptions().apiModelingOptions();
+    assertTrue(options.enableApiCallerIdentification);
+    assertTrue(options.enableOutliningOfMethods);
+    assertTrue(options.enableStubbingOfClasses);
+  }
+
   @Override
   String[] requiredArgsForTest() {
     return new String[0];
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index fe500f8..f343c0f 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -118,8 +118,10 @@
     getApp().writeToDirectory(out, OutputMode.DexIndexed);
     consumers[0].accept(new CodeInspector(out.resolve("classes.dex"), getProguardMap()));
     for (int i = 1; i < consumers.length; i++) {
-      consumers[i].accept(
-          new CodeInspector(out.resolve("classes" + (i + 1) + ".dex"), getProguardMap()));
+      Path dex = out.resolve("classes" + (i + 1) + ".dex");
+      CodeInspector inspector =
+          dex.toFile().exists() ? new CodeInspector(dex, getProguardMap()) : CodeInspector.empty();
+      consumers[i].accept(inspector);
     }
     return self();
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index eb7df8c..b22d6d3 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -2117,7 +2117,7 @@
   public static class ProcessResult {
 
     public final int exitCode;
-    public final String stdout;
+    public String stdout;
     public final String stderr;
     public final String command;
 
@@ -2132,6 +2132,10 @@
       this(exitCode, stdout, stderr, null);
     }
 
+    public void setStdout(String stdout) {
+      this.stdout = stdout;
+    }
+
     @Override
     public String toString() {
       StringBuilder builder = new StringBuilder();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
new file mode 100644
index 0000000..0726238
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelCovariantReturnTypeTest.java
@@ -0,0 +1,66 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentHashMap.KeySetView;
+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 ApiModelCovariantReturnTypeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ApiModelCovariantReturnTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Method main = Main.class.getDeclaredMethod("main", String[].class);
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .applyIf(
+            parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+            b -> b.addDontWarn(KeySetView.class))
+        .addKeepMainRule(Main.class)
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(
+            addTracedApiReferenceLevelCallBack(
+                (method, apiLevel) -> {
+                  if (Reference.methodFromMethod(main).equals(method)) {
+                    // TODO(b/232891189): Should be api level 28.
+                    assertNull(apiLevel);
+                  }
+                }))
+        .compile();
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
+      KeySetView<String, String> strings = map.keySet();
+    }
+  }
+}
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 ae8d0b6..e77e3d9 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
@@ -7,12 +7,13 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+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.graph.DexType;
 import com.google.common.collect.ImmutableList;
 import java.util.Collections;
 import java.util.List;
-import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -47,11 +48,17 @@
               options
                   .getStartupOptions()
                   .setStartupConfiguration(
-                      new StartupConfiguration(
-                          startupClasses.stream()
-                              .map(clazz -> toDexType(clazz, dexItemFactory))
-                              .collect(Collectors.toList()),
-                          Collections.emptyList()));
+                      StartupConfiguration.builder()
+                          .apply(
+                              builder ->
+                                  startupClasses.forEach(
+                                      startupClass ->
+                                          builder.addStartupClass(
+                                              StartupClass.<DexType>builder()
+                                                  .setReference(
+                                                      toDexType(startupClass, dexItemFactory))
+                                                  .build())))
+                          .build());
             })
         .addHorizontallyMergedClassesInspector(
             inspector ->
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 0dcd986..017c652 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.ToolHelper.isTestingR8Lib;
 
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.androidplatformbuild.AndroidPlatformBuildApiTest;
 import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
 import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
 import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
@@ -45,7 +46,8 @@
       ImmutableList.of(
           GlobalSyntheticsTest.ApiTest.class,
           CommandLineParserTest.ApiTest.class,
-          EnableMissingLibraryApiModelingTest.ApiTest.class);
+          EnableMissingLibraryApiModelingTest.ApiTest.class,
+          AndroidPlatformBuildApiTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java
new file mode 100644
index 0000000..07d01cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/androidplatformbuild/AndroidPlatformBuildApiTest.java
@@ -0,0 +1,94 @@
+// 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.compilerapi.androidplatformbuild;
+
+import static com.android.tools.r8.MarkerMatcher.markerMinApi;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+public class AndroidPlatformBuildApiTest extends CompilerApiTestRunner {
+
+  public AndroidPlatformBuildApiTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runD8);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runR8);
+  }
+
+  private void runTest(ThrowingConsumer<ProgramConsumer, Exception> test) throws Exception {
+    Path output = temp.newFolder().toPath().resolve("out.jar");
+    test.accept(new DexIndexedConsumer.ArchiveConsumer(output));
+    assertThat(
+        new CodeInspector(output).getMarkers(),
+        CoreMatchers.everyItem(markerMinApi(AndroidApiLevel.ANDROID_PLATFORM)));
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void runD8(ProgramConsumer programConsumer) throws Exception {
+      D8.run(
+          D8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(programConsumer)
+              .setAndroidPlatformBuild(true)
+              .build());
+    }
+
+    public void runR8(ProgramConsumer programConsumer) throws Exception {
+      R8.run(
+          R8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(programConsumer)
+              .setAndroidPlatformBuild(true)
+              .build());
+    }
+
+    @Test
+    public void testD8() throws Exception {
+      runD8(DexIndexedConsumer.emptyConsumer());
+    }
+
+    @Test
+    public void testR8() throws Exception {
+      runR8(DexIndexedConsumer.emptyConsumer());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
index 6891000..08b55f8 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
@@ -4,15 +4,20 @@
 package com.android.tools.r8.debuginfo;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeFalse;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -91,6 +96,25 @@
             b -> b.getBuilder().setSourceFileProvider(environment -> CUSTOM_SOURCE_FILE))
         .run(parameters.getRuntime(), TestClass.class)
         .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectFailure(
+            i -> {
+              if (parameters.isDexRuntime()) {
+                Set<DexDebugInfo> debugInfos =
+                    i.allClasses().stream()
+                        .flatMap(c -> c.allMethods().stream())
+                        .map(m -> m.getMethod().getCode().asDexCode().getDebugInfo())
+                        .collect(Collectors.toSet());
+                if (isCompileWithPcAsLineNumberSupport() && !customSourceFile) {
+                  // If debug info is stripped all items are null pointers.
+                  assertEquals(Collections.singleton(null), debugInfos);
+                } else {
+                  // If debug info remains it is two canonical items and one null pointer.
+                  // The presence of 'null' debug info items is for methods with no actual lines at
+                  // all.
+                  assertEquals(3, debugInfos.size());
+                }
+              }
+            })
         .inspectOriginalStackTrace(
             stackTrace ->
                 assertThat(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
index a9aae3f..af9ac89 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -217,6 +217,8 @@
     // Manually construct the R8 command as the test builder will change defaults compared
     // to the CLI invocation (eg, compressed and pg-map output).
     Builder builder = R8Command.builder().setOutput(outputThroughCf, OutputMode.DexIndexed);
+    // Set API model until default changes.
+    builder.setEnableExperimentalMissingLibraryApiModeling(true);
     getSharedBuilder().accept(builder);
     ToolHelper.runR8WithOptionsModificationOnly(
         builder.build(),
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDex.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
similarity index 83%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDex.java
rename to src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
index 8c5cdd0..b5ad377 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDex.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.desugar.nestaccesscontrol;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
@@ -11,17 +12,19 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDex.Host.Member1;
-import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDex.Host.Member2;
+import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDexTest.Host.Member1;
+import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDexTest.Host.Member2;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.TypeSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -33,7 +36,7 @@
 import org.objectweb.asm.Opcodes;
 
 @RunWith(Parameterized.class)
-public class NestAttributesInDex extends TestBase {
+public class NestAttributesInDexTest extends TestBase {
 
   @Parameter() public TestParameters parameters;
 
@@ -62,15 +65,18 @@
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
-  private void inspect(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector, boolean emitNestAnnotationsInDex) {
     ClassSubject host = inspector.clazz(Host.class);
     ClassSubject member1 = inspector.clazz(Member1.class);
     ClassSubject member2 = inspector.clazz(Member2.class);
     assertEquals(
-        ImmutableList.of(member1.asTypeSubject(), member2.asTypeSubject()),
+        emitNestAnnotationsInDex
+            ? ImmutableList.of(member1.asTypeSubject(), member2.asTypeSubject())
+            : Collections.emptyList(),
         host.getFinalNestMembersAttribute());
-    assertEquals(host.asTypeSubject(), member1.getFinalNestHostAttribute());
-    assertEquals(host.asTypeSubject(), member2.getFinalNestHostAttribute());
+    TypeSubject expectedNestHostAttribute = emitNestAnnotationsInDex ? host.asTypeSubject() : null;
+    assertEquals(expectedNestHostAttribute, member1.getFinalNestHostAttribute());
+    assertEquals(expectedNestHostAttribute, member2.getFinalNestHostAttribute());
     ClassSubject otherHost = inspector.clazz(OtherHost.class);
     assertNull(otherHost.getFinalNestHostAttribute());
     assertEquals(0, otherHost.getFinalNestMembersAttribute().size());
@@ -83,17 +89,29 @@
         .addProgramClassFileData(getTransformedClasses())
         .addProgramClasses(OtherHost.class)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(
-            options -> {
-              options.emitNestAnnotationsInDex = true;
-            })
+        .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
         .compile()
-        .inspect(this::inspect)
+        .inspect(inspector -> inspect(inspector, true))
         .run(parameters.getRuntime(), TestClass.class)
         // No Art versions have support for nest attributes yet.
         .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
   }
 
+  @Test
+  public void testD8NoDesugar() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .addProgramClasses(OtherHost.class)
+        .disableDesugaring()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> assertFalse(options.emitNestAnnotationsInDex))
+        .compile()
+        .inspect(inspector -> inspect(inspector, false))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
   public Collection<byte[]> getTransformedClasses() throws Exception {
     ClassFileTransformer transformer =
         transformer(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultMethodResolvingToLibraryTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultMethodResolvingToLibraryTest.java
new file mode 100644
index 0000000..6e19e01
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultMethodResolvingToLibraryTest.java
@@ -0,0 +1,183 @@
+// 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.desugaring.interfacemethods;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.util.function.Function;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeSuperInDefaultMethodResolvingToLibraryTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("8");
+
+  private void inspect(CodeInspector inspector) {
+    assertTrue(
+        inspector
+            .clazz(B.class)
+            .uniqueMethodWithName("compose")
+            .streamInstructions()
+            .filter(InstructionSubject::isInvoke)
+            .map(invoke -> invoke.getMethod().getHolderType().toString())
+            .noneMatch(name -> name.endsWith("$-CC")));
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            parameters.isDexRuntime()
+                && parameters
+                    .getRuntime()
+                    .asDex()
+                    .maxSupportedApiLevel()
+                    .isLessThan(AndroidApiLevel.N),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testDesugaringWithApiLevelCheck() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addAndroidBuildVersion(parameters.getRuntime().asDex().maxSupportedApiLevel())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClassWithApiLevelCheck.class)
+        .applyIf(
+            parameters.isDexRuntime()
+                && parameters
+                    .getRuntime()
+                    .asDex()
+                    .maxSupportedApiLevel()
+                    .isLessThan(AndroidApiLevel.N),
+            r -> r.assertSuccessWithOutputLines("No call"),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addInnerClasses(getClass())
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+          .setMinApi(parameters.getApiLevel())
+          .addKeepMainRule(TestClass.class)
+          .compile()
+          // .inspect(this::inspect)
+          .run(parameters.getRuntime(), TestClass.class)
+          .applyIf(
+              parameters.isDexRuntime()
+                  && parameters
+                      .getRuntime()
+                      .asDex()
+                      .maxSupportedApiLevel()
+                      .isLessThan(AndroidApiLevel.N),
+              r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+              r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+    } catch (CompilationFailedException e) {
+      // TODO(b/235184674): Fix this.
+      assertTrue(parameters.isCfRuntime());
+    }
+  }
+
+  // TODO(b/235184674): Fix this.
+  @Test(expected = CompilationFailedException.class)
+  public void testR8WithApiLevelCheck() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClassWithApiLevelCheck.class)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClassWithApiLevelCheck.class)
+        .applyIf(
+            parameters.isDexRuntime()
+                && parameters
+                    .getRuntime()
+                    .asDex()
+                    .maxSupportedApiLevel()
+                    .isLessThan(AndroidApiLevel.N),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  static class TestClass {
+
+    private static void m(C c) {
+      System.out.println(c.compose(c).apply(2));
+    }
+
+    public static void main(String[] args) {
+      m(new C());
+    }
+  }
+
+  static class TestClassWithApiLevelCheck {
+
+    private static void m(C c) {
+      System.out.println(c.compose(c).apply(2));
+    }
+
+    public static void main(String[] args) {
+      if (AndroidBuildVersion.VERSION >= 24) {
+        m(new C());
+      } else {
+        System.out.println("No call");
+      }
+    }
+  }
+
+  interface MyFunction<V, R> extends Function<V, R> {}
+
+  abstract static class B<V, R> implements MyFunction<V, R> {
+
+    @Override
+    public <V1> Function<V1, R> compose(Function<? super V1, ? extends V> before) {
+      return MyFunction.super.compose(before);
+    }
+  }
+
+  static class C extends B<Integer, Integer> {
+
+    @Override
+    public Integer apply(Integer integer) {
+      return integer * 2;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
index 45a3be4..71e7df5 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
@@ -47,28 +47,6 @@
   }
 
   @Test
-  public void testPropagationFromFeature() throws Exception {
-    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
-        r8TestCompileResult -> {
-          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
-          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
-          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
-        };
-    ProcessResult processResult =
-        testDexSplitter(
-            parameters,
-            ImmutableSet.of(BaseSuperClass.class),
-            ImmutableSet.of(FeatureClass.class),
-            FeatureClass.class,
-            EXPECTED,
-            ensureGetFromFeatureGone,
-            TestShrinkerBuilder::noMinification);
-    // We expect art to fail on this with the dex splitter, see b/122902374
-    assertNotEquals(processResult.exitCode, 0);
-    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
-  }
-
-  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 96220f8..53af2b6 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -48,31 +48,6 @@
   }
 
   @Test
-  public void testInliningFromFeature() throws Exception {
-    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
-        r8TestCompileResult -> {
-          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
-          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
-          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
-        };
-    Consumer<R8FullTestBuilder> configurator =
-        r8FullTestBuilder ->
-            r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
-    ProcessResult processResult =
-        testDexSplitter(
-            parameters,
-            ImmutableSet.of(BaseSuperClass.class),
-            ImmutableSet.of(FeatureClass.class),
-            FeatureClass.class,
-            EXPECTED,
-            ensureGetFromFeatureGone,
-            configurator);
-    // We expect art to fail on this with the dex splitter, see b/122902374
-    assertNotEquals(processResult.exitCode, 0);
-    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
-  }
-
-  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ThrowableConsumer<R8FullTestBuilder> configurator =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
index 38d19da..a2e1b1d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -46,28 +46,6 @@
   }
 
   @Test
-  public void testPropagationFromFeature() throws Exception {
-    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
-        r8TestCompileResult -> {
-          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
-          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
-          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
-        };
-    ProcessResult processResult =
-        testDexSplitter(
-            parameters,
-            ImmutableSet.of(BaseSuperClass.class),
-            ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
-            FeatureClass.class,
-            EXPECTED,
-            ensureGetFromFeatureGone,
-            builder -> builder.enableInliningAnnotations().noMinification());
-    // We expect art to fail on this with the dex splitter, see b/122902374
-    assertNotEquals(processResult.exitCode, 0);
-    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
-  }
-
-  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 71ddab8..a7d137f 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -48,43 +48,6 @@
   }
 
   @Test
-  public void testInliningFromFeature() throws Exception {
-    // Static merging is based on sorting order, we assert that we merged to the feature.
-    ThrowingConsumer<R8TestCompileResult, Exception> ensureMergingToFeature =
-        r8TestCompileResult -> {
-          ClassSubject clazz = r8TestCompileResult.inspector().clazz(AFeatureWithStatic.class);
-          assertEquals(2, clazz.allMethods().size());
-          assertThat(clazz.uniqueMethodWithName("getBase42"), isPresent());
-          assertThat(clazz.uniqueMethodWithName("getFoobar"), isPresent());
-        };
-    Consumer<R8FullTestBuilder> configurator =
-        r8FullTestBuilder ->
-            r8FullTestBuilder
-                .addOptionsModification(
-                    options ->
-                        options.testing.horizontalClassMergingTarget =
-                            (appView, candidates, target) -> candidates.iterator().next())
-                .addHorizontallyMergedClassesInspector(
-                    inspector ->
-                        inspector.assertMergedInto(BaseWithStatic.class, AFeatureWithStatic.class))
-                .enableNoVerticalClassMergingAnnotations()
-                .enableInliningAnnotations()
-                .noMinification();
-    ProcessResult processResult =
-        testDexSplitter(
-            parameters,
-            ImmutableSet.of(BaseClass.class, BaseWithStatic.class),
-            ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class),
-            FeatureClass.class,
-            EXPECTED,
-            ensureMergingToFeature,
-            configurator);
-    // We expect art to fail on this with the dex splitter.
-    assertNotEquals(processResult.exitCode, 0);
-    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
-  }
-
-  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ThrowableConsumer<R8FullTestBuilder> configurator =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
deleted file mode 100644
index e90e0ab..0000000
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ /dev/null
@@ -1,484 +0,0 @@
-// Copyright (c) 2017, 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.dexsplitter;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.DexSplitterHelper;
-import com.android.tools.r8.ExtractMarker;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
-import com.android.tools.r8.dex.Marker;
-import com.android.tools.r8.dexsplitter.DexSplitter.Options;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-public class DexSplitterTests {
-
-  private static final String CLASS_DIR =
-      ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR + "classes/dexsplitsample";
-  private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
-  private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
-  private static final String CLASS3_CLASS = CLASS_DIR + "/Class3.class";
-  private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class";
-  private static final String CLASS3_SYNTHETIC_CLASS = CLASS_DIR + "/Class3$1.class";
-  private static final String CLASS4_CLASS = CLASS_DIR + "/Class4.class";
-  private static final String CLASS4_LAMBDA_INTERFACE = CLASS_DIR + "/Class4$LambdaInterface.class";
-  private static final String TEXT_FILE =
-      ToolHelper.EXAMPLES_ANDROID_N_DIR + "dexsplitsample/TextFile.txt";
-
-
-  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
-  private Path createInput(boolean dontCreateMarkerInD8)
-      throws IOException, CompilationFailedException {
-    // Initial normal compile to create dex files.
-    Path inputZip = temp.newFolder().toPath().resolve("input.zip");
-    D8Command command =
-        D8Command.builder()
-            .setOutput(inputZip, OutputMode.DexIndexed)
-            .addProgramFiles(Paths.get(CLASS1_CLASS))
-            .addProgramFiles(Paths.get(CLASS2_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_SYNTHETIC_CLASS))
-            .addProgramFiles(Paths.get(CLASS4_CLASS))
-            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
-            .build();
-
-    DexSplitterHelper.runD8ForTesting(command, dontCreateMarkerInD8);
-
-    return inputZip;
-  }
-
-  private void testMarker(boolean addMarkerToInput)
-      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
-    Path inputZip = createInput(!addMarkerToInput);
-
-    Path output = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(output);
-    Path splitSpec = createSplitSpec();
-
-    DexSplitter.main(
-        new String[] {
-          "--input", inputZip.toString(),
-          "--output", output.toString(),
-          "--feature-splits", splitSpec.toString()
-        });
-
-    Path base = output.resolve("base").resolve("classes.dex");
-    Path feature = output.resolve("feature1").resolve("classes.dex");
-
-    for (Path path : new Path[] {inputZip, base, feature}) {
-      Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(path);
-      assertEquals(addMarkerToInput ? 1 : 0, markers.size());
-    }
-  }
-
-  @Test
-  public void testMarkerPreserved()
-      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
-    testMarker(true);
-  }
-
-  @Test
-  public void testMarkerNotAdded()
-      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
-    testMarker(false);
-  }
-
-  /**
-   * To test the file splitting we have 3 classes that we distribute like this: Class1 -> base
-   * Class2 -> feature1 Class3 -> feature1
-   *
-   * <p>Class1 and Class2 works independently of each other, but Class3 extends Class1, and
-   * therefore can't run without the base being loaded.
-   */
-  @Test
-  public void splitFilesNoObfuscation()
-      throws CompilationFailedException, IOException, FeatureMappingException {
-    noObfuscation(false);
-    noObfuscation(true);
-  }
-
-  private void noObfuscation(boolean useOptions)
-      throws IOException, CompilationFailedException, FeatureMappingException {
-    Path inputZip = createInput(false);
-    Path output = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(output);
-    Path splitSpec = createSplitSpec();
-
-    if (useOptions) {
-      Options options = new Options();
-      options.addInputArchive(inputZip.toString());
-      options.setFeatureSplitMapping(splitSpec.toString());
-      options.setOutput(output.toString());
-      DexSplitter.run(options);
-    } else {
-      DexSplitter.main(
-          new String[] {
-              "--input", inputZip.toString(),
-              "--output", output.toString(),
-              "--feature-splits", splitSpec.toString()
-          });
-    }
-
-    Path base = output.resolve("base").resolve("classes.dex");
-    Path feature = output.resolve("feature1").resolve("classes.dex");
-    validateUnobfuscatedOutput(base, feature);
-  }
-
-  private void validateUnobfuscatedOutput(Path base, Path feature) throws IOException {
-    // Both classes should still work if we give all dex files to the system.
-    for (String className : new String[] {"Class1", "Class2", "Class3"}) {
-      ArtCommandBuilder builder = new ArtCommandBuilder();
-      builder.appendClasspath(base.toString());
-      builder.appendClasspath(feature.toString());
-      builder.setMainClass("dexsplitsample." + className);
-      String out = ToolHelper.runArt(builder);
-      assertEquals(out, className + "\n");
-    }
-    // Individual classes should also work from the individual files.
-    String className = "Class1";
-    ArtCommandBuilder builder = new ArtCommandBuilder();
-    builder.appendClasspath(base.toString());
-    builder.setMainClass("dexsplitsample." + className);
-    String out = ToolHelper.runArt(builder);
-    assertEquals(out, className + "\n");
-
-    className = "Class2";
-    builder = new ArtCommandBuilder();
-    builder.appendClasspath(feature.toString());
-    builder.setMainClass("dexsplitsample." + className);
-    out = ToolHelper.runArt(builder);
-    assertEquals(out, className + "\n");
-
-    className = "Class3";
-    builder = new ArtCommandBuilder();
-    builder.appendClasspath(feature.toString());
-    builder.setMainClass("dexsplitsample." + className);
-    try {
-      ToolHelper.runArt(builder);
-      assertFalse(true);
-    } catch (AssertionError assertionError) {
-      // We expect this to throw since base is not in the path and Class3 depends on Class1
-    }
-
-    className = "Class4";
-    builder = new ArtCommandBuilder();
-    builder.appendClasspath(feature.toString());
-    builder.setMainClass("dexsplitsample." + className);
-    try {
-      ToolHelper.runArt(builder);
-      assertFalse(true);
-    } catch (AssertionError assertionError) {
-      // We expect this to throw since base is not in the path and Class4 includes a lambda that
-      // would have been pushed to base.
-    }
-  }
-
-  private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
-    Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
-    try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
-      out.write(
-          "dexsplitsample.Class1:base\n"
-              + "dexsplitsample.Class2:feature1\n"
-              + "dexsplitsample.Class3:feature1\n"
-              + "dexsplitsample.Class4:feature1");
-    }
-    return splitSpec;
-  }
-
-  private List<String> getProguardConf() {
-    return ImmutableList.of(
-        "-keep class dexsplitsample.Class3 {",
-        "  public static void main(java.lang.String[]);",
-        "}");
-  }
-
-  @Test
-  public void splitFilesFromJar()
-      throws IOException, CompilationFailedException, FeatureMappingException {
-    for (boolean useOptions : new boolean[]{false, true}) {
-      for (boolean explicitBase: new boolean[]{false, true}) {
-        for (boolean renameBase: new boolean[]{false, true}) {
-          splitFromJars(useOptions, explicitBase, renameBase);
-        }
-      }
-    }
-  }
-
-  private void splitFromJars(boolean useOptions, boolean explicitBase, boolean renameBase)
-      throws IOException, CompilationFailedException, FeatureMappingException {
-    Path inputZip = createInput(false);
-    Path output = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(output);
-    Path baseJar = temp.getRoot().toPath().resolve("base.jar");
-    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
-    ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
-    String name = "dexsplitsample/Class1.class";
-    baseStream.putNextEntry(new ZipEntry(name));
-    baseStream.write(Files.readAllBytes(Paths.get(CLASS1_CLASS)));
-    baseStream.closeEntry();
-    baseStream.close();
-
-    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
-    name = "dexsplitsample/Class2.class";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS2_CLASS)));
-    featureStream.closeEntry();
-    name = "dexsplitsample/Class3.class";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_CLASS)));
-    featureStream.closeEntry();
-    name = "dexsplitsample/Class3$InnerClass.class";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_INNER_CLASS)));
-    featureStream.closeEntry();
-    name = "dexsplitsample/Class3$1.class";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_SYNTHETIC_CLASS)));
-    featureStream.closeEntry();
-    name = "dexsplitsample/Class4";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS4_CLASS)));
-    featureStream.closeEntry();
-    name = "dexsplitsample/Class4$LambdaInterface";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(Files.readAllBytes(Paths.get(CLASS4_LAMBDA_INTERFACE)));
-    featureStream.closeEntry();
-    featureStream.close();
-    // Make sure that we can pass in a name for the output.
-    String specificOutputName = "renamed";
-    if (useOptions) {
-      Options options = new Options();
-      options.addInputArchive(inputZip.toString());
-      options.setOutput(output.toString());
-      if (explicitBase) {
-        options.addBaseJar(baseJar.toString());
-      } else if (renameBase){
-        // Ensure that we can rename base (if people called a feature base)
-        options.setBaseOutputName("base_renamed");
-      }
-      options.addFeatureJar(featureJar.toString(), specificOutputName);
-      DexSplitter.run(options);
-    } else {
-      List<String> args = Lists.newArrayList(
-          "--input",
-          inputZip.toString(),
-          "--output",
-          output.toString(),
-          "--feature-jar",
-          featureJar.toString().concat(":").concat(specificOutputName));
-      if (explicitBase) {
-        args.add("--base-jar");
-        args.add(baseJar.toString());
-      } else if (renameBase) {
-        args.add("--base-output-name");
-        args.add("base_renamed");
-      }
-
-      DexSplitter.main(args.toArray(StringUtils.EMPTY_ARRAY));
-    }
-    String baseOutputName = explicitBase || !renameBase ? "base" : "base_renamed";
-    Path base = output.resolve(baseOutputName).resolve("classes.dex");
-    Path feature = output.resolve(specificOutputName).resolve("classes.dex");;
-    validateUnobfuscatedOutput(base, feature);
-  }
-
-  @Test
-  public void splitFilesObfuscation()
-      throws CompilationFailedException, IOException, ExecutionException {
-    // Initial normal compile to create dex files.
-    Path inputDex = temp.newFolder().toPath().resolve("input.zip");
-    Path proguardMap = temp.getRoot().toPath().resolve("proguard.map");
-
-    R8.run(
-        R8Command.builder()
-            .setOutput(inputDex, OutputMode.DexIndexed)
-            .addProgramFiles(Paths.get(CLASS1_CLASS))
-            .addProgramFiles(Paths.get(CLASS2_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
-            .addProgramFiles(Paths.get(CLASS3_SYNTHETIC_CLASS))
-            .addProgramFiles(Paths.get(CLASS4_CLASS))
-            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
-            .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-            .setProguardMapOutputPath(proguardMap)
-            .addProguardConfiguration(getProguardConf(), Origin.unknown())
-            .build());
-
-    Path outputDex = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(outputDex);
-    Path splitSpec = createSplitSpec();
-
-    DexSplitter.main(
-        new String[] {
-          "--input", inputDex.toString(),
-          "--output", outputDex.toString(),
-          "--feature-splits", splitSpec.toString(),
-          "--proguard-map", proguardMap.toString()
-        });
-
-    Path base = outputDex.resolve("base").resolve("classes.dex");
-    Path feature = outputDex.resolve("feature1").resolve("classes.dex");
-    String class3 = "dexsplitsample.Class3";
-    // We should still be able to run the Class3 which we kept, it has a call to the obfuscated
-    // class1 which is in base.
-    ArtCommandBuilder builder = new ArtCommandBuilder();
-    builder.appendClasspath(base.toString());
-    builder.appendClasspath(feature.toString());
-    builder.setMainClass(class3);
-    String out = ToolHelper.runArt(builder);
-    assertEquals(out, "Class3\n");
-
-    // Class1 should not be in the feature, it should still be in base.
-    builder = new ArtCommandBuilder();
-    builder.appendClasspath(feature.toString());
-    builder.setMainClass(class3);
-    try {
-      ToolHelper.runArt(builder);
-      assertFalse(true);
-    } catch (AssertionError assertionError) {
-      // We expect this to throw since base is not in the path and Class3 depends on Class1.
-    }
-
-    // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
-    // shaken away.
-    CodeInspector inspector = new CodeInspector(base, proguardMap);
-    ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
-    assertTrue(subject.isPresent());
-    assertTrue(subject.isRenamed());
-  }
-
-  @Test
-  public void splitNonClassFiles()
-      throws CompilationFailedException, IOException, FeatureMappingException {
-    Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
-    ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
-    String name = "dexsplitsample/TextFile.txt";
-    inputZipStream.putNextEntry(new ZipEntry(name));
-    byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
-    inputZipStream.write(fileBytes);
-    inputZipStream.closeEntry();
-    name = "dexsplitsample/TextFile2.txt";
-    inputZipStream.putNextEntry(new ZipEntry(name));
-    inputZipStream.write(fileBytes);
-    inputZipStream.write(fileBytes);
-    inputZipStream.closeEntry();
-    inputZipStream.close();
-    Path output = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(output);
-    Path baseJar = temp.getRoot().toPath().resolve("base.jar");
-    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
-    ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
-    name = "dexsplitsample/TextFile.txt";
-    baseStream.putNextEntry(new ZipEntry(name));
-    baseStream.write(fileBytes);
-    baseStream.closeEntry();
-    baseStream.close();
-    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
-    name = "dexsplitsample/TextFile2.txt";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(fileBytes);
-    featureStream.write(fileBytes);
-    featureStream.closeEntry();
-    featureStream.close();
-    Options options = new Options();
-    options.addInputArchive(inputZip.toString());
-    options.setOutput(output.toString());
-    options.addFeatureJar(baseJar.toString());
-    options.addFeatureJar(featureJar.toString());
-    options.setSplitNonClassResources(true);
-    DexSplitter.run(options);
-    Path baseDir = output.resolve("base");
-    Path featureDir = output.resolve("feature1");
-    byte[] contents = fileBytes;
-    byte[] contents2 = new byte[contents.length * 2];
-    System.arraycopy(contents, 0, contents2, 0, contents.length);
-    System.arraycopy(contents, 0, contents2, contents.length, contents.length);
-    Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
-    Path featureTextFile = featureDir.resolve("dexsplitsample/TextFile2.txt");
-    assert Files.exists(baseTextFile);
-    assert Files.exists(featureTextFile);
-    assert Arrays.equals(Files.readAllBytes(baseTextFile), contents);
-    assert Arrays.equals(Files.readAllBytes(featureTextFile), contents2);
-  }
-
-  @Test
-  public void splitDuplicateNonClassFiles()
-      throws IOException, CompilationFailedException, FeatureMappingException {
-    Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
-    ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
-    String name = "dexsplitsample/TextFile.txt";
-    inputZipStream.putNextEntry(new ZipEntry(name));
-    byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
-    inputZipStream.write(fileBytes);
-    inputZipStream.closeEntry();
-    inputZipStream.close();
-    Path output = temp.newFolder().toPath().resolve("output");
-    Files.createDirectory(output);
-    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
-    Path feature2Jar = temp.getRoot().toPath().resolve("feature2.jar");
-    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
-    name = "dexsplitsample/TextFile.txt";
-    featureStream.putNextEntry(new ZipEntry(name));
-    featureStream.write(fileBytes);
-    featureStream.closeEntry();
-    featureStream.close();
-    ZipOutputStream feature2Stream = new ZipOutputStream(Files.newOutputStream(feature2Jar));
-    name = "dexsplitsample/TextFile.txt";
-    feature2Stream.putNextEntry(new ZipEntry(name));
-    feature2Stream.write(fileBytes);
-    feature2Stream.closeEntry();
-    feature2Stream.close();
-    Options options = new Options();
-    options.addInputArchive(inputZip.toString());
-    options.setOutput(output.toString());
-    options.addFeatureJar(feature2Jar.toString());
-    options.addFeatureJar(featureJar.toString());
-    options.setSplitNonClassResources(true);
-    DexSplitter.run(options);
-    Path baseDir = output.resolve("base");
-    Path feature1Dir = output.resolve("feature1");
-    Path feature2Dir = output.resolve("feature2");
-    Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
-    Path feature1TextFile = feature1Dir.resolve("dexsplitsample/TextFile2.txt");
-    Path feature2TextFile = feature2Dir.resolve("dexsplitsample/TextFile2.txt");
-    assert !Files.exists(feature1TextFile);
-    assert !Files.exists(feature2TextFile);
-    assert Files.exists(baseTextFile);
-    assert Arrays.equals(Files.readAllBytes(baseTextFile), fileBytes);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 05d6d3d..bd7929d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -17,12 +17,9 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dexsplitter.DexSplitter.Options;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import dalvik.system.PathClassLoader;
 import java.io.IOException;
@@ -31,9 +28,7 @@
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.List;
 import java.util.Set;
-import java.util.function.Consumer;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
@@ -155,96 +150,6 @@
         toRun, baseOutput, r8TestCompileResult.getFeature(0), parameters.getRuntime());
   }
 
-  // Compile the passed in classes plus RunInterface and SplitRunner using R8, then split
-  // based on the base/feature sets. toRun must implement the BaseRunInterface
-  <E extends Throwable> ProcessResult testDexSplitter(
-      TestParameters parameters,
-      Set<Class<?>> baseClasses,
-      Set<Class<?>> featureClasses,
-      Class<?> toRun,
-      String expectedOutput,
-      ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
-      Consumer<R8FullTestBuilder> r8TestConfigurator)
-      throws Exception, E {
-    List<Class<?>> baseClassesWithRunner =
-        ImmutableList.<Class<?>>builder()
-            .add(RunInterface.class, SplitRunner.class)
-            .addAll(baseClasses)
-            .build();
-
-    Path baseJar = jarTestClasses(baseClassesWithRunner);
-    Path featureJar = jarTestClasses(featureClasses);
-
-    Path featureOnly =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(featureClasses)
-            .addClasspathClasses(baseClasses)
-            .addClasspathClasses(RunInterface.class)
-            .addKeepAllClassesRule()
-            .addInliningAnnotations()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .writeToZip();
-    if (parameters.isDexRuntime()) {
-      // With D8 this should just work. We compile all of the base classes, then run with the
-      // feature loaded at runtime. Since there is no inlining/class merging we don't
-      // have any issues.
-      testForD8()
-          .addProgramClasses(SplitRunner.class, RunInterface.class)
-          .addProgramClasses(baseClasses)
-          .setMinApi(parameters.getApiLevel())
-          .compile()
-          .run(
-              parameters.getRuntime(),
-              SplitRunner.class,
-              toRun.getName(),
-              featureOnly.toAbsolutePath().toString())
-          .assertSuccessWithOutput(expectedOutput);
-    }
-
-    R8FullTestBuilder builder = testForR8(parameters.getBackend());
-    if (parameters.isCfRuntime()) {
-      // Compiling to jar we need to support the same way of loading code at runtime as
-      // android supports.
-      builder
-          .addProgramClasses(PathClassLoader.class)
-          .addKeepClassAndMembersRules(PathClassLoader.class);
-    }
-
-    R8FullTestBuilder r8FullTestBuilder =
-        builder
-            .setMinApi(parameters.getApiLevel())
-            .addProgramClasses(SplitRunner.class, RunInterface.class)
-            .addProgramClasses(baseClasses)
-            .addProgramClasses(featureClasses)
-            .addKeepMainRule(SplitRunner.class)
-            .addKeepClassRules(toRun);
-    r8TestConfigurator.accept(r8FullTestBuilder);
-    R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
-    compileResultConsumer.accept(r8TestCompileResult);
-    Path fullFiles = r8TestCompileResult.writeToZip();
-
-    // Ensure that we can run the program as a unit (i.e., without splitting)
-    r8TestCompileResult
-        .run(parameters.getRuntime(), SplitRunner.class, toRun.getName())
-        .assertSuccessWithOutput(expectedOutput);
-
-    Path splitterOutput = temp.newFolder().toPath();
-    Path splitterBaseDexFile = splitterOutput.resolve("base").resolve("classes.dex");
-    Path splitterFeatureDexFile = splitterOutput.resolve("feature").resolve("classes.dex");
-
-    Options options = new Options();
-    options.setOutput(splitterOutput.toString());
-    options.addBaseJar(baseJar.toString());
-    options.addFeatureJar(featureJar.toString(), "feature");
-
-    options.addInputArchive(fullFiles.toString());
-    DexSplitter.run(options);
-
-    return runFeatureOnArt(
-        toRun, splitterBaseDexFile, splitterFeatureDexFile, parameters.getRuntime());
-  }
-
   ProcessResult runFeatureOnArt(
       Class toRun, Path splitterBaseDexFile, Path splitterFeatureDexFile, TestRuntime runtime)
       throws IOException {
diff --git a/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java b/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
index 5437118..05824cc 100644
--- a/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/dump/DumpInputsTest.java
@@ -105,6 +105,7 @@
     Path unzipped = temp.newFolder().toPath();
     ZipUtils.unzip(dumpFile.toString(), unzipped.toFile());
     assertTrue(Files.exists(unzipped.resolve("r8-version")));
+    assertTrue(Files.exists(unzipped.resolve("build.properties")));
     assertTrue(Files.exists(unzipped.resolve("program.jar")));
     assertTrue(Files.exists(unzipped.resolve("library.jar")));
     if (hasClasspath) {
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
deleted file mode 100644
index 9ae3939..0000000
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.internal;
-
-import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
-import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Paths;
-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 YouTubeV1533TreeShakeJarVerificationTest extends YouTubeCompilationTestBase {
-
-  private static final boolean DUMP = false;
-  private static final int MAX_SIZE = 27500000;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
-  }
-
-  public YouTubeV1533TreeShakeJarVerificationTest(TestParameters parameters) {
-    super(15, 33, AndroidApiLevel.H_MR2);
-    parameters.assertNoneRuntime();
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    // TODO(b/141603168): Enable this on the bots.
-    assumeTrue(isLocalDevelopment());
-    assumeTrue(shouldRunSlowTests());
-
-    LibrarySanitizer librarySanitizer =
-        new LibrarySanitizer(temp)
-            .addProgramFiles(getProgramFiles())
-            .addLibraryFiles(ImmutableList.of(getLibraryFile()))
-            .sanitize()
-            .assertSanitizedProguardConfigurationIsEmpty();
-
-    R8TestCompileResult compileResult =
-        testForR8(Backend.DEX)
-            .addProgramFiles(getProgramFiles())
-            .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
-            .addKeepRuleFiles(getKeepRuleFiles())
-            .addMainDexRuleFiles(getMainDexRuleFiles())
-            .addIgnoreWarnings()
-            .allowDiagnosticMessages()
-            .allowUnusedDontWarnPatterns()
-            .allowUnusedProguardConfigurationRules()
-            .setMinApi(getApiLevel())
-            .compile()
-            .apply(this::printProtoStats);
-
-    if (isLocalDevelopment()) {
-      if (DUMP) {
-        long time = System.currentTimeMillis();
-        compileResult.writeToZip(Paths.get("YouTubeV1533-" + time + ".zip"));
-        compileResult.writeProguardMap(Paths.get("YouTubeV1533-" + time + ".map"));
-      }
-    }
-
-    int applicationSize = compileResult.app.applicationSize();
-    System.out.println(applicationSize);
-
-    assertTrue(
-        "Expected max size of " + MAX_SIZE + ", got " + applicationSize,
-        applicationSize < MAX_SIZE);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 5d54843..2eb6a7f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -25,8 +25,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.dexsplitter.DexSplitter;
-import com.android.tools.r8.dexsplitter.DexSplitter.Options;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
@@ -255,49 +253,6 @@
   }
 
   @Test
-  public void everyThirdClassInMainWithDexSplitter() throws Throwable {
-    List<String> featureMappings = new ArrayList<>();
-    List<String> inFeatureMapping = new ArrayList<>();
-
-    ImmutableList.Builder<String> mainDexBuilder = ImmutableList.builder();
-    for (int i = 0; i < MANY_CLASSES.size(); i++) {
-      String clazz = MANY_CLASSES.get(i);
-      // Write the first 2 classes into the split.
-      if (i < 10) {
-        featureMappings.add(clazz + ":feature1");
-        inFeatureMapping.add(clazz);
-      }
-      if (i % 3 == 0) {
-        mainDexBuilder.add(clazz);
-      }
-    }
-    Path featureSplitMapping = temp.getRoot().toPath().resolve("splitmapping");
-    Path mainDexFile = temp.getRoot().toPath().resolve("maindex");
-    FileUtils.writeTextFile(featureSplitMapping, featureMappings);
-    List<String> mainDexList = mainDexBuilder.build();
-    FileUtils.writeTextFile(mainDexFile, ListUtils.map(mainDexList, MainDexListTests::typeToEntry));
-    Path output = temp.getRoot().toPath().resolve("split_output");
-    Files.createDirectories(output);
-    TestDiagnosticsHandler diagnosticsHandler = new TestDiagnosticsHandler();
-    Options options = new Options(diagnosticsHandler);
-    options.addInputArchive(getManyClassesMultiDexAppPath().toString());
-    options.setFeatureSplitMapping(featureSplitMapping.toString());
-    options.setOutput(output.toString());
-    options.setMainDexList(mainDexFile.toString());
-    DexSplitter.run(options);
-    assertEquals(0, diagnosticsHandler.numberOfErrorsAndWarnings());
-    Path baseDir = output.resolve("base");
-    CodeInspector inspector =
-        new CodeInspector(
-            AndroidApp.builder().addProgramFiles(baseDir.resolve("classes.dex")).build());
-    for (String clazz : mainDexList) {
-      if (!inspector.clazz(clazz).isPresent() && !inFeatureMapping.contains(clazz)) {
-        failedToFindClassInExpectedFile(baseDir, clazz);
-      }
-    }
-  }
-
-  @Test
   public void singleClassInMainDex() throws Throwable {
     ImmutableList<String> mainDex = ImmutableList.of(MANY_CLASSES.get(0));
     verifyMainDexContains(mainDex, getManyClassesSingleDexAppPath(), true);
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 a6dbbb7..4419055 100644
--- a/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
+++ b/src/test/java/com/android/tools/r8/startup/MinimalStartupDexTest.java
@@ -12,13 +12,13 @@
 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.graph.DexType;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Lists;
-import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -48,21 +48,27 @@
             options ->
                 options
                     .getStartupOptions()
-                    .setEnableMinimalStartupDex()
+                    .setEnableMinimalStartupDex(true)
                     .setEnableStartupCompletenessCheckForTesting()
                     .setStartupConfiguration(
-                        new StartupConfiguration(
-                            Lists.newArrayList(
-                                toDexType(Main.class, options.dexItemFactory()),
-                                toDexType(StartupClass.class, options.dexItemFactory())),
-                            Collections.emptyList())))
+                        StartupConfiguration.builder()
+                            .addStartupClass(
+                                StartupClass.<DexType>builder()
+                                    .setReference(toDexType(Main.class, options.dexItemFactory()))
+                                    .build())
+                            .addStartupClass(
+                                StartupClass.<DexType>builder()
+                                    .setReference(
+                                        toDexType(AStartupClass.class, options.dexItemFactory()))
+                                    .build())
+                            .build()))
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspectMultiDex(
             primaryDexInspector -> {
               // StartupClass should be in the primary dex.
-              ClassSubject startupClassSubject = primaryDexInspector.clazz(StartupClass.class);
+              ClassSubject startupClassSubject = primaryDexInspector.clazz(AStartupClass.class);
               assertThat(startupClassSubject, isPresent());
 
               MethodSubject startupMethodSubject = startupClassSubject.uniqueMethodWithName("foo");
@@ -99,7 +105,7 @@
   static class Main {
 
     public static void main(String[] args) {
-      StartupClass.foo();
+      AStartupClass.foo();
     }
 
     // @Keep
@@ -108,7 +114,7 @@
     }
   }
 
-  static class StartupClass {
+  static class AStartupClass {
 
     @NeverInline
     static void foo() {
diff --git a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
index 8a73ac4..6364cb0 100644
--- a/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
+++ b/src/test/java/com/android/tools/r8/startup/StartupInstrumentationTest.java
@@ -57,16 +57,13 @@
   }
 
   private static List<String> getExpectedOutput() {
-    return ImmutableList.of(
-        "Lcom/android/tools/r8/startup/StartupInstrumentationTest$Main;",
-        "Lcom/android/tools/r8/startup/StartupInstrumentationTest$StartupClass;",
-        "foo");
+    return ImmutableList.of(descriptor(Main.class), descriptor(AStartupClass.class), "foo");
   }
 
   static class Main {
 
     public static void main(String[] args) {
-      StartupClass.foo();
+      AStartupClass.foo();
     }
 
     // @Keep
@@ -75,7 +72,7 @@
     }
   }
 
-  static class StartupClass {
+  static class AStartupClass {
 
     @NeverInline
     static void foo() {
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
new file mode 100644
index 0000000..884c1bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticPlacementTest.java
@@ -0,0 +1,212 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.startup;
+
+import static com.android.tools.r8.startup.utils.StartupTestingMatchers.isEqualToClassDataLayout;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.utils.MixedSectionLayoutInspector;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StartupSyntheticPlacementTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean enableMinimalStartupDex;
+
+  @Parameter(2)
+  public boolean useLambda;
+
+  @Parameters(name = "{0}, minimal startup dex: {1}, use lambda: {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());
+  }
+
+  @Test
+  public void test() throws Exception {
+    List<StartupClass<ClassReference>> startupList = new ArrayList<>();
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .apply(StartupTestingUtils.enableStartupInstrumentation(parameters))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
+        .run(parameters.getRuntime(), Main.class, Boolean.toString(useLambda))
+        .apply(StartupTestingUtils.removeStartupClassesFromStdout(startupList::add))
+        .assertSuccessWithOutputLines(getExpectedOutput());
+    assertEquals(getExpectedStartupList(), startupList);
+
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(A.class, B.class, C.class)
+        .addOptionsModification(
+            options -> {
+              options.getStartupOptions().setEnableMinimalStartupDex(enableMinimalStartupDex);
+              options
+                  .getTestingOptions()
+                  .setMixedSectionLayoutStrategyInspector(getMixedSectionLayoutInspector());
+            })
+        .apply(testBuilder -> StartupTestingUtils.setStartupConfiguration(testBuilder, startupList))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectMultiDex(this::inspectPrimaryDex, this::inspectSecondaryDex)
+        .run(parameters.getRuntime(), Main.class, Boolean.toString(useLambda))
+        .assertSuccessWithOutputLines(getExpectedOutput());
+  }
+
+  private List<String> getExpectedOutput() {
+    return ImmutableList.of("A", "B", "C");
+  }
+
+  private List<StartupClass<ClassReference>> getExpectedStartupList() {
+    ImmutableList.Builder<StartupClass<ClassReference>> builder = ImmutableList.builder();
+    builder.add(
+        StartupClass.<ClassReference>builder()
+            .setReference(Reference.classFromClass(Main.class))
+            .build());
+    builder.add(
+        StartupClass.<ClassReference>builder()
+            .setReference(Reference.classFromClass(A.class))
+            .build());
+    builder.add(
+        StartupClass.<ClassReference>builder()
+            .setReference(Reference.classFromClass(B.class))
+            .build());
+    if (useLambda) {
+      builder.add(
+          StartupClass.<ClassReference>builder()
+              .setReference(Reference.classFromClass(B.class))
+              .setSynthetic()
+              .build());
+    }
+    builder.add(
+        StartupClass.<ClassReference>builder()
+            .setReference(Reference.classFromClass(C.class))
+            .build());
+    return builder.build();
+  }
+
+  private List<ClassReference> getExpectedClassDataLayout(int virtualFile) {
+    // The synthetic lambda should only be placed alongside its synthetic context (B) if it is used.
+    // Otherwise, it should be last, or in the second dex file if compiling with minimal startup.
+    ImmutableList.Builder<ClassReference> layoutBuilder = ImmutableList.builder();
+    if (virtualFile == 0) {
+      layoutBuilder.add(
+          Reference.classFromClass(Main.class),
+          Reference.classFromClass(A.class),
+          Reference.classFromClass(B.class));
+      if (useLambda) {
+        layoutBuilder.add(getSyntheticLambdaClassReference());
+      }
+      layoutBuilder.add(Reference.classFromClass(C.class));
+    }
+    if (!useLambda) {
+      if (!enableMinimalStartupDex || virtualFile == 1) {
+        layoutBuilder.add(getSyntheticLambdaClassReference());
+      }
+    }
+    return layoutBuilder.build();
+  }
+
+  private MixedSectionLayoutInspector getMixedSectionLayoutInspector() {
+    return new MixedSectionLayoutInspector() {
+      @Override
+      public void inspectClassDataLayout(int virtualFile, Collection<DexProgramClass> layout) {
+        assertThat(layout, isEqualToClassDataLayout(getExpectedClassDataLayout(virtualFile)));
+      }
+    };
+  }
+
+  private void inspectPrimaryDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(Main.class), isPresent());
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(B.class), isPresent());
+    assertThat(inspector.clazz(C.class), isPresent());
+    assertThat(
+        inspector.clazz(getSyntheticLambdaClassReference()),
+        notIf(isPresent(), enableMinimalStartupDex && !useLambda));
+  }
+
+  private void inspectSecondaryDex(CodeInspector inspector) {
+    if (enableMinimalStartupDex && !useLambda) {
+      assertEquals(1, inspector.allClasses().size());
+      assertThat(inspector.clazz(getSyntheticLambdaClassReference()), isPresent());
+    } else {
+      assertTrue(inspector.allClasses().isEmpty());
+    }
+  }
+
+  private static ClassReference getSyntheticLambdaClassReference() {
+    return SyntheticItemsTestUtils.syntheticLambdaClass(B.class, 0);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      boolean useLambda = args.length > 0 && args[0].equals("true");
+      A.a();
+      B.b(useLambda);
+      C.c();
+    }
+  }
+
+  static class A {
+
+    static void a() {
+      System.out.println("A");
+    }
+  }
+
+  static class B {
+
+    static void b(boolean useLambda) {
+      String message = System.currentTimeMillis() > 0 ? "B" : null;
+      if (useLambda) {
+        Consumer<Object> consumer = obj -> {};
+        consumer.accept(consumer);
+      }
+      System.out.println(message);
+    }
+  }
+
+  static class C {
+
+    static void c() {
+      System.out.println("C");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
new file mode 100644
index 0000000..18ed52b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/StartupSyntheticWithoutContextTest.java
@@ -0,0 +1,199 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.startup;
+
+import static com.android.tools.r8.startup.utils.StartupTestingMatchers.isEqualToClassDataLayout;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.experimental.startup.StartupClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.startup.utils.MixedSectionLayoutInspector;
+import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StartupSyntheticWithoutContextTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean enableMinimalStartupDex;
+
+  @Parameters(name = "{0}, minimal startup dex: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        // N so that java.util.function.Consumer is present.
+        getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.N).build(),
+        BooleanUtils.values());
+  }
+
+  @Test
+  public void test() throws Exception {
+    List<StartupClass<ClassReference>> startupList = new ArrayList<>();
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .apply(StartupTestingUtils.enableStartupInstrumentation(parameters))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(StartupTestingUtils.getAndroidUtilLog(temp))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(StartupTestingUtils.removeStartupClassesFromStdout(startupList::add))
+        .assertSuccessWithOutputLines(getExpectedOutput());
+    assertEquals(getExpectedStartupList(), startupList);
+
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(A.class, C.class)
+        .addOptionsModification(
+            options -> {
+              options.getStartupOptions().setEnableMinimalStartupDex(enableMinimalStartupDex);
+              options
+                  .getTestingOptions()
+                  .setMixedSectionLayoutStrategyInspector(getMixedSectionLayoutInspector());
+            })
+        .apply(testBuilder -> StartupTestingUtils.setStartupConfiguration(testBuilder, startupList))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspectMultiDex(this::inspectPrimaryDex, this::inspectSecondaryDex)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(getExpectedOutput());
+  }
+
+  private List<String> getExpectedOutput() {
+    return ImmutableList.of("A", "B", "C");
+  }
+
+  private List<StartupClass<ClassReference>> getExpectedStartupList() {
+    ImmutableList.Builder<StartupClass<ClassReference>> builder = ImmutableList.builder();
+    builder.add(
+        StartupClass.<ClassReference>builder()
+            .setReference(Reference.classFromClass(Main.class))
+            .build());
+    builder.add(
+        StartupClass.<ClassReference>builder()
+            .setReference(Reference.classFromClass(A.class))
+            .build());
+    builder.add(
+        StartupClass.<ClassReference>builder()
+            .setReference(Reference.classFromClass(B.class))
+            .build());
+    builder.add(
+        StartupClass.<ClassReference>builder()
+            .setReference(Reference.classFromClass(B.class))
+            .setSynthetic()
+            .build());
+    builder.add(
+        StartupClass.<ClassReference>builder()
+            .setReference(Reference.classFromClass(C.class))
+            .build());
+    return builder.build();
+  }
+
+  private List<ClassReference> getExpectedClassDataLayout(int virtualFile) {
+    ImmutableList.Builder<ClassReference> builder = ImmutableList.builder();
+    if (virtualFile == 0) {
+      builder.add(
+          Reference.classFromClass(Main.class),
+          Reference.classFromClass(A.class),
+          getSyntheticLambdaClassReference(B.class),
+          Reference.classFromClass(C.class));
+    }
+    if (!enableMinimalStartupDex || virtualFile == 1) {
+      builder.add(getSyntheticLambdaClassReference(Main.class));
+    }
+    return builder.build();
+  }
+
+  private MixedSectionLayoutInspector getMixedSectionLayoutInspector() {
+    return new MixedSectionLayoutInspector() {
+      @Override
+      public void inspectClassDataLayout(int virtualFile, Collection<DexProgramClass> layout) {
+        assertThat(layout, isEqualToClassDataLayout(getExpectedClassDataLayout(virtualFile)));
+      }
+    };
+  }
+
+  private void inspectPrimaryDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(Main.class), isPresent());
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(B.class), isAbsent());
+    assertThat(inspector.clazz(C.class), isPresent());
+    assertThat(inspector.clazz(getSyntheticLambdaClassReference(B.class)), isPresent());
+    assertThat(
+        inspector.clazz(getSyntheticLambdaClassReference(Main.class)),
+        notIf(isPresent(), enableMinimalStartupDex));
+  }
+
+  private void inspectSecondaryDex(CodeInspector inspector) {
+    if (enableMinimalStartupDex) {
+      assertEquals(1, inspector.allClasses().size());
+      assertThat(inspector.clazz(getSyntheticLambdaClassReference(Main.class)), isPresent());
+    } else {
+      assertTrue(inspector.allClasses().isEmpty());
+    }
+  }
+
+  private static ClassReference getSyntheticLambdaClassReference(Class<?> synthesizingContext) {
+    return SyntheticItemsTestUtils.syntheticLambdaClass(synthesizingContext, 0);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      A.a();
+      Runnable r = System.currentTimeMillis() > 0 ? B.b() : Main::error;
+      r.run();
+      C.c();
+    }
+
+    static void error() {
+      throw new RuntimeException();
+    }
+  }
+
+  static class A {
+
+    static void a() {
+      System.out.println("A");
+    }
+  }
+
+  static class B {
+
+    static Runnable b() {
+      return () -> System.out.println("B");
+    }
+  }
+
+  static class C {
+
+    static void c() {
+      System.out.println("C");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/utils/MixedSectionLayoutInspector.java b/src/test/java/com/android/tools/r8/startup/utils/MixedSectionLayoutInspector.java
new file mode 100644
index 0000000..c2d5285
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/utils/MixedSectionLayoutInspector.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.startup.utils;
+
+import com.android.tools.r8.dex.MixedSectionLayoutStrategy;
+import com.android.tools.r8.dex.VirtualFile;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationDirectory;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedArray;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.Collection;
+import java.util.function.BiFunction;
+
+public abstract class MixedSectionLayoutInspector
+    implements BiFunction<MixedSectionLayoutStrategy, VirtualFile, MixedSectionLayoutStrategy> {
+
+  public void inspectAnnotationLayout(int virtualFile, Collection<DexAnnotation> layout) {
+    // Intentionally empty.
+  }
+
+  public void inspectAnnotationDirectoryLayout(
+      int virtualFile, Collection<DexAnnotationDirectory> layout) {
+    // Intentionally empty.
+  }
+
+  public void inspectAnnotationSetLayout(int virtualFile, Collection<DexAnnotationSet> layout) {
+    // Intentionally empty.
+  }
+
+  public void inspectAnnotationSetRefListLayout(
+      int virtualFile, Collection<ParameterAnnotationsList> layout) {
+    // Intentionally empty.
+  }
+
+  public void inspectClassDataLayout(int virtualFile, Collection<DexProgramClass> layout) {
+    // Intentionally empty.
+  }
+
+  public void inspectCodeLayout(int virtualFile, Collection<ProgramMethod> layout) {
+    // Intentionally empty.
+  }
+
+  public void inspectEncodedArrayLayout(int virtualFile, Collection<DexEncodedArray> layout) {
+    // Intentionally empty.
+  }
+
+  public void inspectStringDataLayout(int virtualFile, Collection<DexString> layout) {
+    // Intentionally empty.
+  }
+
+  public void inspectTypeListLayout(int virtualFile, Collection<DexTypeList> layout) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public MixedSectionLayoutStrategy apply(
+      MixedSectionLayoutStrategy mixedSectionLayoutStrategy, VirtualFile virtualFile) {
+    return new MixedSectionLayoutStrategy() {
+
+      @Override
+      public Collection<DexAnnotation> getAnnotationLayout() {
+        Collection<DexAnnotation> layout = mixedSectionLayoutStrategy.getAnnotationLayout();
+        inspectAnnotationLayout(virtualFile.getId(), layout);
+        return layout;
+      }
+
+      @Override
+      public Collection<DexAnnotationDirectory> getAnnotationDirectoryLayout() {
+        Collection<DexAnnotationDirectory> layout =
+            mixedSectionLayoutStrategy.getAnnotationDirectoryLayout();
+        inspectAnnotationDirectoryLayout(virtualFile.getId(), layout);
+        return layout;
+      }
+
+      @Override
+      public Collection<DexAnnotationSet> getAnnotationSetLayout() {
+        Collection<DexAnnotationSet> layout = mixedSectionLayoutStrategy.getAnnotationSetLayout();
+        inspectAnnotationSetLayout(virtualFile.getId(), layout);
+        return layout;
+      }
+
+      @Override
+      public Collection<ParameterAnnotationsList> getAnnotationSetRefListLayout() {
+        Collection<ParameterAnnotationsList> layout =
+            mixedSectionLayoutStrategy.getAnnotationSetRefListLayout();
+        inspectAnnotationSetRefListLayout(virtualFile.getId(), layout);
+        return layout;
+      }
+
+      @Override
+      public Collection<DexProgramClass> getClassDataLayout() {
+        Collection<DexProgramClass> layout = mixedSectionLayoutStrategy.getClassDataLayout();
+        inspectClassDataLayout(virtualFile.getId(), layout);
+        return layout;
+      }
+
+      @Override
+      public Collection<ProgramMethod> getCodeLayout() {
+        Collection<ProgramMethod> layout = mixedSectionLayoutStrategy.getCodeLayout();
+        inspectCodeLayout(virtualFile.getId(), layout);
+        return layout;
+      }
+
+      @Override
+      public Collection<DexEncodedArray> getEncodedArrayLayout() {
+        Collection<DexEncodedArray> layout = mixedSectionLayoutStrategy.getEncodedArrayLayout();
+        inspectEncodedArrayLayout(virtualFile.getId(), layout);
+        return layout;
+      }
+
+      @Override
+      public Collection<DexString> getStringDataLayout() {
+        Collection<DexString> layout = mixedSectionLayoutStrategy.getStringDataLayout();
+        inspectStringDataLayout(virtualFile.getId(), layout);
+        return layout;
+      }
+
+      @Override
+      public Collection<DexTypeList> getTypeListLayout() {
+        Collection<DexTypeList> layout = mixedSectionLayoutStrategy.getTypeListLayout();
+        inspectTypeListLayout(virtualFile.getId(), layout);
+        return layout;
+      }
+    };
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingMatchers.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingMatchers.java
new file mode 100644
index 0000000..618e644
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingMatchers.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.startup.utils;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Collection;
+import java.util.Iterator;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class StartupTestingMatchers {
+
+  public static Matcher<Collection<DexProgramClass>> isEqualToClassDataLayout(
+      Collection<ClassReference> expectedLayout) {
+    return new TypeSafeMatcher<Collection<DexProgramClass>>() {
+      @Override
+      protected boolean matchesSafely(Collection<DexProgramClass> actualLayout) {
+        if (actualLayout.size() != expectedLayout.size()) {
+          return false;
+        }
+        Iterator<ClassReference> expectedLayoutIterator = expectedLayout.iterator();
+        for (DexProgramClass clazz : actualLayout) {
+          if (!clazz.getClassReference().equals(expectedLayoutIterator.next())) {
+            return false;
+          }
+        }
+        return true;
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText(
+            "is equal to ["
+                + StringUtils.join(", ", expectedLayout, TypeReference::getTypeName)
+                + "]");
+      }
+
+      @Override
+      public void describeMismatchSafely(
+          Collection<DexProgramClass> actualLayout, Description description) {
+        description
+            .appendText("class data layout was not: ")
+            .appendText("[")
+            .appendText(StringUtils.join(", ", actualLayout, DexClass::getTypeName))
+            .appendText("]");
+      }
+    };
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
new file mode 100644
index 0000000..dad4d44
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.startup.utils;
+
+import static com.android.tools.r8.TestBase.transformer;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+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.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Consumer;
+import org.junit.rules.TemporaryFolder;
+
+public class StartupTestingUtils {
+
+  private static String startupInstrumentationTag = "startup";
+
+  public static ThrowableConsumer<D8TestBuilder> enableStartupInstrumentation(
+      TestParameters parameters) {
+    return testBuilder -> enableStartupInstrumentation(testBuilder, parameters);
+  }
+
+  public static void enableStartupInstrumentation(
+      D8TestBuilder testBuilder, TestParameters parameters) throws IOException {
+    testBuilder
+        .addOptionsModification(
+            options ->
+                options
+                    .getStartupOptions()
+                    .setEnableStartupInstrumentation()
+                    .setStartupInstrumentationTag(startupInstrumentationTag))
+        .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+        .addLibraryClassFileData(getTransformedAndroidUtilLog());
+  }
+
+  public static Path getAndroidUtilLog(TemporaryFolder temporaryFolder)
+      throws CompilationFailedException, IOException {
+    return TestBase.testForD8(temporaryFolder)
+        .addProgramClassFileData(getTransformedAndroidUtilLog())
+        .setMinApi(AndroidApiLevel.B)
+        .compile()
+        .writeToZip();
+  }
+
+  public static ThrowingConsumer<D8TestRunResult, RuntimeException> removeStartupClassesFromStdout(
+      Consumer<StartupClass<ClassReference>> startupClassConsumer) {
+    return runResult -> removeStartupClassesFromStdout(runResult, startupClassConsumer);
+  }
+
+  public static void removeStartupClassesFromStdout(
+      D8TestRunResult runResult, Consumer<StartupClass<ClassReference>> startupClassConsumer) {
+    StringBuilder stdoutBuilder = new StringBuilder();
+    String startupDescriptorPrefix = "[" + startupInstrumentationTag + "] ";
+    for (String line : StringUtils.splitLines(runResult.getStdOut(), true)) {
+      if (line.startsWith(startupDescriptorPrefix)) {
+        StartupClass.Builder<ClassReference> startupClassBuilder = StartupClass.builder();
+        String message = line.substring(startupDescriptorPrefix.length());
+        message = StartupConfiguration.parseSyntheticFlag(message, startupClassBuilder);
+        startupClassBuilder.setReference(Reference.classFromDescriptor(message));
+        startupClassConsumer.accept(startupClassBuilder.build());
+      } else {
+        stdoutBuilder.append(line).append(System.lineSeparator());
+      }
+    }
+    runResult.getResult().setStdout(stdoutBuilder.toString());
+  }
+
+  public static void setStartupConfiguration(
+      R8TestBuilder<?> testBuilder, List<StartupClass<ClassReference>> startupClasses) {
+    testBuilder.addOptionsModification(
+        options -> {
+          DexItemFactory dexItemFactory = options.dexItemFactory();
+          options
+              .getStartupOptions()
+              .setStartupConfiguration(
+                  StartupConfiguration.builder()
+                      .apply(
+                          builder ->
+                              startupClasses.forEach(
+                                  startupClass ->
+                                      builder.addStartupClass(
+                                          StartupClass.<DexType>builder()
+                                              .setFlags(startupClass.getFlags())
+                                              .setReference(
+                                                  dexItemFactory.createType(
+                                                      startupClass.getReference().getDescriptor()))
+                                              .build())))
+                      .build());
+        });
+  }
+
+  private static byte[] getTransformedAndroidUtilLog() throws IOException {
+    return transformer(Log.class).setClassDescriptor("Landroid/util/Log;").transform();
+  }
+
+  public static class Log {
+
+    public static int i(String tag, String msg) {
+      System.out.println("[" + tag + "] " + msg);
+      return 42;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
deleted file mode 100644
index f41be5c..0000000
--- a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertFalse;
-
-import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import org.junit.Test;
-
-public class FeatureClassMappingTest {
-
-  @Test
-  public void testSimpleParse() throws Exception {
-
-    List<String> lines =
-        ImmutableList.of(
-            "# Comment, don't care about contents: even more ::::",
-            "com.google.base:base",
-            "", // Empty lines allowed
-            "com.google.feature1:feature1",
-            "com.google.feature1:feature1", // Multiple definitions of the same predicate allowed.
-            "com.google$:feature1",
-            "_com.google:feature21",
-            "com.google.*:feature32");
-    FeatureClassMapping mapping = new FeatureClassMapping(lines);
-  }
-
-  private void ensureThrowsMappingException(List<String> lines) {
-    try {
-      new FeatureClassMapping(lines);
-      assertFalse(true);
-    } catch (FeatureMappingException e) {
-      // Expected
-    }
-  }
-
-  private void ensureThrowsMappingException(String string) {
-    ensureThrowsMappingException(ImmutableList.of(string));
-  }
-
-  @Test
-  public void testLookup() throws Exception {
-    List<String> lines =
-        ImmutableList.of(
-            "com.google.Base:base",
-            "",
-            "com.google.Feature1:feature1",
-            "com.google.Feature1:feature1", // Multiple definitions of the same predicate allowed.
-            "com.google.different.*:feature1",
-            "_com.Google:feature21",
-            "com.google.bas42.*:feature42");
-    FeatureClassMapping mapping = new FeatureClassMapping(lines);
-    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
-    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "feature1");
-    assertEquals(mapping.featureForClass("com.google.different.Foobar"), "feature1");
-    assertEquals(mapping.featureForClass("com.google.Base"), "base");
-    assertEquals(mapping.featureForClass("com.google.bas42.foo.bar.bar.Foo"), "feature42");
-    assertEquals(mapping.featureForClass("com.google.bas42.f$o$o$.bar43.bar.Foo"), "feature42");
-    assertEquals(mapping.featureForClass("_com.Google"), "feature21");
-  }
-
-  @Test
-  public void testCatchAllWildcards() throws Exception {
-    testBaseWildcard(true);
-    testBaseWildcard(false);
-    testNonBaseCatchAll();
-  }
-
-  private void testNonBaseCatchAll() throws FeatureMappingException {
-    List<String> lines =
-        ImmutableList.of(
-            "com.google.Feature1:feature1",
-            "*:nonbase",
-            "com.strange.*:feature2");
-    FeatureClassMapping mapping = new FeatureClassMapping(lines);
-    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
-    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "nonbase");
-    assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
-    assertEquals(mapping.featureForClass("Feature1"), "nonbase");
-    assertEquals(mapping.featureForClass("a.b.z.A"), "nonbase");
-  }
-
-  private void testBaseWildcard(boolean explicitBase) throws FeatureMappingException {
-    List<String> lines =
-        ImmutableList.of(
-            "com.google.Feature1:feature1",
-            explicitBase ? "*:base" : "",
-            "com.strange.*:feature2");
-    FeatureClassMapping mapping = new FeatureClassMapping(lines);
-    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
-    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "base");
-    assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
-    assertEquals(mapping.featureForClass("com.stranger.Clazz"), "base");
-    assertEquals(mapping.featureForClass("Feature1"), "base");
-    assertEquals(mapping.featureForClass("a.b.z.A"), "base");
-  }
-
-  @Test
-  public void testWrongLines() throws Exception {
-    // No colon.
-    ensureThrowsMappingException("foo");
-    ensureThrowsMappingException("com.google.base");
-    // Two colons.
-    ensureThrowsMappingException(ImmutableList.of("a:b:c"));
-
-    // Empty identifier.
-    ensureThrowsMappingException("com..google:feature1");
-
-    // Ambiguous redefinition
-    ensureThrowsMappingException(
-        ImmutableList.of("com.google.foo:feature1", "com.google.foo:feature2"));
-    ensureThrowsMappingException(
-        ImmutableList.of("com.google.foo.*:feature1", "com.google.foo.*:feature2"));
-  }
-
-  @Test
-  public void testUsesOnlyExactMappings() throws Exception {
-    List<String> lines =
-        ImmutableList.of(
-            "com.pkg1.Clazz:feature1",
-            "com.pkg2.Clazz:feature2");
-    FeatureClassMapping mapping = new FeatureClassMapping(lines);
-
-    assertEquals(mapping.featureForClass("com.pkg1.Clazz"), "feature1");
-    assertEquals(mapping.featureForClass("com.pkg2.Clazz"), "feature2");
-    assertEquals(mapping.featureForClass("com.pkg1.Other"), mapping.baseName);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 402ef93..efcf8a9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -132,6 +132,10 @@
             .read(app.getProguardMapOutputData()));
   }
 
+  public static CodeInspector empty() throws IOException {
+    return new CodeInspector(ImmutableList.of(), null, null);
+  }
+
   private static InternalOptions runOptionsConsumer(Consumer<InternalOptions> optionsConsumer) {
     InternalOptions internalOptions = new InternalOptions();
     if (optionsConsumer != null) {
diff --git a/third_party/internal-apps/youtube_15_33.tar.gz.sha1 b/third_party/internal-apps/youtube_15_33.tar.gz.sha1
deleted file mode 100644
index 9a04d6e..0000000
--- a/third_party/internal-apps/youtube_15_33.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-907babdaf04052eed13f22400175307131df1610
\ No newline at end of file
diff --git a/third_party/youtube-developer/20200415.tar.gz.sha1 b/third_party/youtube-developer/20200415.tar.gz.sha1
deleted file mode 100644
index f44d810..0000000
--- a/third_party/youtube-developer/20200415.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-bfc2082c67a28dc43c975ccc3b0e10d3d31cae5d
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_15.33.tar.gz.sha1 b/third_party/youtube/youtube.android_15.33.tar.gz.sha1
deleted file mode 100644
index 3ce1c4a..0000000
--- a/third_party/youtube/youtube.android_15.33.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-cdff350c62bf8c72d97be51590e90ad9105b5499
\ No newline at end of file
diff --git a/tools/dexsplitter.py b/tools/dexsplitter.py
deleted file mode 100755
index a97d089..0000000
--- a/tools/dexsplitter.py
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2018, 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.
-
-import sys
-import toolhelper
-
-if __name__ == '__main__':
-  sys.exit(toolhelper.run('dexsplitter', sys.argv[1:]))
diff --git a/tools/golem.py b/tools/golem.py
deleted file mode 100755
index 6a4e399..0000000
--- a/tools/golem.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2018, 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.
-
-# Utility methods to make running on our performance tracking system easier.
-import os
-import sys
-
-LINKED_THIRD_PARTY_DIRECTORIES = [
-    'android_jar',
-    'android_sdk',
-    'benchmarks',
-    'framework',
-    'gmail',
-    'gmscore',
-    'gradle',
-    'gradle-plugin',
-    'openjdk',
-    'proguard',
-    'proguardsettings',
-    'r8',
-    'remapper',
-    'retrace_benchmark',
-    'sample_libraries',
-    'youtube',
-]
-
-LINKED_TOOL_DIRECTORIES = [
-  'linux/dx',
-]
-
-# Path to our internally updated third party
-THIRD_PARTY_SOURCE = "/usr/local/google/home/golem/r8/third_party"
-TOOLS_SOURCE = "/usr/local/google/home/golem/r8/tools"
-
-def link_third_party():
-  assert os.path.exists('third_party')
-  for dir in LINKED_THIRD_PARTY_DIRECTORIES:
-    src = os.path.join(THIRD_PARTY_SOURCE, dir)
-    dest = os.path.join('third_party', dir)
-    if os.path.exists(dest):
-      raise Exception('Destination "{}" already exists, are you running with'
-                      ' --golem locally'.format(dest))
-    print('Symlinking {} to {}'.format(src, dest))
-    os.symlink(src, dest)
-  for dir in LINKED_TOOL_DIRECTORIES:
-    src = os.path.join(TOOLS_SOURCE, dir)
-    dest = os.path.join('tools', dir)
-    if os.path.exists(dest):
-      raise Exception('Destination "{}" already exists, are you running with'
-                      ' --golem locally'.format(dest))
-    print('Symlinking {} to {}'.format(src, dest))
-    if '/' in dir:
-      os.makedirs(os.path.dirname(dest))
-    os.symlink(src, dest)
-
-if __name__ == '__main__':
-  sys.exit(link_third_party())
diff --git a/tools/keeprule_benchmark.py b/tools/keeprule_benchmark.py
index 4b843c6..1401084 100755
--- a/tools/keeprule_benchmark.py
+++ b/tools/keeprule_benchmark.py
@@ -125,10 +125,6 @@
 def parse_arguments(argv):
   parser = argparse.ArgumentParser(
                     description = 'Run keep-rule benchmarks.')
-  parser.add_argument('--golem',
-                    help = 'Link in third party dependencies.',
-                    default = False,
-                    action = 'store_true')
   parser.add_argument('--ignore-java-version',
                     help='Do not check java version',
                     default=False,
@@ -239,8 +235,6 @@
 
 if __name__ == '__main__':
   options = parse_arguments(sys.argv[1:])
-  if options.golem:
-    golem.link_third_party()
   if not options.ignore_java_version:
     utils.check_java_version()
   with utils.TempDir() as temp:
diff --git a/tools/run_kotlin_benchmarks.py b/tools/run_kotlin_benchmarks.py
index ce0f1ad..86adbe2 100755
--- a/tools/run_kotlin_benchmarks.py
+++ b/tools/run_kotlin_benchmarks.py
@@ -5,7 +5,6 @@
 
 # Script for running kotlin based benchmarks
 
-import golem
 import optparse
 import os
 import subprocess
@@ -45,9 +44,6 @@
                     help='The benchmark to run',
                     default='rgx',
                     choices=['rgx', 'deltablue', 'sta', 'empty'])
-  result.add_option('--golem',
-                    help='Don\'t build r8 and link in third_party deps',
-                    default=False, action='store_true')
   result.add_option('--use-device',
                     help='Run the benchmark on an attaced device',
                     default=False, action='store_true')
@@ -85,8 +81,6 @@
 
 def Main():
   (options, args) = parse_options()
-  if options.golem:
-    golem.link_third_party()
   with utils.TempDir() as temp:
     dex_path = os.path.join(temp, "classes.jar")
     proguard_conf = os.path.join(temp, 'proguard.conf')
@@ -100,7 +94,7 @@
         '--min-api', str(options.api),
         benchmark_jar
     ]
-    toolhelper.run('r8', r8_args, build=not options.golem)
+    toolhelper.run('r8', r8_args, True)
     if options.use_device:
       result = run_art_device(dex_path)
     else:
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 8c72939..a0542d4 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -16,7 +16,6 @@
 import gradle
 import gmail_data
 import gmscore_data
-import golem
 import nest_data
 from sanitize_libraries import SanitizeLibraries, SanitizeLibrariesInPgconf
 import toolhelper
@@ -106,10 +105,6 @@
                     type='int',
                     default=0,
                     help='Set timeout instead of waiting for OOM.')
-  result.add_option('--golem',
-                    help='Running on golem, do not build or download',
-                    default=False,
-                    action='store_true')
   result.add_option('--ignore-java-version',
                     help='Do not check java version',
                     default=False,
@@ -138,8 +133,6 @@
                          'Same as --compiler-flags, keeping it for backward'
                          ' compatibility. ' +
                          'If passing several options use a quoted string.')
-  # TODO(tamaskenez) remove track-memory-to-file as soon as we updated golem
-  # to use --print-memoryuse instead
   result.add_option('--track-memory-to-file',
                     help='Track how much memory the jvm is using while ' +
                     ' compiling. Output to the specified file.')
@@ -447,7 +440,7 @@
           raise Exception("Unexpected -libraryjars found in " + pgconf)
 
 def should_build(options):
-  return not options.no_build and not options.golem
+  return not options.no_build
 
 def build_desugared_library_dex(
     options,
@@ -519,9 +512,6 @@
       extra_args.append('-Xmx%sM' % options.max_memory)
     else:
       extra_args.append('-Xmx8G')
-  if options.golem:
-    golem.link_third_party()
-    options.out = os.getcwd()
   if not options.ignore_java_version:
     utils.check_java_version()
 
@@ -743,9 +733,8 @@
   if options.print_dexsegments:
     dex_files = glob(os.path.join(outdir, '*.dex'))
     utils.print_dexsegments(options.print_dexsegments, dex_files)
-    if not options.golem:
-      print('{}-Total(CodeSize): {}'.format(
-              options.print_dexsegments, compute_size_of_dex_files(dex_files)))
+    print('{}-Total(CodeSize): {}'.format(
+            options.print_dexsegments, compute_size_of_dex_files(dex_files)))
   return 0
 
 def compute_size_of_dex_files(dex_files):
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index b047d72..4f22dc5 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -440,16 +440,6 @@
     'revision': '779cf9e187b8ee2c6b620b2abb4524719b3f10f8',
     'folder': 'android/compose-samples/rally',
   }),
-  App({
-    'id': 'youtube_15_33',
-    'name': 'youtube_15_33',
-    'dump_app': 'dump.zip',
-    'apk_app': 'YouTubeRelease_unsigned.apk',
-    'folder': 'youtube_15_33',
-    'internal': True,
-    # TODO(b/181629268): Fix recompilation
-    'skip_recompilation': True,
-  })
 ]
 
 
diff --git a/tools/test_gradle_benchmarks.py b/tools/test_gradle_benchmarks.py
index d9c00e6..7346830 100755
--- a/tools/test_gradle_benchmarks.py
+++ b/tools/test_gradle_benchmarks.py
@@ -6,7 +6,6 @@
 from __future__ import print_function
 import argparse
 import gradle
-import golem
 import os
 import subprocess
 import sys
@@ -20,9 +19,6 @@
     description='Run D8 or DX on gradle apps located in'
                 ' third_party/benchmarks/.'
                 ' Report Golem-compatible RunTimeRaw values.')
-  parser.add_argument('--golem',
-                      help = 'Running on golem, link in third_party resources.',
-                      default = False, action = 'store_true')
   parser.add_argument('--skip_download',
                     help='Don\'t automatically pull down dependencies.',
                     default=False, action='store_true')
@@ -124,7 +120,7 @@
 
   return any(namePattern in taskname for namePattern in acceptedGradleTasks)
 
-def PrintBuildTimeForGolem(benchmark, stdOut):
+def PrintBuildTime(benchmark, stdOut):
   for line in stdOut.splitlines():
     if 'BENCH' in line and benchmark.moduleName in line:
       commaSplit = line.split(',')
@@ -160,12 +156,6 @@
 
 def Main():
   args = parse_arguments()
-  if args.golem:
-    # Ensure that we don't have a running daemon
-    exitcode = subprocess.call(['pkill', 'java'])
-    assert exitcode == 0 or exitcode == 1
-    golem.link_third_party()
-
   if args.tool == 'd8':
     tool = Benchmark.Tools.D8
     desugarMode = Benchmark.DesugarMode.D8_DESUGARING
@@ -217,7 +207,7 @@
               ['clean']),
 
   ]
-  if not args.skip_download and not args.golem:
+  if not args.skip_download:
     EnsurePresence(os.path.join('third_party', 'benchmarks', 'android-sdk'),
                    'android SDK')
     EnsurePresence(os.path.join('third_party', 'gradle-plugin'),
@@ -232,7 +222,7 @@
       benchmark.EnsurePresence()
     benchmark.Clean()
     stdOut = benchmark.Build(tool, desugarMode)
-    PrintBuildTimeForGolem(benchmark, stdOut)
+    PrintBuildTime(benchmark, stdOut)
 
 
 if __name__ == '__main__':
diff --git a/tools/test_r8cfsegments.py b/tools/test_r8cfsegments.py
index be8e49c..2081ee2 100755
--- a/tools/test_r8cfsegments.py
+++ b/tools/test_r8cfsegments.py
@@ -22,7 +22,6 @@
 
 from __future__ import print_function
 import argparse
-import golem
 import minify_tool
 import os
 import sys
@@ -37,10 +36,6 @@
       choices = ['pg', 'r8'],
       required = True,
       help = 'Compiler tool to use.')
-  parser.add_argument('--golem',
-      help = 'Running on golem, link in third_party resources.',
-      default = False,
-      action = 'store_true')
   parser.add_argument('--name',
       required = True,
       help = 'Results will be printed using the specified benchmark name (e.g.'
@@ -59,8 +54,6 @@
 
 def Main():
   args = parse_arguments()
-  if args.golem:
-    golem.link_third_party()
   utils.check_java_version()
   output_dir = args.output
   with utils.TempDir() as temp_dir:
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index cd47f66..0dec031 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -5,15 +5,11 @@
 import os
 import utils
 
-ANDROID_H_MR2_API = '13'
 ANDROID_L_API = '21'
 ANDROID_M_API = '23'
 
 BASE = os.path.join(utils.THIRD_PARTY, 'youtube')
 
-V15_33_BASE = os.path.join(BASE, 'youtube.android_15.33')
-V15_33_PREFIX = os.path.join(V15_33_BASE, 'YouTubeRelease')
-
 V16_20_BASE = os.path.join(BASE, 'youtube.android_16.20')
 V16_20_PREFIX = os.path.join(V16_20_BASE, 'YouTubeRelease')
 
@@ -23,37 +19,6 @@
 LATEST_VERSION = '17.19'
 
 VERSIONS = {
-  '15.33': {
-    'dex' : {
-      'inputs': [os.path.join(V15_33_BASE, 'YouTubeRelease_unsigned.apk')],
-      'pgmap': '%s_proguard.map' % V15_33_PREFIX,
-      'libraries' : [utils.get_android_jar(25)],
-      'min-api' : ANDROID_L_API,
-    },
-    'deploy' : {
-      # When -injars and -libraryjars are used for specifying inputs library
-      # sanitization is on by default. For this version of YouTube -injars and
-      # -libraryjars are not used, but library sanitization is still required.
-      'sanitize_libraries': True,
-      'inputs': ['%s_deploy.jar' % V15_33_PREFIX],
-      'libraries' : [os.path.join(V15_33_BASE, 'legacy_YouTubeRelease_combined_library_jars.jar')],
-      'pgconf': [
-          '%s_proguard.config' % V15_33_PREFIX,
-          '%s_proguard_missing_classes.config' % V15_33_PREFIX,
-          '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY,
-          utils.IGNORE_WARNINGS_RULES],
-      'maindexrules' : [
-          os.path.join(V15_33_BASE, 'mainDexClasses.rules'),
-          os.path.join(V15_33_BASE, 'main-dex-classes-release-optimized.pgcfg'),
-          os.path.join(V15_33_BASE, 'main_dex_YouTubeRelease_proguard.cfg')],
-      'min-api' : ANDROID_H_MR2_API,
-    },
-    'proguarded' : {
-      'inputs': ['%s_proguard.jar' % V15_33_PREFIX],
-      'pgmap': '%s_proguard.map' % V15_33_PREFIX,
-      'min-api' : ANDROID_L_API,
-    }
-  },
   '16.20': {
     'deploy' : {
       'sanitize_libraries': False,