Introduce unused class to feature split map in D8

As part of introducing the class to feature split map in AppInfo, this CL rewrites all constructor calls to AppInfo.<init>, so that they instead use the virtual `rebuild` methods. This is already standard practice in AppInfoWithClassHierarchy and AppInfoWithLiveness.

Bug: b/388737195
Fixes: b/390570711
Change-Id: If4f289659a52cdca2d2763cdac37db59e4f4feb4
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index d3eb5d8..98ffb0b 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.features.ClassToFeatureSplitMap.createInitialD8ClassToFeatureSplitMap;
 import static com.android.tools.r8.utils.AssertionUtils.forTesting;
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
@@ -187,6 +188,7 @@
                     options.isGeneratingDexIndexed()
                         ? GlobalSyntheticsStrategy.forSingleOutputMode()
                         : GlobalSyntheticsStrategy.forPerFileMode(),
+                    createInitialD8ClassToFeatureSplitMap(options),
                     applicationReader.readMainDexClasses(app)));
     return timing.time("Create app-view", () -> AppView.createForD8(appInfo, typeRewriter, timing));
   }
@@ -286,10 +288,7 @@
         timing.begin("Rewrite non-dex inputs");
         DexApplication app = rewriteNonDexInputs(appView, inputApp, executor, marker, timing);
         timing.end();
-        appView.setAppInfo(
-            new AppInfo(
-                appView.appInfo().getSyntheticItems().commit(app),
-                appView.appInfo().getMainDexInfo()));
+        appView.rebuildAppInfo(app);
         appView.setNamingLens(NamingLens.getIdentityLens());
       } else if (options.isGeneratingDex() && hasDexResources) {
         appView.setNamingLens(NamingLens.getIdentityLens());
@@ -434,10 +433,7 @@
     }
     DexApplication cfApp =
         appView.app().builder().replaceProgramClasses(nonDexProgramClasses).build();
-    appView.setAppInfo(
-        new AppInfo(
-            appView.appInfo().getSyntheticItems().commit(cfApp),
-            appView.appInfo().getMainDexInfo()));
+    appView.rebuildAppInfo(cfApp);
     ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles();
     new GenericSignatureRewriter(appView).run(appView.appInfo().classes(), executor);
     new KotlinMetadataRewriter(appView).runForD8(executor);
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 6f51728..a851741 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -60,7 +60,7 @@
   public MainDexInfo traceMainDexForD8(AppView<AppInfo> appView, ExecutorService executor)
       throws ExecutionException {
     return traceMainDex(
-        AppView.createForSimulatingR8InD8(
+        AppView.createForD8MainDexTracing(
             appView.app().toDirect(), appView.appInfo().getMainDexInfo()),
         executor);
   }
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index e9e342d..9871104 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -44,7 +44,6 @@
 import com.android.tools.r8.naming.VarHandleDesugaringRewritingNamingLens;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.SyntheticFinalization;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.synthesis.SyntheticNaming;
@@ -163,8 +162,7 @@
         timing.time(
             "Create app-info",
             () ->
-                AppInfo.createInitialAppInfo(
-                    app, GlobalSyntheticsStrategy.forSingleOutputMode(), MainDexInfo.none()));
+                AppInfo.createInitialAppInfo(app, GlobalSyntheticsStrategy.forSingleOutputMode()));
     // Now that the dex-application is fully loaded, close any internal archive providers.
     inputApp.closeInternalArchiveProviders();
     return timing.time("Create app-view", () -> AppView.createForD8(appInfo, typeRewriter, timing));
@@ -195,18 +193,11 @@
     // We must run proper D8 conversion as the global synthetics may give rise to additional
     // synthetics as part of their implementation.
     assert appView.getSyntheticItems().hasPendingSyntheticClasses();
-    appView.setAppInfo(
-        new AppInfo(
-            appView.appInfo().getSyntheticItems().commit(appView.app()),
-            appView.appInfo().getMainDexInfo()));
+    appView.rebuildAppInfo();
 
     new PrimaryD8L8IRConverter(appView, Timing.empty()).convert(appView, executorService);
 
-    appView
-        .setAppInfo(
-            new AppInfo(
-                appView.appInfo().getSyntheticItems().commit(appView.app()),
-                appView.appInfo().getMainDexInfo()));
+    appView.rebuildAppInfo();
 
     timing.time(
         "Finalize synthetics",
@@ -222,11 +213,7 @@
       createAllApiStubs(appView, synthesizingContext, executorService);
     }
 
