Initial structure for extra round of class merging

Bug: 187496738
Fixes: 187387876
Change-Id: I2c614e06783c058a02ddb02b9dec651c1d50d6a6
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index f1ed0cd..0ba1767 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.dex.Marker.Tool;
 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.DesugaredLibraryConfiguration;
@@ -473,13 +474,14 @@
     // Assert some of R8 optimizations are disabled.
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
-    assert internal.horizontalClassMergerOptions().isDisabled();
     assert !internal.enableVerticalClassMerging;
     assert !internal.enableClassStaticizer;
     assert !internal.enableEnumValueOptimization;
     assert !internal.outline.enabled;
     assert !internal.enableValuePropagation;
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
+    assert !internal.horizontalClassMergerOptions().isEnabled(HorizontalClassMerger.Mode.INITIAL);
+    assert !internal.horizontalClassMergerOptions().isEnabled(HorizontalClassMerger.Mode.FINAL);
 
     internal.desugarState = getDesugarState();
     internal.encodeChecksums = getIncludeClassesChecksum();
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index ec9955d..69fa568 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.dex.Marker.Tool;
 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.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
@@ -172,13 +173,14 @@
     // Assert some of R8 optimizations are disabled.
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
-    assert internal.horizontalClassMergerOptions().isDisabled();
     assert !internal.enableVerticalClassMerging;
     assert !internal.enableClassStaticizer;
     assert !internal.enableEnumValueOptimization;
     assert !internal.outline.enabled;
     assert !internal.enableValuePropagation;
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
+    assert !internal.horizontalClassMergerOptions().isEnabled(HorizontalClassMerger.Mode.INITIAL);
+    assert !internal.horizontalClassMergerOptions().isEnabled(HorizontalClassMerger.Mode.FINAL);
 
     assert internal.desugarState == DesugarState.ON;
     assert internal.enableInheritanceClassInDexDistributor;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index e58264e..5eb2aa3 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -41,8 +41,6 @@
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerResult;
-import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
@@ -323,7 +321,7 @@
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       timing.begin("Strip unused code");
       RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder =
-          new RuntimeTypeCheckInfo.Builder(appView.dexItemFactory());
+          new RuntimeTypeCheckInfo.Builder(appView);
       try {
         // Add synthesized -assumenosideeffects from min api if relevant.
         if (options.isGeneratingDex()) {
@@ -453,7 +451,10 @@
       boolean isKotlinLibraryCompilationWithInlinePassThrough =
           options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods();
 
-      RuntimeTypeCheckInfo runtimeTypeCheckInfo = classMergingEnqueuerExtensionBuilder.build();
+      RuntimeTypeCheckInfo runtimeTypeCheckInfo =
+          classMergingEnqueuerExtensionBuilder.build(appView.graphLens());
+      classMergingEnqueuerExtensionBuilder = null;
+
       if (!isKotlinLibraryCompilationWithInlinePassThrough
           && options.getProguardConfiguration().isOptimizing()) {
         if (options.enableVerticalClassMerging) {
@@ -499,33 +500,9 @@
             timing.end();
           }
         }
-        if (options.horizontalClassMergerOptions().isEnabled()
-            && options.enableInlining
-            && options.isShrinking()) {
-          timing.begin("HorizontalClassMerger");
-          HorizontalClassMerger merger = new HorizontalClassMerger(appViewWithLiveness);
-          HorizontalClassMergerResult horizontalClassMergerResult =
-              merger.run(runtimeTypeCheckInfo, timing);
-          if (horizontalClassMergerResult != null) {
-            // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that
-            // allocations sites, fields accesses, etc. are correctly transferred to the target
-            // classes.
-            appView.rewriteWithLens(horizontalClassMergerResult.getGraphLens());
-            horizontalClassMergerResult
-                .getFieldAccessInfoCollectionModifier()
-                .modify(appViewWithLiveness);
 
-            appView.pruneItems(
-                PrunedItems.builder()
-                    .setPrunedApp(appView.appInfo().app())
-                    .addRemovedClasses(appView.horizontallyMergedClasses().getSources())
-                    .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getSources())
-                    .build());
-          }
-          timing.end();
-        } else {
-          appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty());
-        }
+        HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness)
+            .runIfNecessary(runtimeTypeCheckInfo, timing);
       }
 
       // Clear traced methods roots to not hold on to the main dex live method set.
