Only generate class id fields for disambiguation when needed

Change-Id: I4a23e8da59567148a59ff9db08cf8756eda72d08
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 23cb57c..a7bae5d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -128,8 +128,9 @@
 
   public DexEncodedField[] merge() {
     List<DexEncodedField> newFields = new ArrayList<>();
-    assert classIdField != null;
-    newFields.add(classIdField);
+    if (classIdField != null) {
+      newFields.add(classIdField);
+    }
     fieldMappings.forEach(
         (targetField, oldFields) ->
             newFields.add(mergeSourceFieldsToTargetField(targetField, oldFields)));
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 ad47d8d..220d19e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
@@ -33,8 +34,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepClassInfo;
 import com.android.tools.r8.utils.IterableUtils;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -263,7 +262,10 @@
 
   public void mergeGroup(SyntheticArgumentClass syntheticArgumentClass) {
     fixAccessFlags();
-    appendClassIdField();
+
+    if (group.hasClassIdField()) {
+      appendClassIdField();
+    }
 
     mergeAnnotations();
     mergeInterfaces();
@@ -279,13 +281,6 @@
   public static class Builder {
     private final AppView<AppInfoWithLiveness> appView;
     private final MergeGroup group;
-    private final ClassInitializerSynthesizedCode.Builder classInitializerSynthesizedCodeBuilder =
-        new ClassInitializerSynthesizedCode.Builder();
-    private final Map<DexProto, ConstructorMerger.Builder> constructorMergerBuilders =
-        new LinkedHashMap<>();
-    private final List<ConstructorMerger.Builder> unmergedConstructorBuilders = new ArrayList<>();
-    private final Map<Wrapper<DexMethod>, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
-        new LinkedHashMap<>();
 
     public Builder(AppView<AppInfoWithLiveness> appView, MergeGroup group) {
       this.appView = appView;
@@ -314,84 +309,101 @@
       group.setTarget(appView.testing().horizontalClassMergingTarget.apply(candidates, target));
     }
 
-    private Builder setup() {
-      DexItemFactory dexItemFactory = appView.dexItemFactory();
-      // TODO(b/165498187): ensure the name for the field is fresh
-      group.setClassIdField(
-          dexItemFactory.createField(
-              group.getTarget().getType(), dexItemFactory.intType, CLASS_ID_FIELD_NAME));
-      group.forEach(this::setupForMethodMerging);
-      return this;
+    private ClassInitializerSynthesizedCode createClassInitializerMerger() {
+      ClassInitializerSynthesizedCode.Builder builder =
+          new ClassInitializerSynthesizedCode.Builder();
+      group.forEach(
+          clazz -> {
+            if (clazz.hasClassInitializer()) {
+              builder.add(clazz.getClassInitializer());
+            }
+          });
+      return builder.build();
     }
 
-    private void setupForMethodMerging(DexProgramClass toMerge) {
-      if (toMerge.hasClassInitializer()) {
-        classInitializerSynthesizedCodeBuilder.add(toMerge.getClassInitializer());
-      }
-      toMerge.forEachProgramDirectMethodMatching(
-          DexEncodedMethod::isInstanceInitializer, this::addConstructor);
-      toMerge.forEachProgramVirtualMethod(this::addVirtualMethod);
-    }
-
-    private void addConstructor(ProgramMethod method) {
-      assert method.getDefinition().isInstanceInitializer();
+    private List<ConstructorMerger> createInstanceInitializerMergers() {
+      List<ConstructorMerger> constructorMergers = new ArrayList<>();
       if (appView.options().horizontalClassMergerOptions().isConstructorMergingEnabled()) {
-        constructorMergerBuilders
-            .computeIfAbsent(
-                method.getDefinition().getProto(), ignore -> new ConstructorMerger.Builder(appView))
-            .add(method.getDefinition());
+        Map<DexProto, ConstructorMerger.Builder> buildersByProto = new LinkedHashMap<>();
+        group.forEach(
+            clazz ->
+                clazz.forEachProgramDirectMethodMatching(
+                    DexEncodedMethod::isInstanceInitializer,
+                    method ->
+                        buildersByProto
+                            .computeIfAbsent(
+                                method.getDefinition().getProto(),
+                                ignore -> new ConstructorMerger.Builder(appView))
+                            .add(method.getDefinition())));
+        for (ConstructorMerger.Builder builder : buildersByProto.values()) {
+          constructorMergers.addAll(builder.build(group));
+        }
       } else {
-        unmergedConstructorBuilders.add(
-            new ConstructorMerger.Builder(appView).add(method.getDefinition()));
+        group.forEach(
+            clazz ->
+                clazz.forEachProgramDirectMethodMatching(
+                    DexEncodedMethod::isInstanceInitializer,
+                    method ->
+                        constructorMergers.addAll(
+                            new ConstructorMerger.Builder(appView)
+                                .add(method.getDefinition())
+                                .build(group))));
       }
+
+      // Try and merge the constructors with the most arguments first, to avoid using synthetic
+      // arguments if possible.
+      constructorMergers.sort(Comparator.comparing(ConstructorMerger::getArity).reversed());
+      return constructorMergers;
     }
 
-    private void addVirtualMethod(ProgramMethod method) {
-      assert method.getDefinition().isNonPrivateVirtualMethod();
-      virtualMethodMergerBuilders
-          .computeIfAbsent(
-              MethodSignatureEquivalence.get().wrap(method.getReference()),
-              ignore -> new VirtualMethodMerger.Builder())
-          .add(method);
-    }
-
-    private Collection<ConstructorMerger.Builder> getConstructorMergerBuilders() {
-      return appView.options().horizontalClassMergerOptions().isConstructorMergingEnabled()
-          ? constructorMergerBuilders.values()
-          : unmergedConstructorBuilders;
-    }
-
-    public ClassMerger build(
-        HorizontalClassMergerGraphLens.Builder lensBuilder) {
-      selectTarget();
-      setup();
+    private List<VirtualMethodMerger> createVirtualMethodMergers() {
+      Map<DexMethodSignature, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
+          new LinkedHashMap<>();
+      group.forEach(
+          clazz ->
+              clazz.forEachProgramVirtualMethod(
+                  virtualMethod ->
+                      virtualMethodMergerBuilders
+                          .computeIfAbsent(
+                              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));
       }
-      // Try and merge the functions with the most arguments first, to avoid using synthetic
-      // arguments if possible.
-      virtualMethodMergers.sort(Comparator.comparing(VirtualMethodMerger::getArity).reversed());
+      return virtualMethodMergers;
+    }
 
-      List<ConstructorMerger> constructorMergers =
-          new ArrayList<>(constructorMergerBuilders.size());
-      for (ConstructorMerger.Builder builder : getConstructorMergerBuilders()) {
-        constructorMergers.addAll(builder.build(appView, group));
+    private void createClassIdField() {
+      // TODO(b/165498187): ensure the name for the field is fresh
+      DexItemFactory dexItemFactory = appView.dexItemFactory();
+      group.setClassIdField(
+          dexItemFactory.createField(
+              group.getTarget().getType(), dexItemFactory.intType, CLASS_ID_FIELD_NAME));
+    }
+
+    public ClassMerger build(
+        HorizontalClassMergerGraphLens.Builder lensBuilder) {
+      selectTarget();
+
+      List<VirtualMethodMerger> virtualMethodMergers = createVirtualMethodMergers();
+
+      boolean requiresClassIdField =
+          virtualMethodMergers.stream()
+              .anyMatch(virtualMethodMerger -> !virtualMethodMerger.isNopOrTrivial());
+      if (requiresClassIdField) {
+        createClassIdField();
       }
 
-      // Try and merge the functions with the most arguments first, to avoid using synthetic
-      // arguments if possible.
-      virtualMethodMergers.sort(Comparator.comparing(VirtualMethodMerger::getArity).reversed());
-      constructorMergers.sort(Comparator.comparing(ConstructorMerger::getArity).reversed());
-
       return new ClassMerger(
           appView,
           lensBuilder,
           group,
           virtualMethodMergers,
-          constructorMergers,
-          classInitializerSynthesizedCodeBuilder.build());
+          createInstanceInitializerMergers(),
+          createClassInitializerMerger());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
index 732a5d2..d650b17 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
@@ -49,6 +49,10 @@
     this.classIdField = classIdField;
   }
 
+  private boolean hasClassIdField() {
+    return classIdField != null;
+  }
+
   void addConstructorInvoke(DexMethod typeConstructor) {
     add(
         builder -> {
@@ -66,11 +70,13 @@
 
   /** Assign the given register to the class id field. */
   void addRegisterClassIdAssignment(int idRegister) {
+    assert hasClassIdField();
     add(builder -> builder.addInstancePut(idRegister, getReceiverRegister(), classIdField));
   }
 
   /** Assign the given constant integer value to the class id field. */
   void addConstantRegisterClassIdAssignment(int classId) {
+    assert hasClassIdField();
     int idRegister = nextRegister(ValueType.INT);
     add(builder -> builder.addIntConst(idRegister, classId));
     addRegisterClassIdAssignment(idRegister);
@@ -82,7 +88,9 @@
     // The class id register is always the first synthetic argument.
     int idRegister = getParamRegister(exampleTargetConstructor.getArity());
 
-    addRegisterClassIdAssignment(idRegister);
+    if (hasClassIdField()) {
+      addRegisterClassIdAssignment(idRegister);
+    }
 
     int[] keys = new int[typeConstructorCount - 1];
     int[] offsets = new int[typeConstructorCount - 1];
@@ -115,7 +123,9 @@
 
   protected void prepareSingleConstructorInstructions() {
     Entry<DexMethod> entry = typeConstructors.int2ReferenceEntrySet().first();
-    addConstantRegisterClassIdAssignment(entry.getIntKey());
+    if (hasClassIdField()) {
+      addConstantRegisterClassIdAssignment(entry.getIntKey());
+    }
     addConstructorInvoke(entry.getValue());
     add(IRBuilder::addReturn, endsBlock);
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index 13673fa..ecb4295 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -96,7 +96,7 @@
       return this;
     }
 
-    public List<ConstructorMerger> build(AppView<?> appView, MergeGroup group) {
+    public List<ConstructorMerger> build(MergeGroup group) {
       assert constructorGroups.stream().noneMatch(List::isEmpty);
       return ListUtils.map(
           constructorGroups, constructors -> new ConstructorMerger(appView, group, constructors));
@@ -185,7 +185,7 @@
         new ConstructorEntryPointSynthesizedCode(
             typeConstructorClassMap,
             newConstructorReference,
-            group.getClassIdField(),
+            group.hasClassIdField() ? group.getClassIdField() : null,
             bridgeConstructorReference);
     DexEncodedMethod newConstructor =
         new DexEncodedMethod(
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 7085879..557164c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -98,13 +98,16 @@
     FieldAccessInfoCollectionModifier.Builder builder =
         new FieldAccessInfoCollectionModifier.Builder();
     for (MergeGroup group : groups) {
-      DexProgramClass target = group.getTarget();
-      target.forEachProgramInstanceInitializerMatching(
-          definition -> definition.getCode().isHorizontalClassMergingCode(),
-          method -> builder.recordFieldWrittenInContext(group.getClassIdField(), method));
-      target.forEachProgramVirtualMethodMatching(
-          definition -> definition.hasCode() && definition.getCode().isHorizontalClassMergingCode(),
-          method -> builder.recordFieldReadInContext(group.getClassIdField(), method));
+      if (group.hasClassIdField()) {
+        DexProgramClass target = group.getTarget();
+        target.forEachProgramInstanceInitializerMatching(
+            definition -> definition.getCode().isHorizontalClassMergingCode(),
+            method -> builder.recordFieldWrittenInContext(group.getClassIdField(), method));
+        target.forEachProgramVirtualMethodMatching(
+            definition ->
+                definition.hasCode() && definition.getCode().isHorizontalClassMergingCode(),
+            method -> builder.recordFieldReadInContext(group.getClassIdField(), method));
+      }
     }
     return builder.build();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index c144128..91e6cc0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -171,6 +171,10 @@
     return numberOfNonAbstractMethods <= 1;
   }
 
+  boolean isNopOrTrivial() {
+    return isNop() || isTrivial();
+  }
+
   /**
    * If there is only a single method that does not override anything then it is safe to just move
    * it to the target type if it is not already in it.
@@ -221,12 +225,11 @@
     assert !methods.isEmpty();
 
     // Handle trivial merges.
-    if (isNop() || isTrivial()) {
+    if (isNopOrTrivial()) {
       mergeTrivial(classMethodsBuilder, lensBuilder);
       return;
     }
 
-
     Int2ReferenceSortedMap<DexMethod> classIdToMethodMap = new Int2ReferenceAVLTreeMap<>();
 
     CfVersion classFileVersion = null;