-    appView
-        .setAppInfo(
-            new AppInfo(
-                appView.appInfo().getSyntheticItems().commit(appView.app()),
-                appView.appInfo().getMainDexInfo()));
+    appView.rebuildAppInfo();
   }
 
   private static DexProgramClass createSynthesizingContext(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 56fa562..445f246 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -359,11 +359,7 @@
           .synthesizeClasses(executorService, classSynthesizerEventConsumer);
       classSynthesizerEventConsumer.finished(appView);
       if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
-        appView.setAppInfo(
-            appView
-                .appInfo()
-                .rebuildWithClassHierarchy(
-                    appView.getSyntheticItems().commit(appView.appInfo().app())));
+        appView.rebuildAppInfo();
       }
       timing.end();
       timing.begin("Strip unused code");
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index 110a167..6750fd9 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
@@ -25,8 +24,6 @@
 import com.android.tools.r8.graph.ThrowExceptionCode;
 import com.android.tools.r8.lightir.LirCode;
 import com.android.tools.r8.lightir.LirCode.TryCatchTable;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ListUtils;
@@ -86,25 +83,7 @@
                   ThrowExceptionCode.create(appView.dexItemFactory().noClassDefFoundErrorType),
                   eventConsumer));
       // Commit the synthetic items.
-      if (appView.hasLiveness()) {
-        CommittedItems committedItems = appView.getSyntheticItems().commit(appView.appInfo().app());
-        AppView<AppInfoWithLiveness> appInfoWithLivenessAppView = appView.withLiveness();
-        appInfoWithLivenessAppView.setAppInfo(
-            appInfoWithLivenessAppView.appInfo().rebuildWithLiveness(committedItems));
-      } else if (appView.hasClassHierarchy()) {
-        CommittedItems committedItems = appView.getSyntheticItems().commit(appView.appInfo().app());
-        appView
-            .withClassHierarchy()
-            .setAppInfo(
-                appView.appInfo().withClassHierarchy().rebuildWithClassHierarchy(committedItems));
-      } else {
-        appView
-            .withoutClassHierarchy()
-            .setAppInfo(
-                new AppInfo(
-                    appView.appInfo().getSyntheticItems().commit(appView.app()),
-                    appView.appInfo().getMainDexInfo()));
-      }
+      appView.rebuildAppInfo();
     }
     eventConsumer.finished(appView);
   }
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 54d98a5..83c18a0 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -42,7 +42,17 @@
     return new ClassToFeatureSplitMap(new IdentityHashMap<>(), null);
   }
 