@@ -596,6 +573,10 @@
                   new SubtypingInfo(appView),
                   keptGraphConsumer,
                   prunedTypes);
+          if (options.isClassMergingExtensionRequired(enqueuer.getMode())) {
+            classMergingEnqueuerExtensionBuilder = new RuntimeTypeCheckInfo.Builder(appView);
+            classMergingEnqueuerExtensionBuilder.attach(enqueuer);
+          }
           EnqueuerResult enqueuerResult =
               enqueuer.traceApplication(appView.rootSet(), executorService, timing);
           appView.setAppInfo(enqueuerResult.getAppInfo());
@@ -730,6 +711,15 @@
         SyntheticFinalization.finalizeWithClassHierarchy(appView);
       }
 
+      // Run horizontal class merging. This runs even if shrinking is disabled to ensure synthetics
+      // are always merged.
+      HorizontalClassMerger.createForFinalClassMerging(appView)
+          .runIfNecessary(
+              classMergingEnqueuerExtensionBuilder != null
+                  ? classMergingEnqueuerExtensionBuilder.build(appView.graphLens())
+                  : null,
+              timing);
+
       // Perform minification.
       NamingLens namingLens;
       if (options.getProguardConfiguration().hasApplyMappingFile()) {
@@ -968,7 +958,7 @@
               appView.dexItemFactory(), OptimizationFeedbackSimple.getInstance()));
     }
 
-    if (options.isClassMergingExtensionRequired()) {
+    if (options.isClassMergingExtensionRequired(enqueuer.getMode())) {
       classMergingEnqueuerExtensionBuilder.attach(enqueuer);
     }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index e3de56c..8deaa9c 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 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.DesugaredLibraryConfiguration;
@@ -32,6 +33,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -854,8 +856,12 @@
             ? LineNumberOptimization.ON
             : LineNumberOptimization.OFF;
 
+    HorizontalClassMergerOptions horizontalClassMergerOptions =
+        internal.horizontalClassMergerOptions();
     assert proguardConfiguration.isOptimizing()
-        || internal.horizontalClassMergerOptions().isDisabled();
+        || (!horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL)
+            && !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL));
+
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
     assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
     if (internal.debug) {
@@ -864,26 +870,19 @@
       internal.getProguardConfiguration().getKeepAttributes().localVariableTypeTable = true;
       internal.enableInlining = false;
       internal.enableClassInlining = false;
-      internal.horizontalClassMergerOptions().disable();
       internal.enableVerticalClassMerging = false;
       internal.enableClassStaticizer = false;
       internal.outline.enabled = false;
       internal.enableEnumUnboxing = false;
     }
+
     if (!internal.isShrinking()) {
       // If R8 is not shrinking, there is no point in running various optimizations since the
       // optimized classes will still remain in the program (the application size could increase).
       internal.enableEnumUnboxing = false;
-      internal.horizontalClassMergerOptions().disable();
       internal.enableVerticalClassMerging = false;
     }
 
-    if (!internal.enableInlining) {
-      // If R8 cannot perform inlining, then the synthetic constructors would not inline the called
-      // constructors, producing invalid code.
-      internal.horizontalClassMergerOptions().disable();
-    }
-
     // Amend the proguard-map consumer with options from the proguard configuration.
     internal.proguardMapConsumer =
         wrapStringConsumer(
@@ -939,7 +938,7 @@
     if (internal.isGeneratingClassFiles()) {
       internal.outline.enabled = false;
       internal.enableEnumUnboxing = false;
-      internal.horizontalClassMergerOptions().disable();
+      horizontalClassMergerOptions.disable();
     }
 
     // EXPERIMENTAL flags.
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 b3c5308..d9df6e8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
 import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 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;
@@ -505,6 +506,10 @@
     return collection;
   }
 
