Add support for horizontal merging in D8

Bug: 187675788
Change-Id: I41c18db9b0f97d0267e8df91dee57616d97ca36a
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index f57e064..26a14b4 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
@@ -311,6 +312,8 @@
 
       finalizeApplication(appView, executor);
 
+      HorizontalClassMerger.createForD8ClassMerging(appView).runIfNecessary(executor, timing);
+
       new GenericSignatureRewriter(appView, namingLens)
           .runForD8(appView.appInfo().classes(), executor);
       new KotlinMetadataRewriter(appView, namingLens).runForD8(executor);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 583c6bb..9884112 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.Inspector;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
@@ -516,12 +515,14 @@
     // Disable global optimizations.
     internal.disableGlobalOptimizations();
 
-    // TODO(b/187675788): Enable class merging for synthetics in D8.
     HorizontalClassMergerOptions horizontalClassMergerOptions =
         internal.horizontalClassMergerOptions();
-    horizontalClassMergerOptions.disable();
-    assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL);
-    assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL);
+    if (internal.isGeneratingDex()) {
+      horizontalClassMergerOptions.setRestrictToSynthetics();
+    } else {
+      assert internal.isGeneratingClassFiles();
+      horizontalClassMergerOptions.disable();
+    }
 
     internal.setDumpInputFlags(getDumpInputFlags(), skipDump);
     internal.dumpOptions = dumpOptions();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d479e46..afea47d 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -508,7 +508,7 @@
         assert appView.verticallyMergedClasses() != null;
 
         HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness)
-            .runIfNecessary(runtimeTypeCheckInfo, executorService, timing);
+            .runIfNecessary(executorService, timing, runtimeTypeCheckInfo);
       }
 
       new ProtoNormalizer(appViewWithLiveness).run(executorService, timing);
@@ -751,11 +751,11 @@
       // are always merged.
       HorizontalClassMerger.createForFinalClassMerging(appView)
           .runIfNecessary(
+              executorService,
+              timing,
               classMergingEnqueuerExtensionBuilder != null
                   ? classMergingEnqueuerExtensionBuilder.build(appView.graphLens())
-                  : null,
-              executorService,
-              timing);
+                  : null);
 
       // Perform minification.
       NamingLens namingLens;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index 682da64..464db91 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -23,21 +23,31 @@
 import java.util.Set;
 import java.util.function.BiConsumer;
 