-  public static ClassToFeatureSplitMap createInitialClassToFeatureSplitMap(
+  public static ClassToFeatureSplitMap createInitialD8ClassToFeatureSplitMap(
+      InternalOptions options) {
+    // We only support feature splits in D8 when run through R8 partial.
+    if (options.partialSubCompilationConfiguration != null) {
+      return createInitialClassToFeatureSplitMap(
+          options.dexItemFactory(), options.featureSplitConfiguration, options.reporter);
+    }
+    return createEmptyClassToFeatureSplitMap();
+  }
+
+  public static ClassToFeatureSplitMap createInitialR8ClassToFeatureSplitMap(
       InternalOptions options) {
     return createInitialClassToFeatureSplitMap(
         options.dexItemFactory(),
@@ -121,8 +131,7 @@
     return result;
   }
 
-  public FeatureSplit getFeatureSplit(
-      ProgramDefinition definition, AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public FeatureSplit getFeatureSplit(ProgramDefinition definition, AppView<?> appView) {
     return getFeatureSplit(definition, appView.getSyntheticItems());
   }
 
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
index 9ac1bd5..820de4e 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
@@ -17,7 +17,7 @@
 public class FeatureSplitBoundaryOptimizationUtils {
 
   public static FeatureSplit getMergeKeyForHorizontalClassMerging(
-      DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
+      DexProgramClass clazz, AppView<?> appView) {
     return appView.appInfo().getClassToFeatureSplitMap().getFeatureSplit(clazz, appView);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 07512e5..4c16fe2 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.origin.GlobalSyntheticOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -22,6 +23,7 @@
 public class AppInfo implements DexDefinitionSupplier {
 
   private final DexApplication app;
+  private final ClassToFeatureSplitMap classToFeatureSplitMap;
   private final DexItemFactory dexItemFactory;
   private final MainDexInfo mainDexInfo;
   private final SyntheticItems syntheticItems;
@@ -32,21 +34,31 @@
 
   public static AppInfo createInitialAppInfo(
       DexApplication application, GlobalSyntheticsStrategy globalSyntheticsStrategy) {
-    return createInitialAppInfo(application, globalSyntheticsStrategy, MainDexInfo.none());
+    return createInitialAppInfo(
+        application,
+        globalSyntheticsStrategy,
+        ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
+        MainDexInfo.none());
   }
 
   public static AppInfo createInitialAppInfo(
       DexApplication application,
       GlobalSyntheticsStrategy globalSyntheticsStrategy,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
       MainDexInfo mainDexInfo) {
     return new AppInfo(
+        classToFeatureSplitMap,
         SyntheticItems.createInitialSyntheticItems(application, globalSyntheticsStrategy),
         mainDexInfo);
   }
 
-  public AppInfo(CommittedItems committedItems, MainDexInfo mainDexInfo) {
+  AppInfo(
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      CommittedItems committedItems,
+      MainDexInfo mainDexInfo) {
     this(
         committedItems.getApplication(),
+        classToFeatureSplitMap,
         committedItems.toSyntheticItems(),
         mainDexInfo,
         new BooleanBox());
@@ -54,17 +66,29 @@
 
   // For desugaring.
   // This is a view onto the app info and is the only place the pending synthetics are shared.
-  AppInfo(AppInfoWithClassHierarchy.CreateDesugaringViewOnAppInfo witness, AppInfo appInfo) {
-    this(appInfo.app, appInfo.syntheticItems, appInfo.mainDexInfo, appInfo.obsolete);
+  @SuppressWarnings("InconsistentOverloads")
+  AppInfo(
+      AppInfoWithClassHierarchy.CreateDesugaringViewOnAppInfo witness,
+      AppInfo appInfo,
+      ClassToFeatureSplitMap classToFeatureSplitMap) {
+    this(
+        appInfo.app,
+        classToFeatureSplitMap,
+        appInfo.syntheticItems,
+        appInfo.mainDexInfo,
+        appInfo.obsolete);
     assert witness != null;
   }
 
+  @SuppressWarnings("InconsistentOverloads")
   private AppInfo(
       DexApplication application,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
       SyntheticItems syntheticItems,
       MainDexInfo mainDexInfo,
       BooleanBox obsolete) {
     this.app = application;
+    this.classToFeatureSplitMap = classToFeatureSplitMap;
     this.dexItemFactory = application.dexItemFactory;
     this.mainDexInfo = mainDexInfo;
     this.syntheticItems = syntheticItems;
@@ -83,15 +107,24 @@
     timing.begin("Pruning AppInfo");
     AppInfo result =
         new AppInfo(
+            classToFeatureSplitMap.withoutPrunedItems(prunedItems),
             getSyntheticItems().commitPrunedItems(prunedItems),
             getMainDexInfo().withoutPrunedItems(prunedItems));
     timing.end();
     return result;
   }
 
+  protected AppInfo rebuild(DexApplication app) {
+    return rebuildWithCommittedItems(getSyntheticItems().commit(app));
+  }
+
+  public AppInfo rebuildWithCommittedItems(CommittedItems committedItems) {
+    return new AppInfo(classToFeatureSplitMap, committedItems, mainDexInfo);
+  }
+
   public AppInfo rebuildWithMainDexInfo(MainDexInfo mainDexInfo) {
     assert checkIfObsolete();
-    return new AppInfo(app, syntheticItems, mainDexInfo, new BooleanBox());
+    return new AppInfo(app, classToFeatureSplitMap, syntheticItems, mainDexInfo, new BooleanBox());
   }
 
   public InternalOptions options() {
@@ -120,6 +153,10 @@
     return app;
   }
 
+  public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
+    return classToFeatureSplitMap;
+  }
+
   @Override
   public DexItemFactory dexItemFactory() {
     assert checkIfObsolete();
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 6143f20..2d7f895 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.features.ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap;
 import static com.android.tools.r8.utils.TraversalContinuation.doBreak;
 import static com.android.tools.r8.utils.TraversalContinuation.doContinue;
 
@@ -58,11 +59,9 @@
         MissingClasses.empty());
   }
 
-  private final ClassToFeatureSplitMap classToFeatureSplitMap;
-
   /** Set of types that are mentioned in the program, but for which no definition exists. */
   // TODO(b/175659048): Consider hoisting to AppInfo to allow using MissingClasses in D8 desugar.
-  private final MissingClasses missingClasses;
+  private MissingClasses missingClasses;
 
   // For AppInfoWithLiveness subclass.
   protected AppInfoWithClassHierarchy(
@@ -70,15 +69,13 @@
       ClassToFeatureSplitMap classToFeatureSplitMap,
       MainDexInfo mainDexInfo,
       MissingClasses missingClasses) {
-    super(committedItems, mainDexInfo);
-    this.classToFeatureSplitMap = classToFeatureSplitMap;
+    super(classToFeatureSplitMap, committedItems, mainDexInfo);
     this.missingClasses = missingClasses;
   }
 
   // For desugaring.
   private AppInfoWithClassHierarchy(CreateDesugaringViewOnAppInfo witness, AppInfo appInfo) {
-    super(witness, appInfo);
-    this.classToFeatureSplitMap = ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap();
+    super(witness, appInfo, createEmptyClassToFeatureSplitMap());
     // TODO(b/175659048): Migrate the reporting of missing classes in D8 desugar to MissingClasses,
     //  and use the missing classes from AppInfo instead of MissingClasses.empty().
     this.missingClasses = MissingClasses.empty();
@@ -89,11 +86,13 @@
     return new AppInfoWithClassHierarchy(WITNESS, appInfo);
   }
 
-  public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(DexApplication application) {
-    return rebuildWithClassHierarchy(getSyntheticItems().commit(application));
+  @Override
+  protected AppInfoWithClassHierarchy rebuild(DexApplication application) {
+    return rebuildWithCommittedItems(getSyntheticItems().commit(application));
   }
 
-  public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(CommittedItems commit) {
+  @Override
+  public AppInfoWithClassHierarchy rebuildWithCommittedItems(CommittedItems commit) {
     return new AppInfoWithClassHierarchy(
         commit, getClassToFeatureSplitMap(), getMainDexInfo(), getMissingClasses());
   }
@@ -144,14 +143,14 @@
     return result;
   }
 
-  public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
-    return classToFeatureSplitMap;
-  }
-
   public MissingClasses getMissingClasses() {
     return missingClasses;
   }
 
+  public void setMissingClasses(MissingClasses missingClasses) {
+    this.missingClasses = missingClasses;
+  }
+
   @Override
   public boolean hasClassHierarchy() {
     assert checkIfObsolete();
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index eb26701..fa71464 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.features.ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap;
+
 import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
@@ -22,7 +24,6 @@
 import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintFactory;
-import com.android.tools.r8.ir.analysis.proto.EnumLiteProtoShrinker;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
@@ -260,14 +261,12 @@
         defaultTypeRewriter(appInfo));
   }
 
-  public static AppView<AppInfoWithClassHierarchy> createForSimulatingR8InD8(
+  public static AppView<AppInfoWithClassHierarchy> createForD8MainDexTracing(
       DirectMappedDexApplication application, MainDexInfo mainDexInfo) {
-    ClassToFeatureSplitMap classToFeatureSplitMap =
-        ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(application.options);
     AppInfoWithClassHierarchy appInfo =
         AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
             application,
-            classToFeatureSplitMap,
+            createEmptyClassToFeatureSplitMap(),
             mainDexInfo,
             GlobalSyntheticsStrategy.forSingleOutputMode());
     return new AppView<>(
@@ -296,7 +295,7 @@
   public static AppView<AppInfoWithClassHierarchy> createForR8(
       DexApplication application, MainDexInfo mainDexInfo) {
     ClassToFeatureSplitMap classToFeatureSplitMap =
-        ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(application.options);
+        ClassToFeatureSplitMap.createInitialR8ClassToFeatureSplitMap(application.options);
     AppInfoWithClassHierarchy appInfo =
         AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
             application,
@@ -426,6 +425,20 @@
     return appViewWithSpecializedAppInfo;
   }
 
+  public void rebuildAppInfo() {
+    rebuildAppInfo(app());
+  }
+
+  public void rebuildAppInfo(DexApplication app) {
+    if (hasLiveness()) {
+      withLiveness().setAppInfo(appInfoWithLiveness().rebuild(app));
+    } else if (hasClassHierarchy()) {
+      withClassHierarchy().setAppInfo(appInfoWithClassHierarchy().rebuild(app));
+    } else {
+      withoutClassHierarchy().setAppInfo(appInfo().rebuild(app));
+    }
+  }
+
   public boolean isAllCodeProcessed() {
     return allCodeProcessed;
   }
@@ -605,13 +618,6 @@
     return defaultValue;
   }
 
-  public <U> U withProtoEnumShrinker(Function<EnumLiteProtoShrinker, U> fn, U defaultValue) {
-    if (protoShrinker != null && options().protoShrinking().isEnumLiteProtoShrinkingEnabled()) {
-      return fn.apply(protoShrinker.enumLiteProtoShrinker);
-    }
-    return defaultValue;
-  }
-
   public <E extends Throwable> void withGeneratedExtensionRegistryShrinker(
       ThrowingConsumer<GeneratedExtensionRegistryShrinker, E> consumer) throws E {
     if (protoShrinker != null && protoShrinker.generatedExtensionRegistryShrinker != null) {
@@ -1340,6 +1346,11 @@
     // the lens prior to lens rewriting AppView.
     assert changed || lens.isHorizontalClassMergerGraphLens();
 
+    AppInfo appInfo = appView.appInfo();
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    appView.setAppInfo(
+        appInfo.rebuildWithMainDexInfo(
+            appInfo.getMainDexInfo().rewrittenWithLens(syntheticItems, lens, timing)));
     appView.setArtProfileCollection(
         appView.getArtProfileCollection().rewrittenWithLens(appView, lens, timing));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 52ae66a..3e0ccb4 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -25,6 +25,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class DirectMappedDexApplication extends DexApplication {
 
@@ -276,12 +277,31 @@
     }
 
     public Builder replaceClasspathClasses(Collection<DexClasspathClass> newClasspathClasses) {
+      return replaceClasspathClasses(ImmutableList.copyOf(newClasspathClasses));
+    }
+
+    public Builder replaceClasspathClasses(ImmutableList<DexClasspathClass> newClasspathClasses) {
       assert newClasspathClasses != null;
-      classpathClasses = ImmutableList.copyOf(newClasspathClasses);
+      classpathClasses = newClasspathClasses;
       pendingClasspathClasses.clear();
       return self();
     }
 
+    public Builder removeClasspathClasses(Predicate<DexClasspathClass> predicate) {
+      ImmutableList.Builder<DexClasspathClass> newClasspathClasses = ImmutableList.builder();
+      for (DexClasspathClass clazz : classpathClasses) {
+        if (!predicate.test(clazz)) {
+          newClasspathClasses.add(clazz);
+        }
+      }
+      for (DexClasspathClass clazz : pendingClasspathClasses) {
+        if (!predicate.test(clazz)) {
+          newClasspathClasses.add(clazz);
+        }
+      }
+      return replaceClasspathClasses(newClasspathClasses.build());
+    }
+
     public Builder replaceLibraryClasses(Collection<DexLibraryClass> libraryClasses) {
       ImmutableMap.Builder<DexType, DexLibraryClass> builder = ImmutableMap.builder();
       libraryClasses.forEach(clazz -> builder.put(clazz.type, clazz));
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 51c1aca..612ecec1e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.classmerging.ClassMergerSharedData;
 import com.android.tools.r8.classmerging.Policy;
-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.Code;
@@ -23,7 +22,6 @@
 import com.android.tools.r8.naming.IdentifierMinifier;
 import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
@@ -188,14 +186,7 @@
       LirConverter.rewriteLirWithLens(appViewWithClassHierarchy, timing, executorService);
       new IdentifierMinifier(appViewWithClassHierarchy)
           .rewriteDexItemBasedConstStringInStaticFields(executorService);
-      if (appView.hasLiveness()) {
-        AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-        appViewWithLiveness.setAppInfo(
-            appViewWithLiveness.appInfo().rebuildWithLiveness(newApplication));
-      } else {
-        appViewWithClassHierarchy.setAppInfo(
-            appViewWithClassHierarchy.appInfo().rebuildWithClassHierarchy(newApplication));
-      }
+      appView.rebuildAppInfo(newApplication);
       appView.clearCodeRewritings(executorService, timing);
     } else {
       SyntheticItems syntheticItems = appView.appInfo().getSyntheticItems();
@@ -203,13 +194,11 @@
       appView
           .withoutClassHierarchy()
           .setAppInfo(
-              new AppInfo(
-                  syntheticItems.commitRewrittenWithLens(
-                      newApplication, horizontalClassMergerGraphLens, timing),
-                  appView
-                      .appInfo()
-                      .getMainDexInfo()
-                      .rewrittenWithLens(syntheticItems, horizontalClassMergerGraphLens, timing)));
+              appView
+                  .appInfo()
+                  .rebuildWithCommittedItems(
+                      syntheticItems.commitRewrittenWithLens(
+                          newApplication, horizontalClassMergerGraphLens, timing)));
       appView.rewriteWithD8Lens(horizontalClassMergerGraphLens, timing);
     }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index d1d6d92..0f1c8de 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -219,6 +219,7 @@
     ImmutableList.Builder<MultiClassPolicy> builder = ImmutableList.builder();
     builder.add(
         new CheckAbstractClasses(appView),
+        new SameFeatureSplit(appView),
         new SameMainDexGroup(appView),
         new SameNestHost(appView),
         new SameParentClass(),
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 5ad8747..6fd9009 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
@@ -1,20 +1,18 @@
 // 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.horizontalclassmerging.policies;
 
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
 
 public class SameFeatureSplit extends MultiClassSameReferencePolicy<FeatureSplit> {
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
 
-  public SameFeatureSplit(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public SameFeatureSplit(AppView<?> appView) {
     this.appView = appView;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
index 93b681a..ddc3b2a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
@@ -91,11 +91,7 @@
     timing.end();
 
     application = builder.build();
-    appView.setAppInfo(
-        new AppInfo(
-            appView.appInfo().getSyntheticItems().commit(application),
-            appView.appInfo().getMainDexInfo()));
-
+    appView.rebuildAppInfo(application);
     profileCollectionAdditions.commit(appView);
   }
 
@@ -267,13 +263,10 @@
   private DexApplication commitPendingSyntheticItems(
       AppView<AppInfo> appView, DexApplication application) {
     if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
-      appView.setAppInfo(
-          new AppInfo(
-              appView.appInfo().getSyntheticItems().commit(application),
-              appView.appInfo().getMainDexInfo()));
-      application = appView.appInfo().app();
+      appView.rebuildAppInfo(application);
+      application = appView.app();
     }
-    assert application == appView.appInfo().app();
+    assert application == appView.app();
     return application;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index fad62b5..5357748 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -244,10 +244,7 @@
 
   private static void commitPendingSyntheticItems(AppView<AppInfoWithLiveness> appView) {
     if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
-      appView.setAppInfo(
-          appView
-              .appInfo()
-              .rebuildWithLiveness(appView.getSyntheticItems().commit(appView.appInfo().app())));
+      appView.rebuildAppInfo();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java
index 125f719..6072319 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java
@@ -220,8 +220,7 @@
 
   private void commitPendingSyntheticClasses() {
     assert appView.getSyntheticItems().hasPendingSyntheticClasses();
-    appView.setAppInfo(
-        appView.appInfo().rebuildWithLiveness(appView.getSyntheticItems().commit(appView.app())));
+    appView.rebuildAppInfo();
   }
 
   private void updateArtProfiles(Collection<Group> groups) {
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
index 6ee0789..5725e15 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialSubCompilationConfiguration.java
@@ -5,12 +5,16 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.Set;
 
 public abstract class R8PartialSubCompilationConfiguration {
 
@@ -104,21 +108,40 @@
       return newApp;
     }
 
-    public void commitDexingOutputClasses(AppView<AppInfoWithClassHierarchy> appView) {
+    public void commitDexingOutputClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+      Set<DexType> dexingOutputTypes =
+          SetUtils.mapIdentityHashSet(dexingOutputClasses, DexClass::getType);
       DirectMappedDexApplication newApp =
           appView
               .app()
               .asDirect()
               .builder()
-              // TODO(b/390570711): This should not clear all classpath classes, only the ones we
-              //  inject into program.
-              .replaceClasspathClasses(Collections.emptyList())
+              .removeClasspathClasses(clazz -> dexingOutputTypes.contains(clazz.getType()))
               .addProgramClasses(dexingOutputClasses)
               .build();
-      appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(newApp));
+      appView.rebuildAppInfo(newApp);
+      assert amendMissingClasses(appView);
       dexingOutputClasses = null;
     }
 
+    private boolean amendMissingClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+      if (appView.hasLiveness()) {
+        MissingClasses.Builder missingClassesBuilder =
+            appView.appInfo().getMissingClasses().builder();
+        for (DexProgramClass clazz : dexingOutputClasses) {
+          clazz.forEachImmediateSuperClassMatching(
+              appView.app(),
+              (supertype, superclass) -> superclass == null,
+              (supertype, superclass) ->
+                  missingClassesBuilder.addNewMissingClass(supertype, clazz));
+        }
+        appView
+            .appInfoWithLiveness()
+            .setMissingClasses(missingClassesBuilder.ignoreMissingClasses());
+      }
+      return true;
+    }
+
     @Override
     public boolean isR8() {
       return true;
diff --git a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
index a2c3821..f768b8e 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
@@ -117,10 +117,7 @@
 
     DexApplication newApplication =
         appView.app().builder().addProgramClasses(extraProgramClasses).build();
-    appView.setAppInfo(
-        new AppInfo(
-            appView.appInfo().getSyntheticItems().commit(newApplication),
-            appView.appInfo().getMainDexInfo()));
+    appView.rebuildAppInfo(newApplication);
   }
 
   private List<DexProgramClass> createStartupRuntimeLibraryClasses() {
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 f16a61c..d9f9f72 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -939,11 +939,13 @@
     return appInfoWithLiveness;
   }
 
-  public AppInfoWithLiveness rebuildWithLiveness(DexApplication application) {
-    return rebuildWithLiveness(getSyntheticItems().commit(application));
+  @Override
+  public AppInfoWithLiveness rebuild(DexApplication application) {
+    return rebuildWithCommittedItems(getSyntheticItems().commit(application));
   }
 
-  public AppInfoWithLiveness rebuildWithLiveness(CommittedItems committedItems) {
+  @Override
+  public AppInfoWithLiveness rebuildWithCommittedItems(CommittedItems committedItems) {
     return new AppInfoWithLiveness(this, committedItems);
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index e5e514d..6f19771 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -131,6 +131,10 @@
       return build();
     }
 
+    public MissingClasses ignoreMissingClasses() {
+      return build();
+    }
+
     public MissingClasses reportMissingClasses(
         AppView<?> appView, SynthesizingContextOracle synthesizingContextOracle) {
       Map<DexType, Set<ProgramDerivedContext>> missingClassesToBeReported =
diff --git a/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java
index 5e5f3bd..37b8efc 100644
--- a/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java
+++ b/src/main/java/com/android/tools/r8/startup/NonStartupInStartupOutliner.java
@@ -26,8 +26,6 @@
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.shaking.KeepMethodInfo.Joiner;
-import com.android.tools.r8.synthesis.CommittedItems;
-import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -361,19 +359,8 @@
   }
 
   private void commitPendingSyntheticClasses() {
-    SyntheticItems syntheticItems = appView.getSyntheticItems();
-    if (!syntheticItems.hasPendingSyntheticClasses()) {
-      return;
-    }
-    CommittedItems committedItems = syntheticItems.commit(appView.app());
-    if (appView.hasLiveness()) {
-      appView
-          .withLiveness()
-          .setAppInfo(appView.appInfoWithLiveness().rebuildWithLiveness(committedItems));
-    } else {
-      appView
-          .withClassHierarchy()
-          .setAppInfo(appView.appInfo().rebuildWithClassHierarchy(committedItems));
+    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
+      appView.rebuildAppInfo();
     }
   }
 
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 ece8caa..75137d7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -173,16 +173,12 @@
     assert !appView.appInfo().hasLiveness();
     appView.options().testing.checkDeterminism(appView);
     Result result = appView.getSyntheticItems().computeFinalSynthetics(appView, timing);
-    appView.setAppInfo(new AppInfo(result.commit, result.mainDexInfo));
+    appView.setAppInfo(
+        appView
+            .appInfo()
+            .rebuildWithCommittedItems(result.commit)
+            .rebuildWithMainDexInfo(result.mainDexInfo));
     if (result.lens != null) {
-      appView.setAppInfo(
-          appView
-              .appInfo()
-              .rebuildWithMainDexInfo(
-                  appView
-                      .appInfo()
-                      .getMainDexInfo()
-                      .rewrittenWithLens(appView.getSyntheticItems(), result.lens, timing)));
       appView.rewriteWithD8Lens(result.lens, timing);
     }
     appView.pruneItems(result.prunedItems, executorService, timing);
@@ -194,8 +190,11 @@
     assert !appView.appInfo().hasLiveness();
     appView.options().testing.checkDeterminism(appView);
     Result result = appView.getSyntheticItems().computeFinalSynthetics(appView, timing);
-    appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
-    appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo));
+    appView.setAppInfo(
+        appView
+            .appInfo()
+            .rebuildWithCommittedItems(result.commit)
+            .rebuildWithMainDexInfo(result.mainDexInfo));
     if (result.lens != null) {
       appView.rewriteWithLens(result.lens, executorService, timing);
     }
@@ -215,7 +214,7 @@
     } else {
       assert result.commit.getApplication() == appView.appInfo().app();
     }
-    appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit));
+    appView.setAppInfo(appView.appInfo().rebuildWithCommittedItems(result.commit));
     appView.pruneItems(result.prunedItems, executorService, timing);
     appView.notifyOptimizationFinishedForTesting();
   }
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 b940b87..71121cd 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
 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;
@@ -329,11 +328,11 @@
     if (appView.appInfo().hasClassHierarchy()) {
       appView
           .withClassHierarchy()
-          .setAppInfo(appView.appInfo().withClassHierarchy().rebuildWithClassHierarchy(commit));
+          .setAppInfo(appView.appInfo().withClassHierarchy().rebuildWithCommittedItems(commit));
     } else {
       appView
           .withoutClassHierarchy()
-          .setAppInfo(new AppInfo(commit, appView.appInfo().getMainDexInfo()));
+          .setAppInfo(appView.appInfo().rebuildWithCommittedItems(commit));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index 0c74460..df805ff 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.benchmarks.appdumps;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.LibraryDesugaringTestConfiguration;
@@ -28,7 +26,6 @@
 import com.android.tools.r8.benchmarks.BenchmarkTarget;
 import com.android.tools.r8.dump.CompilerDump;
 import com.android.tools.r8.dump.DumpOptions;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
 import com.android.tools.r8.keepanno.annotations.KeepEdge;
 import com.android.tools.r8.keepanno.annotations.KeepItemKind;
@@ -400,20 +397,20 @@
                   CompilerDump dump = builder.getExtractedDump(environment);
                   DumpOptions dumpProperties = dump.getBuildProperties();
 
-                  // Verify that the dump does not use features that are not implemented below.
-                  dump.forEachFeatureArchive(
-                      feature -> {
-                        throw new Unimplemented();
-                      });
-                  assertFalse(dumpProperties.getEnableSameFilePolicy());
-                  assertFalse(dumpProperties.getIsolatedSplits());
-                  assertNull(dumpProperties.getAndroidApiExtensionPackages());
-
                   // Run R8.
                   TestBase.testForR8Partial(environment.getTemp(), Backend.DEX)
                       .addProgramFiles(dump.getProgramArchive())
                       .addLibraryFiles(dump.getLibraryArchive())
                       .addKeepRuleFiles(dump.getProguardConfigFile())
+                      .addR8PartialOptionsModification(
+                          options -> {
+                            options.apiModelingOptions().androidApiExtensionPackages =
+                                dumpProperties.getAndroidApiExtensionPackages();
+                            options
+                                .horizontalClassMergerOptions()
+                                .setEnableSameFilePolicy(dumpProperties.getEnableSameFilePolicy());
+                          })
+                      .enableIsolatedSplits(dumpProperties.getIsolatedSplits())
                       .setMinApi(dumpProperties.getMinApi())
                       .setR8PartialConfiguration(
                           b -> {
@@ -425,7 +422,11 @@
                               builder.programPackages.forEach(b::addJavaTypeIncludePattern);
                             }
                           })
-                      .apply(b -> addDesugaredLibrary(b, dump))
+                      .apply(
+                          b -> {
+                            dump.forEachFeatureArchive(b::addFeatureSplit);
+                            addDesugaredLibrary(b, dump);
+                          })
                       .apply(configuration)
                       .applyIf(
                           environment.getConfig().containsComposableCodeSizeMetric(),
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
index 9bcfcb7..b58c940 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/ChromeBenchmarks.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.benchmarks.appdumps;
 
 import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8PartialTestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.benchmarks.BenchmarkBase;
@@ -12,6 +13,8 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
+import org.junit.Ignore;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
@@ -36,7 +39,12 @@
             .setName("ChromeApp")
             .setDumpDependencyPath(dir)
             .setFromRevision(16457)
-            .buildR8(ChromeBenchmarks::configure));
+            .buildR8(ChromeBenchmarks::configure),
+        AppDumpBenchmarkBuilder.builder()
+            .setName("ChromeAppPartial")
+            .setDumpDependencyPath(dir)
+            .setFromRevision(16457)
+            .buildR8WithPartialShrinking(ChromeBenchmarks::configurePartial));
   }
 
   private static void configure(R8FullTestBuilder testBuilder) {
@@ -49,4 +57,31 @@
         .allowUnusedProguardConfigurationRules()
         .allowUnnecessaryDontWarnWildcards();
   }
+
+  private static void configurePartial(R8PartialTestBuilder testBuilder) {
+    testBuilder
+        .allowDiagnosticInfoMessages()
+        .allowUnusedDontWarnPatterns()
+        .allowUnusedProguardConfigurationRules()
+        .allowUnnecessaryDontWarnWildcards();
+  }
+
+  @Ignore
+  @Test
+  @Override
+  public void testBenchmarks() throws Exception {
+    super.testBenchmarks();
+  }
+
+  @Test
+  public void testChromeApp() throws Exception {
+    testBenchmarkWithName("ChromeApp");
+  }
+
+  // TODO(b/388421578): Add support for feature splits in R8 partial.
+  @Ignore
+  @Test
+  public void testChromeAppPartial() throws Exception {
+    testBenchmarkWithName("ChromeAppPartial");
+  }
 }