+  public boolean hasHorizontallyMergedClasses() {
+    return horizontallyMergedClasses != null;
+  }
+
   /**
    * Get the result of horizontal class merging. Returns null if horizontal class merging has not
    * been run.
@@ -513,10 +518,18 @@
     return horizontallyMergedClasses;
   }
 
-  public void setHorizontallyMergedClasses(HorizontallyMergedClasses horizontallyMergedClasses) {
-    assert this.horizontallyMergedClasses == null;
-    this.horizontallyMergedClasses = horizontallyMergedClasses;
-    testing().horizontallyMergedClassesConsumer.accept(dexItemFactory(), horizontallyMergedClasses);
+  public void setHorizontallyMergedClasses(
+      HorizontallyMergedClasses horizontallyMergedClasses, HorizontalClassMerger.Mode mode) {
+    if (mode.isInitial()) {
+      assert !hasHorizontallyMergedClasses();
+      this.horizontallyMergedClasses = horizontallyMergedClasses;
+      testing()
+          .horizontallyMergedClassesConsumer
+          .accept(dexItemFactory(), horizontallyMergedClasses);
+    } else {
+      // TODO(b/187496738): Enable final class merging.
+      assert horizontallyMergedClasses.isEmpty();
+    }
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index d82db4a..31b83b2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -20,6 +20,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.function.Predicate;
 
 public abstract class DexApplication {
 
@@ -177,6 +178,11 @@
       return self();
     }
 
+    public synchronized T removeProgramClasses(Predicate<DexProgramClass> predicate) {
+      this.programClasses.removeIf(predicate);
+      return self();
+    }
+
     public synchronized T replaceProgramClasses(Collection<DexProgramClass> newProgramClasses) {
       assert newProgramClasses != null;
       this.programClasses.clear();
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 c94039b..2397bdb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -7,6 +7,8 @@
 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.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.DontInlinePolicy;
@@ -24,6 +26,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
 import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
 import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
@@ -42,7 +45,9 @@
 import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy;
 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.utils.InternalOptions.HorizontalClassMergerOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -52,32 +57,58 @@
 
 public class HorizontalClassMerger {
 
-  // TODO(b/181846319): Add 'FINAL' mode that runs after synthetic finalization.
   public enum Mode {
-    INITIAL;
+    INITIAL,
+    FINAL;
 
     public boolean isInitial() {
       return this == INITIAL;
     }
+
+    public boolean isFinal() {
+      return this == FINAL;
+    }
   }
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final Mode mode = Mode.INITIAL;
+  private final Mode mode;
+  private final HorizontalClassMergerOptions options;
 
-  public HorizontalClassMerger(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  private HorizontalClassMerger(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
     this.appView = appView;
-    assert appView.options().enableInlining;
+    this.mode = mode;
+    this.options = appView.options().horizontalClassMergerOptions();
   }
 
-  public HorizontalClassMergerResult run(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
+  public static HorizontalClassMerger createForInitialClassMerging(
+      AppView<AppInfoWithLiveness> appView) {
+    return new HorizontalClassMerger(appView, Mode.INITIAL);
+  }
+
+  public static HorizontalClassMerger createForFinalClassMerging(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return new HorizontalClassMerger(appView, Mode.FINAL);
+  }
+
+  public void runIfNecessary(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
+    if (options.isEnabled(mode)) {
+      timing.begin("HorizontalClassMerger (" + mode.toString() + ")");
+      run(runtimeTypeCheckInfo, timing);
+      timing.end();
+    } else {
+      appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty(), mode);
+    }
+  }
+
+  private void run(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
     // Run the policies on all program classes to produce a final grouping.
     List<Policy> policies = getPolicies(runtimeTypeCheckInfo);
     Collection<MergeGroup> groups = new PolicyExecutor().run(getInitialGroups(), policies, timing);
 
     // If there are no groups, then end horizontal class merging.
     if (groups.isEmpty()) {
-      appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty());
-      return null;
+      appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty(), mode);
+      return;
     }
 
     HorizontalClassMergerGraphLens.Builder lensBuilder =
@@ -94,20 +125,38 @@
     // Generate the graph lens.
     HorizontallyMergedClasses mergedClasses =
         HorizontallyMergedClasses.builder().addMergeGroups(groups).build();
-    appView.setHorizontallyMergedClasses(mergedClasses);
-    HorizontalClassMergerGraphLens lens =
+    appView.setHorizontallyMergedClasses(mergedClasses, mode);
+
+    HorizontalClassMergerGraphLens horizontalClassMergerGraphLens =
         createLens(mergedClasses, lensBuilder, syntheticArgumentClass);
 
     // Prune keep info.
-    appView
-        .getKeepInfo()
-        .mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.getSources()));
+    KeepInfoCollection keepInfo = appView.getKeepInfo();
+    keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.getSources()));
 
-    return new HorizontalClassMergerResult(createFieldAccessInfoCollectionModifier(groups), lens);
+    // 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));
+
+    // Record where the synthesized $r8$classId fields are read and written.
+    if (mode.isInitial()) {
+      createFieldAccessInfoCollectionModifier(groups).modify(appView.withLiveness());
+    } else {
+      assert groups.stream().noneMatch(MergeGroup::hasClassIdField);
+    }
+
+    appView.pruneItems(
+        PrunedItems.builder()
+            .setPrunedApp(appView.appInfo().app())
+            .addRemovedClasses(mergedClasses.getSources())
+            .addNoLongerSyntheticItems(mergedClasses.getSources())
+            .build());
   }
 
   private FieldAccessInfoCollectionModifier createFieldAccessInfoCollectionModifier(
       Collection<MergeGroup> groups) {
+    assert mode.isInitial();
     FieldAccessInfoCollectionModifier.Builder builder =
         new FieldAccessInfoCollectionModifier.Builder();
     for (MergeGroup group : groups) {
@@ -125,14 +174,25 @@
     return builder.build();
   }
 
+  private DirectMappedDexApplication 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();
+  }
+
   private List<MergeGroup> getInitialGroups() {
     MergeGroup initialClassGroup = new MergeGroup();
     MergeGroup initialInterfaceGroup = new MergeGroup();
     for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
       if (clazz.isInterface()) {
-        if (appView.options().horizontalClassMergerOptions().isInterfaceMergingEnabled()) {
-          initialInterfaceGroup.add(clazz);
-        }
+        initialInterfaceGroup.add(clazz);
       } else {
         initialClassGroup.add(clazz);
       }
@@ -152,6 +212,7 @@
             new NoDeadEnumLiteMaps(appViewWithLiveness),
             new NoAnnotationClasses(),
             new NoEnums(appView),
+            new NoInterfaces(appView),
             new NoInnerClasses(),
             new NoInstanceFieldAnnotations(),
             new NoClassInitializerWithObservableSideEffects(),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java
deleted file mode 100644
index 820c24a..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging;
-
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-
-public class HorizontalClassMergerResult {
-
-  private final FieldAccessInfoCollectionModifier fieldAccessInfoCollectionModifier;
-  private final HorizontalClassMergerGraphLens graphLens;
-
-  HorizontalClassMergerResult(
-      FieldAccessInfoCollectionModifier fieldAccessInfoCollectionModifier,
-      HorizontalClassMergerGraphLens graphLens) {
-    this.fieldAccessInfoCollectionModifier = fieldAccessInfoCollectionModifier;
-    this.graphLens = graphLens;
-  }
-
-  public FieldAccessInfoCollectionModifier getFieldAccessInfoCollectionModifier() {
-    return fieldAccessInfoCollectionModifier;
-  }
-
-  public HorizontalClassMergerGraphLens getGraphLens() {
-    return graphLens;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
index 927e629..67d390b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -58,6 +58,10 @@
     return mergedClasses.containsKey(type);
   }
 
+  public boolean isEmpty() {
+    return mergedClasses.isEmpty();
+  }
+
   @Override
   public boolean isMergeTarget(DexType type) {
     return mergedClasses.containsValue(type);
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
new file mode 100644
index 0000000..a6babcd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
@@ -0,0 +1,35 @@
+// 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.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
+
+public class NoInterfaces extends SingleClassPolicy {
+
+  private final HorizontalClassMergerOptions options;
+
+  public NoInterfaces(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.options = appView.options().horizontalClassMergerOptions();
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass clazz) {
+    return !clazz.isInterface();
+  }
+
+  @Override
+  public boolean shouldSkipPolicy() {
+    return options.isInterfaceMergingEnabled();
+  }
+
+  @Override
+  public String getName() {
+    return "NoInterfaces";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
index fb3868a..fc2756a 100644
--- a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -33,18 +34,25 @@
       implements EnqueuerInstanceOfAnalysis,
           EnqueuerCheckCastAnalysis,
           EnqueuerExceptionGuardAnalysis {
+
+    private final GraphLens appliedGraphLens;
     private final DexItemFactory factory;
 
     private final Set<DexType> instanceOfTypes = Sets.newIdentityHashSet();
     private final Set<DexType> checkCastTypes = Sets.newIdentityHashSet();
     private final Set<DexType> exceptionGuardTypes = Sets.newIdentityHashSet();
 
-    public Builder(DexItemFactory factory) {
-      this.factory = factory;
+    public Builder(AppView<?> appView) {
+      this.appliedGraphLens = appView.graphLens();
+      this.factory = appView.dexItemFactory();
     }
 
-    public RuntimeTypeCheckInfo build() {
-      return new RuntimeTypeCheckInfo(instanceOfTypes, checkCastTypes, exceptionGuardTypes);
+    public RuntimeTypeCheckInfo build(GraphLens graphLens) {
+      RuntimeTypeCheckInfo runtimeTypeCheckInfo =
+          new RuntimeTypeCheckInfo(instanceOfTypes, checkCastTypes, exceptionGuardTypes);
+      return graphLens.isNonIdentityLens() && graphLens != appliedGraphLens
+          ? runtimeTypeCheckInfo.rewriteWithLens(graphLens.asNonIdentityLens())
+          : runtimeTypeCheckInfo;
     }
 
     @Override
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 74ed75a..f1a3bd9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.code.IRCode;
@@ -614,8 +615,16 @@
    * If any non-static class merging is enabled, information about types referred to by instanceOf
    * and check cast instructions needs to be collected.
    */