-public class ClassInstanceFieldsMerger {
+public interface ClassInstanceFieldsMerger {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final MergeGroup group;
-  private final Builder lensBuilder;
+  void setClassIdField(DexEncodedField classIdField);
 
-  private DexEncodedField classIdField;
+  DexEncodedField[] merge();
 
-  public ClassInstanceFieldsMerger(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      HorizontalClassMergerGraphLens.Builder lensBuilder,
-      MergeGroup group) {
-    this.appView = appView;
-    this.group = group;
-    this.lensBuilder = lensBuilder;
+  static ClassInstanceFieldsMerger create(
+      AppView<?> appView, HorizontalClassMergerGraphLens.Builder lensBuilder, MergeGroup group) {
+    if (appView.hasClassHierarchy()) {
+      return new ClassInstanceFieldsMergerImpl(appView.withClassHierarchy(), lensBuilder, group);
+    } else {
+      assert group.getInstanceFieldMap().isEmpty();
+      appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics();
+      return new ClassInstanceFieldsMerger() {
+        @Override
+        public void setClassIdField(DexEncodedField classIdField) {
+          throw new UnsupportedOperationException("No instance field merging in D8");
+        }
+
+        @Override
+        public DexEncodedField[] merge() {
+          return DexEncodedField.EMPTY_ARRAY;
+        }
+      };
+    }
   }
 
   /**
@@ -50,7 +60,7 @@
    * Bar has fields 'A b' and 'B a'), we make a prepass that matches fields with the same reference
    * type.
    */
-  public static void mapFields(
+  static void mapFields(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       DexProgramClass source,
       DexProgramClass target,
@@ -90,7 +100,7 @@
     }
   }
 
-  private static Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo(
+  static Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo(
       DexProgramClass target) {
     Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByInfo =
         new LinkedHashMap<>();
@@ -102,10 +112,9 @@
     return availableFieldsByInfo;
   }
 
-  private static Map<InstanceFieldInfo, LinkedList<DexEncodedField>>
-      getAvailableFieldsByRelaxedInfo(
-          AppView<? extends AppInfoWithClassHierarchy> appView,
-          Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo) {
+  static Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByRelaxedInfo(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo) {
     Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByRelaxedInfo =
         new LinkedHashMap<>();
     availableFieldsByExactInfo.forEach(
@@ -118,64 +127,85 @@
     return availableFieldsByRelaxedInfo;
   }
 
-  private void fixAccessFlags(DexEncodedField newField, Collection<DexEncodedField> oldFields) {
-    if (newField.isSynthetic() && Iterables.any(oldFields, oldField -> !oldField.isSynthetic())) {
-      newField.getAccessFlags().demoteFromSynthetic();
-    }
-    if (newField.isFinal() && Iterables.any(oldFields, oldField -> !oldField.isFinal())) {
-      newField.getAccessFlags().demoteFromFinal();
-    }
-  }
+  class ClassInstanceFieldsMergerImpl implements ClassInstanceFieldsMerger {
 
-  public void setClassIdField(DexEncodedField classIdField) {
-    this.classIdField = classIdField;
-  }
+    private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private final MergeGroup group;
+    private final Builder lensBuilder;
 
-  public DexEncodedField[] merge() {
-    assert group.hasInstanceFieldMap();
-    List<DexEncodedField> newFields = new ArrayList<>();
-    if (classIdField != null) {
-      newFields.add(classIdField);
-    }
-    group
-        .getInstanceFieldMap()
-        .forEachManyToOneMapping(
-            (sourceFields, targetField) ->
-                newFields.add(mergeSourceFieldsToTargetField(targetField, sourceFields)));
-    return newFields.toArray(DexEncodedField.EMPTY_ARRAY);
-  }
+    private DexEncodedField classIdField;
 
-  private DexEncodedField mergeSourceFieldsToTargetField(
-      DexEncodedField targetField, Set<DexEncodedField> sourceFields) {
-    fixAccessFlags(targetField, sourceFields);
-
-    DexEncodedField newField;
-    if (needsRelaxedType(targetField, sourceFields)) {
-      DexType newFieldType =
-          DexTypeUtils.computeLeastUpperBound(
-              appView,
-              Iterables.transform(
-                  Iterables.concat(IterableUtils.singleton(targetField), sourceFields),
-                  DexEncodedField::getType));
-      newField =
-          targetField.toTypeSubstitutedField(
-              appView, targetField.getReference().withType(newFieldType, appView.dexItemFactory()));
-    } else {
-      newField = targetField;
+    private ClassInstanceFieldsMergerImpl(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        HorizontalClassMergerGraphLens.Builder lensBuilder,
+        MergeGroup group) {
+      this.appView = appView;
+      this.group = group;
+      this.lensBuilder = lensBuilder;
     }
 
-    lensBuilder.recordNewFieldSignature(
-        Iterables.transform(
-            IterableUtils.append(sourceFields, targetField), DexEncodedField::getReference),
-        newField.getReference(),
-        targetField.getReference());
+    @Override
+    public void setClassIdField(DexEncodedField classIdField) {
+      this.classIdField = classIdField;
+    }
 
-    return newField;
-  }
+    @Override
+    public DexEncodedField[] merge() {
+      assert group.hasInstanceFieldMap();
+      List<DexEncodedField> newFields = new ArrayList<>();
+      if (classIdField != null) {
+        newFields.add(classIdField);
+      }
+      group
+          .getInstanceFieldMap()
+          .forEachManyToOneMapping(
+              (sourceFields, targetField) ->
+                  newFields.add(mergeSourceFieldsToTargetField(targetField, sourceFields)));
+      return newFields.toArray(DexEncodedField.EMPTY_ARRAY);
+    }
 
-  private boolean needsRelaxedType(
-      DexEncodedField targetField, Iterable<DexEncodedField> sourceFields) {
-    return Iterables.any(
-        sourceFields, sourceField -> sourceField.getType() != targetField.getType());
+    private DexEncodedField mergeSourceFieldsToTargetField(
+        DexEncodedField targetField, Set<DexEncodedField> sourceFields) {
+      fixAccessFlags(targetField, sourceFields);
+
+      DexEncodedField newField;
+      if (needsRelaxedType(targetField, sourceFields)) {
+        DexType newFieldType =
+            DexTypeUtils.computeLeastUpperBound(
+                appView,
+                Iterables.transform(
+                    Iterables.concat(IterableUtils.singleton(targetField), sourceFields),
+                    DexEncodedField::getType));
+        newField =
+            targetField.toTypeSubstitutedField(
+                appView,
+                targetField.getReference().withType(newFieldType, appView.dexItemFactory()));
+      } else {
+        newField = targetField;
+      }
+
+      lensBuilder.recordNewFieldSignature(
+          Iterables.transform(
+              IterableUtils.append(sourceFields, targetField), DexEncodedField::getReference),
+          newField.getReference(),
+          targetField.getReference());
+
+      return newField;
+    }
+
+    private void fixAccessFlags(DexEncodedField newField, Collection<DexEncodedField> oldFields) {
+      if (newField.isSynthetic() && Iterables.any(oldFields, oldField -> !oldField.isSynthetic())) {
+        newField.getAccessFlags().demoteFromSynthetic();
+      }
+      if (newField.isFinal() && Iterables.any(oldFields, oldField -> !oldField.isFinal())) {
+        newField.getAccessFlags().demoteFromFinal();
+      }
+    }
+
+    private boolean needsRelaxedType(
+        DexEncodedField targetField, Iterable<DexEncodedField> sourceFields) {
+      return Iterables.any(
+          sourceFields, sourceField -> sourceField.getType() != targetField.getType());
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 303efef..0751f8e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -7,7 +7,6 @@
 import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
-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.DexDefinition;
@@ -37,6 +36,7 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -55,7 +55,7 @@
 
   private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final Mode mode;
   private final MergeGroup group;
   private final DexItemFactory dexItemFactory;
@@ -74,7 +74,7 @@
   private final Collection<VirtualMethodMerger> virtualMethodMergers;
 
   private ClassMerger(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       IRCodeProvider codeProvider,
       Mode mode,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
@@ -88,7 +88,7 @@
 
     // Field mergers.
     this.classStaticFieldsMerger = new ClassStaticFieldsMerger(appView, lensBuilder, group);
-    this.classInstanceFieldsMerger = new ClassInstanceFieldsMerger(appView, lensBuilder, group);
+    this.classInstanceFieldsMerger = ClassInstanceFieldsMerger.create(appView, lensBuilder, group);
 
     // Method mergers.
     this.classInitializerMerger = ClassInitializerMerger.create(group);
@@ -344,16 +344,12 @@
   }
 
   public static class Builder {
-    private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private final AppView<?> appView;
     private final IRCodeProvider codeProvider;
-    private Mode mode;
+    private final Mode mode;
     private final MergeGroup group;
 
-    public Builder(
-        AppView<? extends AppInfoWithClassHierarchy> appView,
-        IRCodeProvider codeProvider,
-        MergeGroup group,
-        Mode mode) {
+    public Builder(AppView<?> appView, IRCodeProvider codeProvider, MergeGroup group, Mode mode) {
       this.appView = appView;
       this.codeProvider = codeProvider;
       this.group = group;
@@ -361,6 +357,24 @@
     }
 
     private List<VirtualMethodMerger> createVirtualMethodMergers() {
+      if (!appView.hasClassHierarchy()) {
+        assert getVirtualMethodMergerBuilders().isEmpty();
+        return Collections.emptyList();
+      }
+      Map<DexMethodSignature, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
+          getVirtualMethodMergerBuilders();
+      if (virtualMethodMergerBuilders.isEmpty()) {
+        return Collections.emptyList();
+      }
+      List<VirtualMethodMerger> virtualMethodMergers =
+          new ArrayList<>(virtualMethodMergerBuilders.size());
+      for (VirtualMethodMerger.Builder builder : virtualMethodMergerBuilders.values()) {
+        virtualMethodMergers.add(builder.build(appView.withClassHierarchy(), group));
+      }
+      return virtualMethodMergers;
+    }
+
+    private Map<DexMethodSignature, VirtualMethodMerger.Builder> getVirtualMethodMergerBuilders() {
       Map<DexMethodSignature, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
           new LinkedHashMap<>();
       group.forEach(
@@ -372,12 +386,7 @@
                               virtualMethod.getReference().getSignature(),
                               ignore -> new VirtualMethodMerger.Builder())
                           .add(virtualMethod)));
-      List<VirtualMethodMerger> virtualMethodMergers =
-          new ArrayList<>(virtualMethodMergerBuilders.size());
-      for (VirtualMethodMerger.Builder builder : virtualMethodMergerBuilders.values()) {
-        virtualMethodMergers.add(builder.build(appView, group));
-      }
-      return virtualMethodMergers;
+      return virtualMethodMergerBuilders;
     }
 
     private void createClassIdField() {
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 df258fe..a3df5dd 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -6,11 +6,12 @@
 
 import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
 
+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.DexApplication;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
@@ -18,8 +19,8 @@
 import com.android.tools.r8.ir.code.Invoke.Type;
 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;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -47,11 +48,11 @@
     }
   }
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final Mode mode;
   private final HorizontalClassMergerOptions options;
 
-  private HorizontalClassMerger(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+  private HorizontalClassMerger(AppView<?> appView, Mode mode) {
     this.appView = appView;
     this.mode = mode;
     this.options = appView.options().horizontalClassMergerOptions();
@@ -67,12 +68,26 @@
     return new HorizontalClassMerger(appView, Mode.FINAL);
   }
 
+  public static HorizontalClassMerger createForD8ClassMerging(AppView<?> appView) {
+    assert appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics();
+    return new HorizontalClassMerger(appView, Mode.FINAL);
+  }
+
+  public void runIfNecessary(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    runIfNecessary(executorService, timing, null);
+  }
+
   public void runIfNecessary(
-      RuntimeTypeCheckInfo runtimeTypeCheckInfo, ExecutorService executorService, Timing timing)
+      ExecutorService executorService, Timing timing, RuntimeTypeCheckInfo runtimeTypeCheckInfo)
       throws ExecutionException {
     if (options.isEnabled(mode)) {
       timing.begin("HorizontalClassMerger (" + mode.toString() + ")");
-      run(runtimeTypeCheckInfo, executorService, timing);
+      IRCodeProvider codeProvider =
+          appView.hasClassHierarchy()
+              ? IRCodeProvider.create(appView.withClassHierarchy())
+              : IRCodeProvider.createThrowing();
+      run(runtimeTypeCheckInfo, codeProvider, executorService, timing);
 
       // Clear type elements cache after IR building.
       appView.dexItemFactory().clearTypeElementsCache();
@@ -84,10 +99,11 @@
   }
 
   private void run(
-      RuntimeTypeCheckInfo runtimeTypeCheckInfo, ExecutorService executorService, Timing timing)
+      RuntimeTypeCheckInfo runtimeTypeCheckInfo,
+      IRCodeProvider codeProvider,
+      ExecutorService executorService,
+      Timing timing)
       throws ExecutionException {
-    IRCodeProvider codeProvider = new IRCodeProvider(appView);
-
     // Run the policies on all program classes to produce a final grouping.
     List<Policy> policies =
         PolicyScheduler.getPolicies(appView, codeProvider, mode, runtimeTypeCheckInfo);
@@ -121,6 +137,7 @@
 
     SyntheticInitializerConverter syntheticInitializerConverter =
         syntheticInitializerConverterBuilder.build();
+    assert syntheticInitializerConverter.isEmpty() || appView.enableWholeProgramOptimizations();
     syntheticInitializerConverter.convertClassInitializers(executorService);
 
     // Generate the graph lens.
@@ -131,16 +148,31 @@
     HorizontalClassMergerGraphLens horizontalClassMergerGraphLens =
         createLens(mergedClasses, lensBuilder, mode, syntheticArgumentClass);
 
-    assert verifyNoCyclesInInterfaceHierarchies(groups);
-
-    // Prune keep info.
-    KeepInfoCollection keepInfo = appView.getKeepInfo();
-    keepInfo.mutate(mutator -> mutator.removeKeepInfoForMergedClasses(prunedItems));
+    assert verifyNoCyclesInInterfaceHierarchies(appView, groups);
 
     // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that allocation
     // sites, fields accesses, etc. are correctly transferred to the target classes.
-    appView.rewriteWithLensAndApplication(
-        horizontalClassMergerGraphLens, getNewApplication(mergedClasses));
+    DexApplication newApplication = getNewApplication(mergedClasses);
+    if (appView.hasClassHierarchy()) {
+      appView.rewriteWithLensAndApplication(
+          horizontalClassMergerGraphLens, newApplication.toDirect());
+    } else {
+      assert mode.isFinal();
+      assert !appView.enableWholeProgramOptimizations();
+      SyntheticItems syntheticItems = appView.appInfo().getSyntheticItems();
+      assert !syntheticItems.hasPendingSyntheticClasses();
+      appView
+          .withoutClassHierarchy()
+          .setAppInfo(
+              new AppInfo(
+                  syntheticItems.commitRewrittenWithLens(
+                      newApplication, horizontalClassMergerGraphLens),
+                  appView
+                      .appInfo()
+                      .getMainDexInfo()
+                      .rewrittenWithLens(syntheticItems, horizontalClassMergerGraphLens)));
+      appView.setGraphLens(horizontalClassMergerGraphLens);
+    }
     codeProvider.setGraphLens(horizontalClassMergerGraphLens);
 
     // Record where the synthesized $r8$classId fields are read and written.
@@ -162,6 +194,10 @@
   private void amendKeepInfo(
       HorizontalClassMergerGraphLens horizontalClassMergerGraphLens,
       List<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfos) {
+    if (!appView.enableWholeProgramOptimizations()) {
+      assert virtuallyMergedMethodsKeepInfos.isEmpty();
+      return;
+    }
     appView
         .getKeepInfo()
         .mutate(
@@ -213,6 +249,10 @@
       HorizontalClassMergerGraphLens horizontalClassMergerGraphLens,
       ExecutorService executorService)
       throws ExecutionException {
+    if (!appView.hasClassHierarchy()) {
+      assert verifyNoIncompleteCode(groups, executorService);
+      return;
+    }
     ThreadUtils.processItems(
         groups,
         group -> {
@@ -226,23 +266,46 @@
                     (IncompleteHorizontalClassMergerCode) method.getDefinition().getCode();
                 method
                     .setCode(
-                        code.toCfCode(appView, method, horizontalClassMergerGraphLens), appView);
+                        code.toCfCode(
+                            appView.withClassHierarchy(), method, horizontalClassMergerGraphLens),
+                        appView);
               });
         },
         executorService);
   }
 
-  private DirectMappedDexApplication getNewApplication(HorizontallyMergedClasses mergedClasses) {
+  private boolean verifyNoIncompleteCode(
+      Collection<MergeGroup> groups, ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(
+        groups,
+        group -> {
+          assert !group
+                  .getTarget()
+                  .methods(
+                      method ->
+                          method.hasCode()
+                              && method.getCode().isIncompleteHorizontalClassMergerCode())
+                  .iterator()
+                  .hasNext()
+              : "Expected no incomplete code";
+        },
+        executorService);
+    return true;
+  }
+
+  private DexApplication getNewApplication(HorizontallyMergedClasses mergedClasses) {
     // In the second round of class merging, we must forcefully remove the merged classes from the
     // application, since we won't run tree shaking before writing the application.
-    DirectMappedDexApplication application = appView.appInfo().app().asDirect();
-    return mode.isInitial()
-        ? application
-        : application
-            .builder()
-            .removeProgramClasses(
-                clazz -> mergedClasses.hasBeenMergedIntoDifferentType(clazz.getType()))
-            .build();
+    if (mode.isInitial()) {
+      return appView.app();
+    } else {
+      return appView
+          .app()
+          .builder()
+          .removeProgramClasses(
+              clazz -> mergedClasses.hasBeenMergedIntoDifferentType(clazz.getType()))
+          .build();
+    }
   }
 
   private List<MergeGroup> getInitialGroups() {
@@ -312,13 +375,16 @@
         .fixupTypeReferences();
   }
 
-  private boolean verifyNoCyclesInInterfaceHierarchies(Collection<MergeGroup> groups) {
+  private static boolean verifyNoCyclesInInterfaceHierarchies(
+      AppView<?> appView, Collection<MergeGroup> groups) {
     for (MergeGroup group : groups) {
       if (group.isClassGroup()) {
         continue;
       }
+      assert appView.hasClassHierarchy();
       DexProgramClass interfaceClass = group.getTarget();
       appView
+          .withClassHierarchy()
           .appInfo()
           .traverseSuperTypes(
               interfaceClass,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
index 9f74d8e..166bb1f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
@@ -11,31 +11,56 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 
-public class IRCodeProvider {
+public interface IRCodeProvider {
 
-  private final AppView<AppInfo> appViewForConversion;
+  IRCode buildIR(ProgramMethod method);
 
-  public IRCodeProvider(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    // At this point the code rewritings described by repackaging and synthetic finalization have
-    // not been applied to the code objects. These code rewritings will be applied in the
-    // application writer. We therefore simulate that we are in D8, to allow building IR for each of
-    // the class initializers without applying the unapplied code rewritings, to avoid that we apply
-    // the lens more than once to the same piece of code.
-    AppView<AppInfo> appViewForConversion =
-        AppView.createForD8(AppInfo.createInitialAppInfo(appView.appInfo().app()));
-    appViewForConversion.setGraphLens(appView.graphLens());
-    appViewForConversion.setCodeLens(appView.codeLens());
-    this.appViewForConversion = appViewForConversion;
+  void setGraphLens(GraphLens graphLens);
+
+  static IRCodeProvider create(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return new IRCodeProviderImpl(appView);
   }
 
-  public IRCode buildIR(ProgramMethod method) {
-    return method
-        .getDefinition()
-        .getCode()
-        .buildIR(method, appViewForConversion, method.getOrigin());
+  static IRCodeProvider createThrowing() {
+    return new IRCodeProvider() {
+      @Override
+      public IRCode buildIR(ProgramMethod method) {
+        throw new UnsupportedOperationException("Should never build IR for methods in D8");
+      }
+
+      @Override
+      public void setGraphLens(GraphLens graphLens) {}
+    };
   }
 
-  public void setGraphLens(GraphLens graphLens) {
-    appViewForConversion.setGraphLens(graphLens);
+  class IRCodeProviderImpl implements IRCodeProvider {
+
+    private final AppView<AppInfo> appViewForConversion;
+
+    private IRCodeProviderImpl(AppView<? extends AppInfoWithClassHierarchy> appView) {
+      // At this point the code rewritings described by repackaging and synthetic finalization have
+      // not been applied to the code objects. These code rewritings will be applied in the
+      // application writer. We therefore simulate that we are in D8, to allow building IR for each
+      // of the class initializers without applying the unapplied code rewritings, to avoid that we
+      // apply the lens more than once to the same piece of code.
+      AppView<AppInfo> appViewForConversion =
+          AppView.createForD8(AppInfo.createInitialAppInfo(appView.appInfo().app()));
+      appViewForConversion.setGraphLens(appView.graphLens());
+      appViewForConversion.setCodeLens(appView.codeLens());
+      this.appViewForConversion = appViewForConversion;
+    }
+
+    @Override
+    public IRCode buildIR(ProgramMethod method) {
+      return method
+          .getDefinition()
+          .getCode()
+          .buildIR(method, appViewForConversion, method.getOrigin());
+    }
+
+    @Override
+    public void setGraphLens(GraphLens graphLens) {
+      appViewForConversion.setGraphLens(graphLens);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
index dbf0943..952421e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -37,14 +38,21 @@
   }
 
   public static InstanceInitializerMergerCollection create(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       Reference2IntMap<DexType> classIdentifiers,
       IRCodeProvider codeProvider,
       MergeGroup group,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Mode mode) {
+    if (!appView.hasClassHierarchy()) {
+      assert appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics();
+      assert verifyNoInstanceInitializers(group);
+      return new InstanceInitializerMergerCollection(
+          Collections.emptyList(), Collections.emptyMap());
+    }
     // Create an instance initializer merger for each group of instance initializers that are
     // equivalent.
+    AppView<AppInfoWithClassHierarchy> appViewWithClassHierarchy = appView.withClassHierarchy();
     Map<InstanceInitializerDescription, Builder> buildersByDescription = new LinkedHashMap<>();
     ProgramMethodSet buildersWithoutDescription = ProgramMethodSet.createLinked();
     group.forEach(
@@ -54,7 +62,7 @@
                 instanceInitializer -> {
                   InstanceInitializerDescription description =
                       InstanceInitializerAnalysis.analyze(
-                          appView, codeProvider, group, instanceInitializer);
+                          appViewWithClassHierarchy, codeProvider, group, instanceInitializer);
                   if (description != null) {
                     buildersByDescription
                         .computeIfAbsent(
@@ -62,7 +70,10 @@
                             ignoreKey(
                                 () ->
                                     new InstanceInitializerMerger.Builder(
-                                        appView, classIdentifiers, lensBuilder, mode)))
+                                        appViewWithClassHierarchy,
+                                        classIdentifiers,
+                                        lensBuilder,
+                                        mode)))
                         .addEquivalent(instanceInitializer);
                   } else {
                     buildersWithoutDescription.add(instanceInitializer);
@@ -95,7 +106,7 @@
                       instanceInitializer.getDefinition().getProto(),
                       ignore ->
                           new InstanceInitializerMerger.Builder(
-                              appView, classIdentifiers, lensBuilder, mode))
+                              appViewWithClassHierarchy, classIdentifiers, lensBuilder, mode))
                   .add(instanceInitializer));
       for (InstanceInitializerMerger.Builder builder : buildersByProto.values()) {
         instanceInitializerMergers.addAll(builder.build(group));
@@ -105,7 +116,7 @@
           instanceInitializer ->
               instanceInitializerMergers.addAll(
                   new InstanceInitializerMerger.Builder(
-                          appView, classIdentifiers, lensBuilder, mode)
+                          appViewWithClassHierarchy, classIdentifiers, lensBuilder, mode)
                       .add(instanceInitializer)
                       .build(group)));
     }
@@ -118,6 +129,14 @@
         instanceInitializerMergers, equivalentInstanceInitializerMergers);
   }
 
+  private static boolean verifyNoInstanceInitializers(MergeGroup group) {
+    group.forEach(
+        clazz -> {
+          assert !clazz.programInstanceInitializers().iterator().hasNext();
+        });
+    return true;
+  }
+
   public void forEach(Consumer<InstanceInitializerMerger> consumer) {
     instanceInitializerMergers.forEach(consumer);
     equivalentInstanceInitializerMergers.values().forEach(consumer);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index 458f0ba..7c7a68d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.shaking.KeepClassInfo;
+import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
@@ -167,17 +167,18 @@
     return new ProgramField(getTarget(), targetField);
   }
 
-  public void selectTarget(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public void selectTarget(AppView<?> appView) {
     Iterable<DexProgramClass> candidates = Iterables.filter(getClasses(), DexClass::isPublic);
     if (IterableUtils.isEmpty(candidates)) {
       candidates = getClasses();
     }
     Iterator<DexProgramClass> candidateIterator = candidates.iterator();
     DexProgramClass target = IterableUtils.first(candidates);
+    KeepInfoCollection keepInfo = appView.getKeepInfo();
     while (candidateIterator.hasNext()) {
       DexProgramClass current = candidateIterator.next();
-      KeepClassInfo keepClassInfo = appView.getKeepInfo().getClassInfo(current);
-      if (keepClassInfo.isMinificationAllowed(appView.options())) {
+      if (keepInfo != null
+          && keepInfo.getClassInfo(current).isMinificationAllowed(appView.options())) {
         target = current;
         break;
       }
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 5e76e2d..2ff1bfb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+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.ImmediateProgramSubtypingInfo;
@@ -43,6 +44,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoVirtualMethodMerging;
 import com.android.tools.r8.horizontalclassmerging.policies.NoWeakerAccessPrivileges;
 import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
+import com.android.tools.r8.horizontalclassmerging.policies.OnlyClassesWithStaticDefinitions;
 import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
 import com.android.tools.r8.horizontalclassmerging.policies.PreventClassMethodAndDefaultMethodCollisions;
@@ -53,7 +55,8 @@
 import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
 import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy;
-import com.android.tools.r8.horizontalclassmerging.policies.VerifyPolicyAlwaysSatisfied;
+import com.android.tools.r8.horizontalclassmerging.policies.VerifyMultiClassPolicyAlwaysSatisfied;
+import com.android.tools.r8.horizontalclassmerging.policies.VerifySingleClassPolicyAlwaysSatisfied;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
 import com.android.tools.r8.utils.ListUtils;
@@ -63,6 +66,31 @@
 public class PolicyScheduler {
 
   public static List<Policy> getPolicies(
+      AppView<?> appView,
+      IRCodeProvider codeProvider,
+      Mode mode,
+      RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+    if (appView.hasClassHierarchy()) {
+      return getPoliciesForR8(
+          appView.withClassHierarchy(), codeProvider, mode, runtimeTypeCheckInfo);
+    } else {
+      return getPoliciesForD8(appView.withoutClassHierarchy(), mode);
+    }
+  }
+
+  private static List<Policy> getPoliciesForD8(AppView<AppInfo> appView, Mode mode) {
+    assert mode.isFinal();
+    List<Policy> policies =
+        ImmutableList.<Policy>builder()
+            .addAll(getSingleClassPoliciesForD8(appView, mode))
+            .addAll(getMultiClassPoliciesForD8(appView, mode))
+            .build();
+    policies = appView.options().testing.horizontalClassMergingPolicyRewriter.apply(policies);
+    assert verifyPolicyOrderingConstraints(policies);
+    return policies;
+  }
+
+  private static List<Policy> getPoliciesForR8(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       IRCodeProvider codeProvider,
       Mode mode,
@@ -104,6 +132,16 @@
     return builder.build();
   }
 
+  private static List<SingleClassPolicy> getSingleClassPoliciesForD8(
+      AppView<AppInfo> appView, Mode mode) {
+    ImmutableList.Builder<SingleClassPolicy> builder =
+        ImmutableList.<SingleClassPolicy>builder()
+            .add(new CheckSyntheticClasses(appView))
+            .add(new OnlyClassesWithStaticDefinitions());
+    assert verifySingleClassPoliciesIrrelevantForMergingSyntheticsInD8(appView, mode, builder);
+    return builder.build();
+  }
+
   private static void addRequiredSingleClassPolicies(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ImmutableList.Builder<SingleClassPolicy> builder) {
@@ -148,7 +186,22 @@
             new NoKotlinMetadata(),
             new NoNativeMethods(),
             new NoServiceLoaders(appView));
-    policies.stream().map(VerifyPolicyAlwaysSatisfied::new).forEach(builder::add);
+    policies.stream().map(VerifySingleClassPolicyAlwaysSatisfied::new).forEach(builder::add);
+    return true;
+  }
+
+  private static boolean verifySingleClassPoliciesIrrelevantForMergingSyntheticsInD8(
+      AppView<AppInfo> appView, Mode mode, ImmutableList.Builder<SingleClassPolicy> builder) {
+    List<SingleClassPolicy> policies =
+        ImmutableList.of(
+            new NoAnnotationClasses(),
+            new NoDirectRuntimeTypeChecks(appView, mode),
+            new NoInterfaces(appView, mode),
+            new NoInnerClasses(),
+            new NoInstanceFieldAnnotations(),
+            new NoKotlinMetadata(),
+            new NoNativeMethods());
+    policies.stream().map(VerifySingleClassPolicyAlwaysSatisfied::new).forEach(builder::add);
     return true;
   }
 
@@ -194,6 +247,22 @@
     return builder.add(new FinalizeMergeGroup(appView, mode)).build();
   }
 
+  private static List<? extends Policy> getMultiClassPoliciesForD8(
+      AppView<AppInfo> appView, Mode mode) {
+    ImmutableList.Builder<MultiClassPolicy> builder = ImmutableList.builder();
+    builder.add(
+        new CheckAbstractClasses(appView),
+        new SameMainDexGroup(appView),
+        new SameNestHost(appView),
+        new SameParentClass(),
+        new SyntheticItemsPolicy(appView, mode),
+        new NoDifferentApiReferenceLevel(appView),
+        new LimitClassGroups(appView));
+    assert verifyMultiClassPoliciesIrrelevantForMergingSyntheticsInD8(appView, mode, builder);
+    builder.add(new FinalizeMergeGroup(appView, mode));
+    return builder.build();
+  }
+
   private static void addRequiredMultiClassPolicies(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       Mode mode,
@@ -234,6 +303,14 @@
         new OnlyDirectlyConnectedOrUnrelatedInterfaces(appView, mode));
   }
 
+  private static boolean verifyMultiClassPoliciesIrrelevantForMergingSyntheticsInD8(
+      AppView<AppInfo> appView, Mode mode, ImmutableList.Builder<MultiClassPolicy> builder) {
+    List<MultiClassPolicy> policies =
+        ImmutableList.of(new SyntheticItemsPolicy(appView, mode), new SameParentClass());
+    policies.stream().map(VerifyMultiClassPolicyAlwaysSatisfied::new).forEach(builder::add);
+    return true;
+  }
+
   private static boolean verifyPolicyOrderingConstraints(List<Policy> policies) {
     // No policies that may split interface groups are allowed to run after the
     // OnlyDirectlyConnectedOrUnrelatedInterfaces policy. This policy ensures that interface merging
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 183664a..31753bf 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -43,7 +42,7 @@
  */
 class TreeFixer extends TreeFixerBase {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final HorizontallyMergedClasses mergedClasses;
   private final Mode mode;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
@@ -55,7 +54,7 @@
       HashBiMap.create();
 
   public TreeFixer(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Mode mode,
@@ -123,17 +122,20 @@
    * </ul>
    */
   public HorizontalClassMergerGraphLens fixupTypeReferences() {
-    Collection<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
-    Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
-    classes.forEach(this::fixupAttributes);
-    classes.forEach(this::fixupProgramClassSuperTypes);
-    SubtypingForrestForClasses subtypingForrest = new SubtypingForrestForClasses(appView);
-    // TODO(b/170078037): parallelize this code segment.
-    for (DexProgramClass root : subtypingForrest.getProgramRoots()) {
-      subtypingForrest.traverseNodeDepthFirst(root, HashBiMap.create(), this::fixupProgramClass);
-    }
     HorizontalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
-    new AnnotationFixer(lens).run(appView.appInfo().classes());
+    if (appView.enableWholeProgramOptimizations()) {
+      Collection<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
+      Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
+      classes.forEach(this::fixupAttributes);
+      classes.forEach(this::fixupProgramClassSuperTypes);
+      SubtypingForrestForClasses subtypingForrest =
+          new SubtypingForrestForClasses(appView.withClassHierarchy());
+      // TODO(b/170078037): parallelize this code segment.
+      for (DexProgramClass root : subtypingForrest.getProgramRoots()) {
+        subtypingForrest.traverseNodeDepthFirst(root, HashBiMap.create(), this::fixupProgramClass);
+      }
+      new AnnotationFixer(lens).run(appView.appInfo().classes());
+    }
     return lens;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
index 446d245..7912f4c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.horizontalclassmerging.code;
 
 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.ProgramMethod;
 import com.android.tools.r8.horizontalclassmerging.IRCodeProvider;
@@ -25,13 +24,13 @@
  */
 public class SyntheticInitializerConverter {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final IRCodeProvider codeProvider;
   private final List<ProgramMethod> classInitializers;
   private final List<ProgramMethod> instanceInitializers;
 
   private SyntheticInitializerConverter(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       IRCodeProvider codeProvider,
       List<ProgramMethod> classInitializers,
       List<ProgramMethod> instanceInitializers) {
@@ -41,8 +40,7 @@
     this.instanceInitializers = instanceInitializers;
   }
 
-  public static Builder builder(
-      AppView<? extends AppInfoWithClassHierarchy> appView, IRCodeProvider codeProvider) {
+  public static Builder builder(AppView<?> appView, IRCodeProvider codeProvider) {
     return new Builder(appView, codeProvider);
   }
 
@@ -88,13 +86,12 @@
 
   public static class Builder {
 
-    private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private final AppView<?> appView;
     private final IRCodeProvider codeProvider;
     private final List<ProgramMethod> classInitializers = new ArrayList<>();
     private final List<ProgramMethod> instanceInitializers = new ArrayList<>();
 
-    private Builder(
-        AppView<? extends AppInfoWithClassHierarchy> appView, IRCodeProvider codeProvider) {
+    private Builder(AppView<?> appView, IRCodeProvider codeProvider) {
       this.appView = appView;
       this.codeProvider = codeProvider;
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
index 41fba55..06874f0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-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;
@@ -20,7 +19,7 @@
 
   private final InternalOptions options;
 
-  public CheckAbstractClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public CheckAbstractClasses(AppView<?> appView) {
     this.options = appView.options();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
index 8c08712..4d2554a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-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.SingleClassPolicy;
@@ -16,7 +15,7 @@
   private final HorizontalClassMergerOptions options;
   private final SyntheticItems syntheticItems;
 
-  public CheckSyntheticClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public CheckSyntheticClasses(AppView<?> appView) {
     this.options = appView.options().horizontalClassMergerOptions();
     this.syntheticItems = appView.getSyntheticItems();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
index 705c8cd..b9c4c3c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
@@ -12,6 +11,7 @@
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import java.util.Collection;
 import java.util.Set;
 
@@ -25,23 +25,30 @@
  */
 public class FinalizeMergeGroup extends MultiClassPolicy {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final Mode mode;
 
-  public FinalizeMergeGroup(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+  public FinalizeMergeGroup(AppView<?> appView, Mode mode) {
     this.appView = appView;
     this.mode = mode;
   }
 
   @Override
   public Collection<MergeGroup> apply(MergeGroup group) {
-    if (mode.isInitial() || group.isInterfaceGroup()) {
-      group.selectTarget(appView);
-      group.selectInstanceFieldMap(appView);
+    if (appView.enableWholeProgramOptimizations()) {
+      if (mode.isInitial() || group.isInterfaceGroup()) {
+        group.selectTarget(appView);
+        group.selectInstanceFieldMap(appView.withClassHierarchy());
+      } else {
+        // In the final round of merging each group should be finalized by the
+        // NoInstanceInitializerMerging policy.
+        assert verifyAlreadyFinalized(group);
+      }
     } else {
-      // In the final round of merging each group should be finalized by the
-      // NoInstanceInitializerMerging policy.
-      assert verifyAlreadyFinalized(group);
+      assert !group.hasTarget();
+      assert !group.hasInstanceFieldMap();
+      group.selectTarget(appView);
+      group.setInstanceFieldMap(new EmptyBidirectionalOneToOneMap<>());
     }
     return ListUtils.newLinkedList(group);
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
index 2752544..55d0472 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-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.MergeGroup;
@@ -17,8 +16,11 @@
 
   private final int maxGroupSize;
 
-  public LimitClassGroups(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    maxGroupSize = appView.options().horizontalClassMergerOptions().getMaxClassGroupSize();
+  public LimitClassGroups(AppView<?> appView) {
+    maxGroupSize =
+        appView.enableWholeProgramOptimizations()
+            ? appView.options().horizontalClassMergerOptions().getMaxClassGroupSizeInR8()
+            : appView.options().horizontalClassMergerOptions().getMaxClassGroupSizeInD8();
     assert maxGroupSize >= 2;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
index 79b6601..feabe1c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-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.HorizontalClassMerger.Mode;
@@ -16,7 +15,7 @@
   private final Mode mode;
   private final HorizontalClassMergerOptions options;
 
-  public NoInterfaces(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+  public NoInterfaces(AppView<?> appView, Mode mode) {
     this.mode = mode;
     this.options = appView.options().horizontalClassMergerOptions();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitions.java
new file mode 100644
index 0000000..736ede0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitions.java
@@ -0,0 +1,23 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.google.common.collect.Iterables;
+
+/** Prevent merging of classes that has non-static methods or fields. */
+public class OnlyClassesWithStaticDefinitions extends SingleClassPolicy {
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return !Iterables.any(program.members(), member -> !member.isStatic());
+  }
+
+  @Override
+  public String getName() {
+    return "OnlyStaticDefinitions";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java
index 6df343a..82dcbed 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-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;
@@ -17,7 +16,7 @@
   private final MainDexInfo mainDexInfo;
   private final SyntheticItems synthetics;
 
-  public SameMainDexGroup(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public SameMainDexGroup(AppView<?> appView) {
     mainDexInfo = appView.appInfo().getMainDexInfo();
     synthetics = appView.getSyntheticItems();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
index a4ba653..0f6e5ae 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-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;
@@ -15,7 +14,7 @@
 
   private final DexItemFactory dexItemFactory;
 
-  public SameNestHost(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public SameNestHost(AppView<?> appView) {
     this.dexItemFactory = appView.dexItemFactory();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
index f40b722..95764f9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-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.HorizontalClassMerger.Mode;
@@ -22,7 +21,7 @@
   private final Mode mode;
   private final SyntheticItems syntheticItems;
 
-  public SyntheticItemsPolicy(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+  public SyntheticItemsPolicy(AppView<?> appView, Mode mode) {
     this.mode = mode;
     this.syntheticItems = appView.getSyntheticItems();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyMultiClassPolicyAlwaysSatisfied.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyMultiClassPolicyAlwaysSatisfied.java
new file mode 100644
index 0000000..7ad1e60
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyMultiClassPolicyAlwaysSatisfied.java
@@ -0,0 +1,44 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collection;
+import java.util.Collections;
+
+public class VerifyMultiClassPolicyAlwaysSatisfied extends MultiClassPolicy {
+
+  private final MultiClassPolicy policy;
+
+  public VerifyMultiClassPolicyAlwaysSatisfied(MultiClassPolicy policy) {
+    this.policy = policy;
+  }
+
+  @Override
+  public String getName() {
+    return "VerifyMultiClassPolicyAlwaysSatisfied(" + policy.getName() + ")";
+  }
+
+  @Override
+  public boolean shouldSkipPolicy() {
+    return !InternalOptions.assertionsEnabled() || policy.shouldSkipPolicy();
+  }
+
+  @Override
+  public Collection<MergeGroup> apply(MergeGroup group) {
+    assert verifySameAppliedGroup(group);
+    return Collections.singletonList(group);
+  }
+
+  private boolean verifySameAppliedGroup(MergeGroup group) {
+    Collection<MergeGroup> applied = policy.apply(group);
+    assert applied.size() == 1;
+    MergeGroup appliedGroup = applied.iterator().next();
+    assert appliedGroup.size() == group.size() && group.containsAll(appliedGroup);
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyPolicyAlwaysSatisfied.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
similarity index 65%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyPolicyAlwaysSatisfied.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
index 1fc559c..51d33b7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyPolicyAlwaysSatisfied.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
@@ -6,12 +6,13 @@
 
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.utils.InternalOptions;
 
-public class VerifyPolicyAlwaysSatisfied extends SingleClassPolicy {
+public class VerifySingleClassPolicyAlwaysSatisfied extends SingleClassPolicy {
 
   private final SingleClassPolicy policy;
 
-  public VerifyPolicyAlwaysSatisfied(SingleClassPolicy policy) {
+  public VerifySingleClassPolicyAlwaysSatisfied(SingleClassPolicy policy) {
     this.policy = policy;
   }
 
@@ -23,11 +24,11 @@
 
   @Override
   public String getName() {
-    return "VerifyAlwaysSatisfied(" + policy.getName() + ")";
+    return "VerifySingleClassPolicyAlwaysSatisfied(" + policy.getName() + ")";
   }
 
   @Override
   public boolean shouldSkipPolicy() {
-    return policy.shouldSkipPolicy();
+    return !InternalOptions.assertionsEnabled() || policy.shouldSkipPolicy();
   }
 }
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 b7a666d..210ea92 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1449,10 +1449,14 @@
       this.enable = enable;
     }
 
-    public int getMaxClassGroupSize() {
+    public int getMaxClassGroupSizeInR8() {
       return 30;
     }
 
+    public int getMaxClassGroupSizeInD8() {
+      return 100;
+    }
+
     public int getMaxInterfaceGroupSize() {
       return 100;
     }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
index 31f5bda..e8d6257 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
@@ -78,9 +78,9 @@
   @Test
   public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForD8()
         .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
@@ -109,9 +109,8 @@
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(
+        parameters.isCfRuntime() || parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
index d00efb9..fb98821 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
@@ -105,7 +105,7 @@
         .apply(this::checkOutput)
         .inspect(
             inspector -> {
-              // TODO(b/187675788): Update when horizontal merging is enabled for D8.
+              // TODO(b/187675788): Update when horizontal merging is enabled for D8 for debug mode.
               if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
                 // We have generated 4 outlines two having api level 23 and two having api level 27.
                 assertEquals(7, inspector.allClasses().size());
@@ -133,19 +133,7 @@
             b -> b.addBootClasspathClasses(LibraryClass.class, OtherLibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector -> {
-              // TODO(b/187675788): Update when horizontal merging is enabled for D8.
-              if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
-                // We have generated 4 outlines two having api level 23 and two having api level 27.
-                assertEquals(7, inspector.allClasses().size());
-              } else if (parameters.getApiLevel().isLessThan(secondMethodApiLevel)) {
-                assertEquals(5, inspector.allClasses().size());
-              } else {
-                // No outlining on this api level.
-                assertEquals(3, inspector.allClasses().size());
-              }
-            });
+        .inspect(this::inspect);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
index 89f57c8..e021fe5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
@@ -74,7 +74,7 @@
                               mergeGroup ->
                                   mergeGroup.size()
                                       <= defaultHorizontalClassMergerOptions
-                                          .getMaxClassGroupSize()));
+                                          .getMaxClassGroupSizeInR8()));
                 })
             .allowDiagnosticWarningMessages()
             .compile()