-  public boolean isClassMergingExtensionRequired() {
-    return horizontalClassMergerOptions.isEnabled() || enableVerticalClassMerging;
+  public boolean isClassMergingExtensionRequired(Enqueuer.Mode mode) {
+    if (mode.isInitialTreeShaking()) {
+      return horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL)
+          || enableVerticalClassMerging;
+    }
+    if (mode.isFinalTreeShaking()) {
+      return horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL);
+    }
+    assert false;
+    return false;
   }
 
   @Override
@@ -1190,7 +1199,7 @@
     }
   }
 
-  public static class HorizontalClassMergerOptions {
+  public class HorizontalClassMergerOptions {
 
     // TODO(b/138781768): Set enable to true when this bug is resolved.
     private boolean enable =
@@ -1226,12 +1235,16 @@
       return enableConstructorMerging;
     }
 
-    public boolean isDisabled() {
-      return !isEnabled();
-    }
-
-    public boolean isEnabled() {
-      return enable;
+    public boolean isEnabled(HorizontalClassMerger.Mode mode) {
+      if (!enable || debug || intermediate) {
+        return false;
+      }
+      if (mode.isInitial()) {
+        return enableInlining && isShrinking();
+      }
+      assert mode.isFinal();
+      // TODO(b/187496738): Enable final class merging.
+      return false;
     }
 
     public boolean isIgnoreRuntimeTypeChecksForTestingEnabled() {
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 1f82c8b..0a80034 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
@@ -21,6 +21,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -66,7 +67,7 @@
             .addHorizontallyMergedClassesInspector(
                 inspector -> {
                   HorizontalClassMergerOptions defaultHorizontalClassMergerOptions =
-                      new HorizontalClassMergerOptions();
+                      new InternalOptions().horizontalClassMergerOptions();
                   assertEquals(4833, inspector.getSources().size());
                   assertEquals(167, inspector.getTargets().size());
                   assertTrue(