Revert "Merge equivalent instance initializers"

This reverts commit f1a7dfd4e7ace989f8be1dfa20c2dff89f004848.

Reason for revert: Failures

Change-Id: I4528bdd771c900f2a9306b294ff10559a979dd50
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index cd8d6b7..592ee31 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -42,10 +42,6 @@
     spec.withInt(f -> f.opcode).withItem(f -> f.field).withItem(f -> f.declaringField);
   }
 
-  public CfFieldInstruction(int opcode, DexField field) {
-    this(opcode, field, field);
-  }
-
   public CfFieldInstruction(int opcode, DexField field, DexField declaringField) {
     this.opcode = opcode;
     this.field = field;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index a42388a..646c9a0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2238,10 +2238,6 @@
     return createMethod(holder, createProto(voidType), classConstructorMethodName);
   }
 
-  public DexMethod createInstanceInitializer(DexType holder, DexType... parameters) {
-    return createMethod(holder, createProto(voidType, parameters), constructorMethodName);
-  }
-
   public DexMethod createInstanceInitializerWithFreshProto(
       DexMethod method, List<DexType> extraTypes, Predicate<DexMethod> isFresh) {
     assert method.isInstanceInitializer(this);
@@ -2262,6 +2258,8 @@
 
   private DexMethod createInstanceInitializerWithFreshProto(
       DexProto proto, List<DexType> extraTypes, Function<DexProto, Optional<DexMethod>> isFresh) {
+    assert !extraTypes.isEmpty();
+
     Queue<Iterable<DexProto>> tryProtos = new LinkedList<>();
     Iterator<DexProto> current = IterableUtils.singleton(proto).iterator();
 
@@ -2278,7 +2276,6 @@
       if (object.isPresent()) {
         return object.get();
       }
-      assert !extraTypes.isEmpty();
       tryProtos.add(
           Iterables.transform(extraTypes, extraType -> appendTypeToProto(tryProto, extraType)));
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 96c3084..a38770a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -93,11 +93,6 @@
     return descriptor;
   }
 
-  public int getRequiredRegisters() {
-    assert !isVoidType();
-    return isWideType() ? 2 : 1;
-  }
-
   @Override
   public int computeHashCode() {
     return descriptor.hashCode();
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
deleted file mode 100644
index b6d9757..0000000
--- a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2021, 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.graph;
-
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
-import com.android.tools.r8.utils.ObjectUtils;
-import java.util.Iterator;
-
-public class DexTypeUtils {
-
-  public static DexType computeLeastUpperBound(
-      AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexType> types) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-
-    Iterator<DexType> iterator = types.iterator();
-    assert iterator.hasNext();
-
-    DexType result = iterator.next();
-    while (iterator.hasNext()) {
-      result = computeLeastUpperBound(appView, result, iterator.next());
-    }
-    return result;
-  }
-
-  public static DexType computeLeastUpperBound(
-      AppView<? extends AppInfoWithClassHierarchy> appView, DexType type, DexType other) {
-    if (type == other) {
-      return type;
-    }
-
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    if (type == dexItemFactory.objectType
-        || other == dexItemFactory.objectType
-        || type.isArrayType() != other.isArrayType()) {
-      return dexItemFactory.objectType;
-    }
-
-    if (type.isArrayType()) {
-      assert other.isArrayType();
-      int arrayDimension = type.getNumberOfLeadingSquareBrackets();
-      if (other.getNumberOfLeadingSquareBrackets() != arrayDimension) {
-        return dexItemFactory.objectType;
-      }
-
-      DexType baseType = type.toBaseType(dexItemFactory);
-      DexType otherBaseType = other.toBaseType(dexItemFactory);
-      if (baseType.isPrimitiveType() || otherBaseType.isPrimitiveType()) {
-        assert baseType != otherBaseType;
-        return dexItemFactory.objectType;
-      }
-
-      return dexItemFactory.createArrayType(
-          arrayDimension, computeLeastUpperBound(appView, baseType, otherBaseType));
-    }
-
-    assert !type.isArrayType();
-    assert !other.isArrayType();
-
-    boolean isInterface =
-        type.isClassType()
-            && ObjectUtils.getBooleanOrElse(
-                appView.definitionFor(type), DexClass::isInterface, false);
-    boolean otherIsInterface =
-        other.isClassType()
-            && ObjectUtils.getBooleanOrElse(
-                appView.definitionFor(other), DexClass::isInterface, false);
-    if (isInterface != otherIsInterface) {
-      return dexItemFactory.objectType;
-    }
-
-    if (isInterface) {
-      assert otherIsInterface;
-      InterfaceCollection interfaceCollection =
-          ClassTypeElement.computeLeastUpperBoundOfInterfaces(
-              appView, InterfaceCollection.singleton(type), InterfaceCollection.singleton(other));
-      return interfaceCollection.hasSingleKnownInterface()
-          ? interfaceCollection.getSingleKnownInterface()
-          : dexItemFactory.objectType;
-    }
-
-    assert !isInterface;
-    assert !otherIsInterface;
-
-    return ClassTypeElement.computeLeastUpperBoundOfClasses(appView.appInfo(), type, other);
-  }
-}
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 3927d11..999869c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -62,18 +62,13 @@
   private final Mode mode;
   private final MergeGroup group;
   private final DexItemFactory dexItemFactory;
+  private final ClassInitializerMerger classInitializerMerger;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
 
   private final ClassMethodsBuilder classMethodsBuilder = new ClassMethodsBuilder();
   private final Reference2IntMap<DexType> classIdentifiers = new Reference2IntOpenHashMap<>();
-
-  // Field mergers.
-  private final ClassInstanceFieldsMerger classInstanceFieldsMerger;
   private final ClassStaticFieldsMerger classStaticFieldsMerger;
-
-  // Method mergers.
-  private final ClassInitializerMerger classInitializerMerger;
-  private final InstanceInitializerMergerCollection instanceInitializerMergers;
+  private final ClassInstanceFieldsMerger classInstanceFieldsMerger;
   private final Collection<VirtualMethodMerger> virtualMethodMergers;
 
   private ClassMerger(
@@ -87,18 +82,10 @@
     this.group = group;
     this.lensBuilder = lensBuilder;
     this.mode = mode;
-
-    // Field mergers.
+    this.virtualMethodMergers = virtualMethodMergers;
+    this.classInitializerMerger = ClassInitializerMerger.create(group);
     this.classStaticFieldsMerger = new ClassStaticFieldsMerger(appView, lensBuilder, group);
     this.classInstanceFieldsMerger = new ClassInstanceFieldsMerger(appView, lensBuilder, group);
-
-    // Method mergers.
-    this.classInitializerMerger = ClassInitializerMerger.create(group);
-    this.instanceInitializerMergers =
-        InstanceInitializerMergerCollection.create(
-            appView, classIdentifiers, group, classInstanceFieldsMerger, lensBuilder, mode);
-    this.virtualMethodMergers = virtualMethodMergers;
-
     buildClassIdentifierMap();
   }
 
@@ -188,8 +175,12 @@
   }
 
   void mergeInstanceInitializers(SyntheticArgumentClass syntheticArgumentClass) {
+    InstanceInitializerMergerCollection instanceInitializerMergers =
+        InstanceInitializerMergerCollection.create(appView, group, lensBuilder, mode);
     instanceInitializerMergers.forEach(
-        merger -> merger.merge(classMethodsBuilder, syntheticArgumentClass));
+        merger ->
+            merger.merge(
+                classMethodsBuilder, lensBuilder, classIdentifiers, syntheticArgumentClass));
   }
 
   void mergeMethods(
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 4ad6f55..c05af00 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -212,9 +212,6 @@
     for (ClassMerger merger : classMergers) {
       merger.mergeGroup(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
     }
-
-    // Clear type elements cache after IR building.
-    appView.dexItemFactory().clearTypeElementsCache();
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerCfCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerCfCode.java
deleted file mode 100644
index 839b6d5..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerCfCode.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2021, 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.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfTryCatch;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexType;
-import java.util.List;
-
-/**
- * Similar to CfCode, but with a marker that makes it possible to recognize this is synthesized by
- * the horizontal class merger.
- */
-public class HorizontalClassMergerCfCode extends CfCode {
-
-  HorizontalClassMergerCfCode(
-      DexType originalHolder,
-      int maxStack,
-      int maxLocals,
-      List<CfInstruction> instructions,
-      List<CfTryCatch> tryCatchRanges,
-      List<LocalVariableInfo> localVariables) {
-    super(originalHolder, maxStack, maxLocals, instructions, tryCatchRanges, localVariables);
-  }
-
-  @Override
-  public boolean isHorizontalClassMergingCode() {
-    return true;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index 20eb5a3..abadbc4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -120,11 +120,6 @@
           newMethodSignatures);
     }
 
-    DexMethod getRenamedMethodSignature(DexMethod method) {
-      assert newMethodSignatures.containsKey(method);
-      return newMethodSignatures.get(method);
-    }
-
     void recordNewFieldSignature(DexField oldFieldSignature, DexField newFieldSignature) {
       fieldMap.put(oldFieldSignature, newFieldSignature);
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
index 9a6390a..febdbe6 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
@@ -4,204 +4,14 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
-import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
-import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
-import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
-import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
-import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
-import static com.android.tools.r8.ir.code.Opcodes.GOTO;
-import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
-import static com.android.tools.r8.ir.code.Opcodes.RETURN;
-
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstancePut;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
-import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
-import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.WorkList;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import java.util.ArrayList;
-import java.util.List;
 
 public class InstanceInitializerAnalysis {
 
   public static InstanceInitializerDescription analyze(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      MergeGroup group,
-      ProgramMethod instanceInitializer,
-      ClassInstanceFieldsMerger instanceFieldsMerger,
-      Mode mode) {
-    InstanceInitializerDescription.Builder builder =
-        InstanceInitializerDescription.builder(appView, instanceInitializer);
-
-    if (mode.isFinal()) {
-      // TODO(b/189296638): We can't build IR in the final round of class merging without simulating
-      //  that we're in D8.
-      MethodOptimizationInfo optimizationInfo =
-          instanceInitializer.getDefinition().getOptimizationInfo();
-      if (optimizationInfo.mayHaveSideEffects()) {
-        return null;
-      }
-      InstanceInitializerInfo instanceInitializerInfo =
-          optimizationInfo.getContextInsensitiveInstanceInitializerInfo();
-      if (!instanceInitializerInfo.hasParent()) {
-        // We don't know the parent constructor of the first constructor.
-        return null;
-      }
-      DexMethod parent = instanceInitializerInfo.getParent();
-      if (parent.getArity() > 0) {
-        return null;
-      }
-      builder.addInvokeConstructor(parent, ImmutableList.of());
-      return builder.build();
-    }
-
-    IRCode code = instanceInitializer.buildIR(appView);
-    WorkList<BasicBlock> workList = WorkList.newIdentityWorkList(code.entryBlock());
-    while (workList.hasNext()) {
-      BasicBlock block = workList.next();
-      for (Instruction instruction : block.getInstructions()) {
-        switch (instruction.opcode()) {
-          case ARGUMENT:
-          case ASSUME:
-          case CONST_CLASS:
-          case CONST_NUMBER:
-          case CONST_STRING:
-          case DEX_ITEM_BASED_CONST_STRING:
-          case RETURN:
-            break;
-
-          case GOTO:
-            if (!workList.addIfNotSeen(instruction.asGoto().getTarget())) {
-              return invalid();
-            }
-            break;
-
-          case INSTANCE_PUT:
-            {
-              InstancePut instancePut = instruction.asInstancePut();
-
-              // This must initialize a field on the receiver.
-              if (!instancePut.object().getAliasedValue().isThis()) {
-                return invalid();
-              }
-
-              // Check that this writes a field on the enclosing class.
-              DexField fieldReference = instancePut.getField();
-              DexField lensRewrittenFieldReference =
-                  appView.graphLens().lookupField(fieldReference);
-              if (lensRewrittenFieldReference.getHolderType()
-                  != instanceInitializer.getHolderType()) {
-                return invalid();
-              }
-
-              ProgramField sourceField =
-                  instanceInitializer.getHolder().lookupProgramField(lensRewrittenFieldReference);
-              if (sourceField == null) {
-                return invalid();
-              }
-
-              InstanceFieldInitializationInfo initializationInfo =
-                  getInitializationInfo(instancePut.value(), appView, instanceInitializer);
-              if (initializationInfo == null) {
-                return invalid();
-              }
-
-              ProgramField targetField = instanceFieldsMerger.getTargetField(sourceField);
-              assert targetField != null;
-
-              builder.addInstancePut(targetField.getReference(), initializationInfo);
-              break;
-            }
-
-          case INVOKE_DIRECT:
-            {
-              InvokeDirect invoke = instruction.asInvokeDirect();
-
-              // This must initialize the receiver.
-              if (!invoke.getReceiver().getAliasedValue().isThis()) {
-                return invalid();
-              }
-
-              DexMethod invokedMethod = invoke.getInvokedMethod();
-              DexMethod lensRewrittenInvokedMethod =
-                  appView
-                      .graphLens()
-                      .lookupInvokeDirect(invokedMethod, instanceInitializer)
-                      .getReference();
-
-              // TODO(b/189296638): Consider allowing constructor forwarding.
-              if (!lensRewrittenInvokedMethod.isInstanceInitializer(appView.dexItemFactory())
-                  || lensRewrittenInvokedMethod.getHolderType() != group.getSuperType()) {
-                return invalid();
-              }
-
-              // Extract the non-receiver arguments.
-              List<InstanceFieldInitializationInfo> arguments =
-                  new ArrayList<>(invoke.arguments().size() - 1);
-              for (Value argument : Iterables.skip(invoke.arguments(), 1)) {
-                InstanceFieldInitializationInfo initializationInfo =
-                    getInitializationInfo(argument, appView, instanceInitializer);
-                if (initializationInfo == null) {
-                  return invalid();
-                }
-                arguments.add(initializationInfo);
-              }
-
-              if (!builder.addInvokeConstructor(invokedMethod, arguments)) {
-                return invalid();
-              }
-            }
-            break;
-
-          default:
-            // Not allowed.
-            return invalid();
-        }
-      }
-    }
-
-    return builder.isValid() ? builder.build() : null;
-  }
-
-  private static InstanceFieldInitializationInfo getInitializationInfo(
-      Value value,
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      ProgramMethod instanceInitializer) {
-    InstanceFieldInitializationInfoFactory factory =
-        appView.instanceFieldInitializationInfoFactory();
-
-    Value root = value.getAliasedValue();
-    if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
-      return factory.createArgumentInitializationInfo(
-          value.getDefinition().asArgument().getIndex());
-    }
-
-    AbstractValue abstractValue = value.getAbstractValue(appView, instanceInitializer);
-    if (abstractValue.isSingleConstValue()) {
-      return abstractValue.asSingleConstValue();
-    }
-
-    return null;
-  }
-
-  private static InstanceInitializerDescription invalid() {
+      ProgramMethod instanceInitializer, HorizontalClassMergerGraphLens.Builder lensBuilder) {
+    // TODO(b/189296638): Return an InstanceInitializerDescription if the given instance initializer
+    //  is parent constructor call followed by a sequence of instance field puts.
     return null;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
index b67c652..41f2c4e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -4,44 +4,14 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import com.android.tools.r8.cf.code.CfConstClass;
-import com.android.tools.r8.cf.code.CfConstNull;
-import com.android.tools.r8.cf.code.CfConstNumber;
-import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfLabel;
-import com.android.tools.r8.cf.code.CfLoad;
-import com.android.tools.r8.cf.code.CfPosition;
-import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.InvokeDirectRange;
-import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.value.SingleConstValue;
-import com.android.tools.r8.ir.analysis.value.SingleDexItemBasedStringValue;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
-import com.android.tools.r8.utils.IntBox;
-import com.android.tools.r8.utils.IterableUtils;
-import com.google.common.collect.ImmutableList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import org.objectweb.asm.Opcodes;
 
 /**
  * A simple abstraction of an instance initializer's code, which allows a parent constructor call
@@ -49,219 +19,24 @@
  */
 public class InstanceInitializerDescription {
 
-  // Field assignments that happen prior to the parent constructor call.
-  //
-  // Most fields are generally assigned after the parent constructor call, but both javac and
-  // kotlinc may assign instance fields prior to the parent constructor call. For example, the
-  // synthetic this$0 field for non-static inner classes is typically assigned prior to the parent
-  // constructor call.
-  private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre;
-
-  // Field assignments that happens after the parent constructor call.
-  private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost;
-
-  // The parent constructor method and the arguments passed to it.
+  private final int arity;
+  private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments;
   private final DexMethod parentConstructor;
   private final List<InstanceFieldInitializationInfo> parentConstructorArguments;
 
-  // The constructor parameters, where reference types have been mapped to java.lang.Object, to
-  // ensure we don't group constructors such as <init>(int) and <init>(Object), since this would
-  // lead to type errors.
-  private final DexTypeList relaxedParameters;
-
   InstanceInitializerDescription(
-      Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre,
-      Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost,
+      int arity,
+      Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments,
       DexMethod parentConstructor,
-      List<InstanceFieldInitializationInfo> parentConstructorArguments,
-      DexTypeList relaxedParameters) {
-    this.instanceFieldAssignmentsPre = instanceFieldAssignmentsPre;
-    this.instanceFieldAssignmentsPost = instanceFieldAssignmentsPost;
+      List<InstanceFieldInitializationInfo> parentConstructorArguments) {
+    this.arity = arity;
+    this.instanceFieldAssignments = instanceFieldAssignments;
     this.parentConstructor = parentConstructor;
     this.parentConstructorArguments = parentConstructorArguments;
-    this.relaxedParameters = relaxedParameters;
   }
 
-  public static Builder builder(
-      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod instanceInitializer) {
-    return new Builder(appView.dexItemFactory(), instanceInitializer);
-  }
-
-  /**
-   * Transform this description into actual CF code.
-   *
-   * @param newMethodReference the reference of the method that is being synthesized
-   * @param originalMethodReference the original reference of the representative method
-   * @param syntheticMethodReference the original, synthetic reference of the new method reference
-   *     ($r8$init$synthetic)
-   */
-  public CfCode createCfCode(
-      DexMethod newMethodReference,
-      DexMethod originalMethodReference,
-      DexMethod syntheticMethodReference,
-      MergeGroup group,
-      boolean hasClassId,
-      int extraNulls) {
-    int[] argumentToLocalIndex =
-        new int[newMethodReference.getParameters().size() + 1 - extraNulls];
-    int maxLocals = 0;
-    argumentToLocalIndex[0] = maxLocals++;
-    for (int i = 1; i < argumentToLocalIndex.length; i++) {
-      argumentToLocalIndex[i] = maxLocals;
-      maxLocals += newMethodReference.getParameter(i - 1).getRequiredRegisters();
-    }
-
-    IntBox maxStack = new IntBox();
-    ImmutableList.Builder<CfInstruction> instructionBuilder = ImmutableList.builder();
-
-    // Set position.
-    Position callerPosition = Position.synthetic(0, syntheticMethodReference, null);
-    Position calleePosition = Position.synthetic(0, originalMethodReference, callerPosition);
-    CfPosition position = new CfPosition(new CfLabel(), calleePosition);
-    instructionBuilder.add(position);
-    instructionBuilder.add(position.getLabel());
-
-    // Assign class id.
-    if (group.hasClassIdField()) {
-      assert hasClassId;
-      int classIdLocalIndex = maxLocals - 1;
-      instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
-      instructionBuilder.add(new CfLoad(ValueType.INT, classIdLocalIndex));
-      instructionBuilder.add(new CfFieldInstruction(Opcodes.PUTFIELD, group.getClassIdField()));
-      maxStack.set(2);
-    } else {
-      assert !hasClassId;
-    }
-
-    // Assign each field.
-    addCfInstructionsForInstanceFieldAssignments(
-        instructionBuilder, instanceFieldAssignmentsPre, argumentToLocalIndex, maxStack);
-
-    // Load receiver for parent constructor call.
-    int stackHeightForParentConstructorCall = 1;
-    instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
-
-    // Load constructor arguments.
-    int i = 0;
-    for (InstanceFieldInitializationInfo initializationInfo : parentConstructorArguments) {
-      stackHeightForParentConstructorCall +=
-          addCfInstructionsForInitializationInfo(
-              instructionBuilder,
-              initializationInfo,
-              argumentToLocalIndex,
-              parentConstructor.getParameter(i));
-      i++;
-    }
-
-    // Invoke parent constructor.
-    instructionBuilder.add(new CfInvoke(Opcodes.INVOKESPECIAL, parentConstructor, false));
-    maxStack.setMax(stackHeightForParentConstructorCall);
-
-    // Assign each field.
-    addCfInstructionsForInstanceFieldAssignments(
-        instructionBuilder, instanceFieldAssignmentsPost, argumentToLocalIndex, maxStack);
-
-    // Return.
-    instructionBuilder.add(new CfReturnVoid());
-
-    return new HorizontalClassMergerCfCode(
-        newMethodReference.getHolderType(),
-        maxStack.get(),
-        maxLocals,
-        instructionBuilder.build(),
-        ImmutableList.of(),
-        ImmutableList.of());
-  }
-
-  private static void addCfInstructionsForInstanceFieldAssignments(
-      ImmutableList.Builder<CfInstruction> instructionBuilder,
-      Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments,
-      int[] argumentToLocalIndex,
-      IntBox maxStack) {
-    instanceFieldAssignments.forEach(
-        (field, initializationInfo) -> {
-          // Load the receiver, the field value, and then set the field.
-          instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
-          int stackSizeForInitializationInfo =
-              addCfInstructionsForInitializationInfo(
-                  instructionBuilder, initializationInfo, argumentToLocalIndex, field.getType());
-          instructionBuilder.add(new CfFieldInstruction(Opcodes.PUTFIELD, field));
-          maxStack.setMax(stackSizeForInitializationInfo + 1);
-        });
-  }
-
-  private static int addCfInstructionsForInitializationInfo(
-      ImmutableList.Builder<CfInstruction> instructionBuilder,
-      InstanceFieldInitializationInfo initializationInfo,
-      int[] argumentToLocalIndex,
-      DexType type) {
-    if (initializationInfo.isArgumentInitializationInfo()) {
-      int argumentIndex = initializationInfo.asArgumentInitializationInfo().getArgumentIndex();
-      instructionBuilder.add(
-          new CfLoad(ValueType.fromDexType(type), argumentToLocalIndex[argumentIndex]));
-      return type.getRequiredRegisters();
-    }
-
-    assert initializationInfo.isSingleValue();
-    assert initializationInfo.asSingleValue().isSingleConstValue();
-
-    SingleConstValue singleConstValue = initializationInfo.asSingleValue().asSingleConstValue();
-    if (singleConstValue.isSingleConstClassValue()) {
-      instructionBuilder.add(
-          new CfConstClass(singleConstValue.asSingleConstClassValue().getType()));
-      return 1;
-    } else if (singleConstValue.isSingleDexItemBasedStringValue()) {
-      SingleDexItemBasedStringValue dexItemBasedStringValue =
-          singleConstValue.asSingleDexItemBasedStringValue();
-      instructionBuilder.add(
-          new CfDexItemBasedConstString(
-              dexItemBasedStringValue.getItem(), dexItemBasedStringValue.getNameComputationInfo()));
-      return 1;
-    } else if (singleConstValue.isSingleNumberValue()) {
-      if (type.isReferenceType()) {
-        assert singleConstValue.isNull();
-        instructionBuilder.add(new CfConstNull());
-        return 1;
-      } else {
-        instructionBuilder.add(
-            new CfConstNumber(
-                singleConstValue.asSingleNumberValue().getValue(), ValueType.fromDexType(type)));
-        return type.getRequiredRegisters();
-      }
-    } else {
-      assert singleConstValue.isSingleStringValue();
-      instructionBuilder.add(
-          new CfConstString(singleConstValue.asSingleStringValue().getDexString()));
-      return 1;
-    }
-  }
-
-  public DexCode createDexCode(
-      DexMethod newMethodReference,
-      DexMethod originalMethodReference,
-      DexMethod syntheticMethodReference,
-      MergeGroup group,
-      boolean hasClassId,
-      int extraNulls) {
-    assert !hasClassId;
-    assert extraNulls == 0;
-    assert parentConstructorArguments.isEmpty();
-    assert instanceFieldAssignmentsPre.isEmpty();
-    assert instanceFieldAssignmentsPost.isEmpty();
-    Instruction[] instructions = new Instruction[2];
-    instructions[0] = new InvokeDirectRange(0, 1, parentConstructor);
-    instructions[1] = new ReturnVoid();
-    int incomingRegisterSize =
-        1 + IterableUtils.sumInt(newMethodReference.getParameters(), DexType::getRequiredRegisters);
-    int outgoingRegisterSize = 1;
-    return new DexCode(
-        incomingRegisterSize,
-        incomingRegisterSize,
-        outgoingRegisterSize,
-        instructions,
-        new DexCode.Try[0],
-        new DexCode.TryHandler[0],
-        null);
+  public static Builder builder(ProgramMethod instanceInitializer) {
+    return new Builder(instanceInitializer);
   }
 
   @Override
@@ -270,8 +45,8 @@
       return false;
     }
     InstanceInitializerDescription description = (InstanceInitializerDescription) obj;
-    return instanceFieldAssignmentsPre.equals(description.instanceFieldAssignmentsPre)
-        && instanceFieldAssignmentsPost.equals(description.instanceFieldAssignmentsPost)
+    return arity == description.arity
+        && instanceFieldAssignments.equals(description.instanceFieldAssignments)
         && parentConstructor == description.parentConstructor
         && parentConstructorArguments.equals(description.parentConstructorArguments);
   }
@@ -279,67 +54,38 @@
   @Override
   public int hashCode() {
     return Objects.hash(
-        instanceFieldAssignmentsPre,
-        instanceFieldAssignmentsPost,
-        parentConstructor,
-        parentConstructorArguments,
-        relaxedParameters);
+        arity, instanceFieldAssignments, parentConstructor, parentConstructorArguments);
   }
 
   public static class Builder {
 
-    private final DexItemFactory dexItemFactory;
-    private final DexTypeList relaxedParameters;
+    private final int arity;
 
-    private Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre =
-        new LinkedHashMap<>();
-    private Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost =
+    private Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments =
         new LinkedHashMap<>();
     private DexMethod parentConstructor;
     private List<InstanceFieldInitializationInfo> parentConstructorArguments;
 
-    Builder(DexItemFactory dexItemFactory, ProgramMethod method) {
-      this.dexItemFactory = dexItemFactory;
-      this.relaxedParameters =
-          method
-              .getParameters()
-              .map(
-                  parameter -> parameter.isPrimitiveType() ? parameter : dexItemFactory.objectType);
+    Builder(ProgramMethod method) {
+      this.arity = method.getReference().getArity();
     }
 
     public void addInstancePut(DexField field, InstanceFieldInitializationInfo value) {
-      // If the parent constructor is java.lang.Object.<init>() then group all the field assignments
-      // before the parent constructor call to allow more sharing.
-      if (parentConstructor == null
-          || parentConstructor == dexItemFactory.objectMembers.constructor) {
-        instanceFieldAssignmentsPre.put(field, value);
-      } else {
-        instanceFieldAssignmentsPost.put(field, value);
-      }
+      instanceFieldAssignments.put(field, value);
     }
 
-    public boolean addInvokeConstructor(
+    public void addInvokeConstructor(
         DexMethod method, List<InstanceFieldInitializationInfo> arguments) {
-      if (parentConstructor == null) {
-        parentConstructor = method;
-        parentConstructorArguments = arguments;
-        return true;
-      }
-      return false;
+      assert parentConstructor == null;
+      assert parentConstructorArguments == null;
+      parentConstructor = method;
+      parentConstructorArguments = arguments;
     }
 
     public InstanceInitializerDescription build() {
-      assert isValid();
+      assert parentConstructor != null;
       return new InstanceInitializerDescription(
-          instanceFieldAssignmentsPre,
-          instanceFieldAssignmentsPost,
-          parentConstructor,
-          parentConstructorArguments,
-          relaxedParameters);
-    }
-
-    public boolean isValid() {
-      return parentConstructor != null;
+          arity, instanceFieldAssignments, parentConstructor, parentConstructorArguments);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 862a23c..b50c71f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -10,13 +10,11 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
 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.DexType;
-import com.android.tools.r8.graph.DexTypeUtils;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -26,62 +24,61 @@
 import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
-import com.android.tools.r8.utils.ArrayUtils;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.structural.Ordered;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 
 public class InstanceInitializerMerger {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final Reference2IntMap<DexType> classIdentifiers;
+  private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
   private final MergeGroup group;
   private final List<ProgramMethod> instanceInitializers;
   private final InstanceInitializerDescription instanceInitializerDescription;
-  private final HorizontalClassMergerGraphLens.Builder lensBuilder;
   private final Mode mode;
 
   InstanceInitializerMerger(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      Reference2IntMap<DexType> classIdentifiers,
-      MergeGroup group,
-      List<ProgramMethod> instanceInitializers,
-      HorizontalClassMergerGraphLens.Builder lensBuilder,
-      Mode mode) {
-    this(appView, classIdentifiers, group, instanceInitializers, lensBuilder, mode, null);
+      AppView<?> appView, MergeGroup group, List<ProgramMethod> instanceInitializers, Mode mode) {
+    this(appView, group, instanceInitializers, mode, null);
   }
 
   InstanceInitializerMerger(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      Reference2IntMap<DexType> classIdentifiers,
+      AppView<?> appView,
       MergeGroup group,
       List<ProgramMethod> instanceInitializers,
-      HorizontalClassMergerGraphLens.Builder lensBuilder,
       Mode mode,
       InstanceInitializerDescription instanceInitializerDescription) {
     this.appView = appView;
-    this.classIdentifiers = classIdentifiers;
     this.dexItemFactory = appView.dexItemFactory();
     this.group = group;
     this.instanceInitializers = instanceInitializers;
     this.instanceInitializerDescription = instanceInitializerDescription;
-    this.lensBuilder = lensBuilder;
     this.mode = mode;
 
-    // Constructors should not be empty and all constructors should have the same prototype unless
-    // equivalent.
+    // Constructors should not be empty and all constructors should have the same prototype.
     assert !instanceInitializers.isEmpty();
-    assert instanceInitializers.stream().map(ProgramMethod::getProto).distinct().count() == 1
-        || instanceInitializerDescription != null;
+    assert instanceInitializers.stream().map(ProgramMethod::getProto).distinct().count() == 1;
+  }
+
+  /**
+   * The method reference template describes which arguments the constructor must have, and is used
+   * to generate the final reference by appending null arguments until it is fresh.
+   */
+  private DexMethod generateReferenceMethodTemplate() {
+    DexMethod methodTemplate = instanceInitializers.iterator().next().getReference();
+    if (instanceInitializers.size() > 1) {
+      methodTemplate = dexItemFactory.appendTypeToMethod(methodTemplate, dexItemFactory.intType);
+    }
+    return methodTemplate.withHolder(group.getTarget(), dexItemFactory);
   }
 
   public int getArity() {
@@ -92,78 +89,6 @@
     return instanceInitializers;
   }
 
-  private CfVersion getNewClassFileVersion() {
-    CfVersion classFileVersion = null;
-    for (ProgramMethod instanceInitializer : instanceInitializers) {
-      if (instanceInitializer.getDefinition().hasClassFileVersion()) {
-        classFileVersion =
-            Ordered.maxIgnoreNull(
-                classFileVersion, instanceInitializer.getDefinition().getClassFileVersion());
-      }
-    }
-    return classFileVersion;
-  }
-
-  private DexMethod getNewMethodReference(ProgramMethod representative) {
-    return getNewMethodReference(representative, false);
-  }
-
-  private DexMethod getNewMethodReference(ProgramMethod representative, boolean needsClassId) {
-    DexType[] oldParameters = representative.getParameters().values;
-    DexType[] newParameters =
-        new DexType[representative.getParameters().size() + BooleanUtils.intValue(needsClassId)];
-    System.arraycopy(oldParameters, 0, newParameters, 0, oldParameters.length);
-    for (int i = 0; i < oldParameters.length; i++) {
-      final int parameterIndex = i;
-      newParameters[i] =
-          DexTypeUtils.computeLeastUpperBound(
-              appView,
-              Iterables.transform(
-                  instanceInitializers,
-                  instanceInitializer -> instanceInitializer.getParameter(parameterIndex)));
-    }
-    if (needsClassId) {
-      assert ArrayUtils.last(newParameters) == null;
-      newParameters[newParameters.length - 1] = dexItemFactory.intType;
-    }
-    return dexItemFactory.createInstanceInitializer(group.getTarget().getType(), newParameters);
-  }
-
-  private DexMethod getOriginalMethodReference() {
-    return appView.graphLens().getOriginalMethodSignature(getRepresentative().getReference());
-  }
-
-  private ProgramMethod getRepresentative() {
-    return ListUtils.first(instanceInitializers);
-  }
-
-  /**
-   * Returns a special original method signature for the synthesized constructor that did not exist
-   * prior to horizontal class merging. Otherwise we might accidentally think that the synthesized
-   * constructor corresponds to the previous <init>() method on the target class, which could have
-   * unintended side-effects such as leading to unused argument removal being applied to the
-   * synthesized constructor all-though it by construction doesn't have any unused arguments.
-   */
-  private DexMethod getSyntheticMethodReference(
-      ClassMethodsBuilder classMethodsBuilder, ProgramMethod representative) {
-    return dexItemFactory.createFreshMethodNameWithoutHolder(
-        "$r8$init$synthetic",
-        representative.getProto(),
-        representative.getHolderType(),
-        classMethodsBuilder::isFresh);
-  }
-
-  private Int2ReferenceSortedMap<DexMethod> createClassIdToInstanceInitializerMap() {
-    assert !hasInstanceInitializerDescription();
-    Int2ReferenceSortedMap<DexMethod> typeConstructorClassMap = new Int2ReferenceAVLTreeMap<>();
-    for (ProgramMethod instanceInitializer : instanceInitializers) {
-      typeConstructorClassMap.put(
-          classIdentifiers.getInt(instanceInitializer.getHolderType()),
-          lensBuilder.getRenamedMethodSignature(instanceInitializer.getReference()));
-    }
-    return typeConstructorClassMap;
-  }
-
   public int size() {
     return instanceInitializers.size();
   }
@@ -171,20 +96,12 @@
   public static class Builder {
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
-    private final Reference2IntMap<DexType> classIdentifiers;
     private int estimatedDexCodeSize;
     private final List<List<ProgramMethod>> instanceInitializerGroups = new ArrayList<>();
-    private final HorizontalClassMergerGraphLens.Builder lensBuilder;
     private final Mode mode;
 
-    public Builder(
-        AppView<? extends AppInfoWithClassHierarchy> appView,
-        Reference2IntMap<DexType> classIdentifiers,
-        HorizontalClassMergerGraphLens.Builder lensBuilder,
-        Mode mode) {
+    public Builder(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
       this.appView = appView;
-      this.classIdentifiers = classIdentifiers;
-      this.lensBuilder = lensBuilder;
       this.mode = mode;
       createNewGroup();
     }
@@ -219,8 +136,7 @@
       return ListUtils.map(
           instanceInitializerGroups,
           instanceInitializers ->
-              new InstanceInitializerMerger(
-                  appView, classIdentifiers, group, instanceInitializers, lensBuilder, mode));
+              new InstanceInitializerMerger(appView, group, instanceInitializers, mode));
     }
 
     public InstanceInitializerMerger buildSingle(
@@ -229,18 +145,56 @@
       assert instanceInitializerGroups.size() == 1;
       List<ProgramMethod> instanceInitializers = ListUtils.first(instanceInitializerGroups);
       return new InstanceInitializerMerger(
-          appView,
-          classIdentifiers,
-          group,
-          instanceInitializers,
-          lensBuilder,
-          mode,
-          instanceInitializerDescription);
+          appView, group, instanceInitializers, mode, instanceInitializerDescription);
     }
   }
 
-  private boolean hasInstanceInitializerDescription() {
-    return instanceInitializerDescription != null;
+  // Returns true if we can simply use an existing constructor as the new constructor.
+  private boolean isTrivialMerge(ClassMethodsBuilder classMethodsBuilder) {
+    if (group.hasClassIdField()) {
+      // We need to set the class id field.
+      return false;
+    }
+    DexMethod trivialInstanceInitializerReference =
+        ListUtils.first(instanceInitializers)
+            .getReference()
+            .withHolder(group.getTarget(), dexItemFactory);
+    if (!classMethodsBuilder.isFresh(trivialInstanceInitializerReference)) {
+      // We need to append null arguments for disambiguation.
+      return false;
+    }
+    return isMergeOfEquivalentInstanceInitializers();
+  }
+
+  private boolean isMergeOfEquivalentInstanceInitializers() {
+    Iterator<ProgramMethod> instanceInitializerIterator = instanceInitializers.iterator();
+    ProgramMethod firstInstanceInitializer = instanceInitializerIterator.next();
+    if (!instanceInitializerIterator.hasNext()) {
+      return true;
+    }
+    // We need all the constructors to be equivalent.
+    InstanceInitializerInfo instanceInitializerInfo =
+        firstInstanceInitializer
+            .getDefinition()
+            .getOptimizationInfo()
+            .getContextInsensitiveInstanceInitializerInfo();
+    if (!instanceInitializerInfo.hasParent()) {
+      // We don't know the parent constructor of the first constructor.
+      return false;
+    }
+    DexMethod parent = instanceInitializerInfo.getParent();
+    return Iterables.all(
+        instanceInitializers,
+        instanceInitializer ->
+            isSideEffectFreeInstanceInitializerWithParent(instanceInitializer, parent));
+  }
+
+  private boolean isSideEffectFreeInstanceInitializerWithParent(
+      ProgramMethod instanceInitializer, DexMethod parent) {
+    MethodOptimizationInfo optimizationInfo =
+        instanceInitializer.getDefinition().getOptimizationInfo();
+    return !optimizationInfo.mayHaveSideEffects()
+        && optimizationInfo.getContextInsensitiveInstanceInitializerInfo().getParent() == parent;
   }
 
   private DexMethod moveInstanceInitializer(
@@ -265,118 +219,128 @@
     return method;
   }
 
-  private MethodAccessFlags getNewAccessFlags() {
+  private MethodAccessFlags getAccessFlags() {
     // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
     return MethodAccessFlags.fromSharedAccessFlags(
         Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true);
   }
 
-  private Code getNewCode(
-      DexMethod newMethodReference,
-      DexMethod syntheticMethodReference,
-      boolean needsClassId,
-      int extraNulls) {
-    if (hasInstanceInitializerDescription()) {
-      if (mode.isInitial() || appView.options().isGeneratingClassFiles()) {
-        return instanceInitializerDescription.createCfCode(
-            newMethodReference,
-            getOriginalMethodReference(),
-            syntheticMethodReference,
-            group,
-            needsClassId,
-            extraNulls);
-      }
-      return instanceInitializerDescription.createDexCode(
-          newMethodReference,
-          getOriginalMethodReference(),
-          syntheticMethodReference,
-          group,
-          needsClassId,
-          extraNulls);
-    }
-    if (isSingleton() && !group.hasClassIdField()) {
-      return getRepresentative().getDefinition().getCode();
-    }
-    return new ConstructorEntryPointSynthesizedCode(
-        createClassIdToInstanceInitializerMap(),
-        newMethodReference,
-        group.hasClassIdField() ? group.getClassIdField() : null,
-        syntheticMethodReference);
-  }
-
-  private boolean isSingleton() {
-    return instanceInitializers.size() == 1;
-  }
-
   /** Synthesize a new method which selects the constructor based on a parameter type. */
   void merge(
       ClassMethodsBuilder classMethodsBuilder,
+      HorizontalClassMergerGraphLens.Builder lensBuilder,
+      Reference2IntMap<DexType> classIdentifiers,
       SyntheticArgumentClass syntheticArgumentClass) {
-    ProgramMethod representative = ListUtils.first(instanceInitializers);
-
-    // Create merged instance initializer reference.
-    boolean needsClassId =
-        instanceInitializers.size() > 1
-            && (!hasInstanceInitializerDescription() || group.hasClassIdField());
-    assert mode.isInitial() || !needsClassId;
-
-    DexMethod newMethodReferenceTemplate = getNewMethodReference(representative, needsClassId);
-    assert mode.isInitial() || classMethodsBuilder.isFresh(newMethodReferenceTemplate);
-
-    DexMethod newMethodReference =
-        dexItemFactory.createInstanceInitializerWithFreshProto(
-            newMethodReferenceTemplate,
-            mode.isInitial() ? syntheticArgumentClass.getArgumentClasses() : ImmutableList.of(),
-            classMethodsBuilder::isFresh);
-    int extraNulls = newMethodReference.getArity() - newMethodReferenceTemplate.getArity();
-
-    // Verify that the merge is a simple renaming in the final round of merging.
-    assert mode.isInitial() || newMethodReference == newMethodReferenceTemplate;
-
-    // Move instance initializers to target class.
-    if (hasInstanceInitializerDescription()) {
-      lensBuilder.moveMethods(instanceInitializers, newMethodReference);
-    } else if (isSingleton() && !group.hasClassIdField()) {
-      lensBuilder.moveMethod(representative.getReference(), newMethodReference, true);
-    } else {
-      for (ProgramMethod instanceInitializer : instanceInitializers) {
-        DexMethod movedInstanceInitializer =
-            moveInstanceInitializer(classMethodsBuilder, instanceInitializer);
-        lensBuilder.mapMethod(movedInstanceInitializer, movedInstanceInitializer);
-        lensBuilder.recordNewMethodSignature(
-            instanceInitializer.getReference(), movedInstanceInitializer);
-      }
+    // TODO(b/189296638): Handle merging of equivalent constructors when
+    //  `instanceInitializerDescription` is set.
+    if (isTrivialMerge(classMethodsBuilder)) {
+      mergeTrivial(classMethodsBuilder, lensBuilder);
+      return;
     }
 
-    // Add a mapping from a synthetic name to the synthetic constructor.
-    DexMethod syntheticMethodReference =
-        getSyntheticMethodReference(classMethodsBuilder, representative);
-    if (!isSingleton() || group.hasClassIdField()) {
-      lensBuilder.recordNewMethodSignature(syntheticMethodReference, newMethodReference, true);
-    }
+    assert mode.isInitial();
 
-    // Map each of the instance initializers to the new instance initializer in the graph lens.
+    // Tree map as must be sorted.
+    Int2ReferenceSortedMap<DexMethod> typeConstructorClassMap = new Int2ReferenceAVLTreeMap<>();
+
+    // Move constructors to target class.
+    CfVersion classFileVersion = null;
     for (ProgramMethod instanceInitializer : instanceInitializers) {
+      if (instanceInitializer.getDefinition().hasClassFileVersion()) {
+        classFileVersion =
+            Ordered.maxIgnoreNull(
+                classFileVersion, instanceInitializer.getDefinition().getClassFileVersion());
+      }
+      DexMethod movedInstanceInitializer =
+          moveInstanceInitializer(classMethodsBuilder, instanceInitializer);
+      lensBuilder.mapMethod(movedInstanceInitializer, movedInstanceInitializer);
+      lensBuilder.recordNewMethodSignature(
+          instanceInitializer.getReference(), movedInstanceInitializer);
+      typeConstructorClassMap.put(
+          classIdentifiers.getInt(instanceInitializer.getHolderType()), movedInstanceInitializer);
+    }
+
+    // Create merged constructor reference.
+    DexMethod methodReferenceTemplate = generateReferenceMethodTemplate();
+    DexMethod newConstructorReference =
+        dexItemFactory.createInstanceInitializerWithFreshProto(
+            methodReferenceTemplate,
+            syntheticArgumentClass.getArgumentClasses(),
+            classMethodsBuilder::isFresh);
+    int extraNulls = newConstructorReference.getArity() - methodReferenceTemplate.getArity();
+
+    ProgramMethod representative = ListUtils.first(instanceInitializers);
+    DexMethod originalConstructorReference =
+        appView.graphLens().getOriginalMethodSignature(representative.getReference());
+
+    // Create a special original method signature for the synthesized constructor that did not exist
+    // prior to horizontal class merging. Otherwise we might accidentally think that the synthesized
+    // constructor corresponds to the previous <init>() method on the target class, which could have
+    // unintended side-effects such as leading to unused argument removal being applied to the
+    // synthesized constructor all-though it by construction doesn't have any unused arguments.
+    DexMethod bridgeConstructorReference =
+        dexItemFactory.createFreshMethodNameWithoutHolder(
+            "$r8$init$bridge",
+            originalConstructorReference.getProto(),
+            originalConstructorReference.getHolderType(),
+            classMethodsBuilder::isFresh);
+
+    ConstructorEntryPointSynthesizedCode synthesizedCode =
+        new ConstructorEntryPointSynthesizedCode(
+            typeConstructorClassMap,
+            newConstructorReference,
+            group.hasClassIdField() ? group.getClassIdField() : null,
+            bridgeConstructorReference);
+    DexEncodedMethod newConstructor =
+        new DexEncodedMethod(
+            newConstructorReference,
+            getAccessFlags(),
+            MethodTypeSignature.noSignature(),
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
+            synthesizedCode,
+            true,
+            classFileVersion);
+
+    // Map each old constructor to the newly synthesized constructor in the graph lens.
+    for (ProgramMethod oldInstanceInitializer : instanceInitializers) {
       List<ExtraParameter> extraParameters = new ArrayList<>();
-      if (needsClassId) {
-        int classIdentifier = classIdentifiers.getInt(instanceInitializer.getHolderType());
+      if (instanceInitializers.size() > 1) {
+        int classIdentifier = classIdentifiers.getInt(oldInstanceInitializer.getHolderType());
         extraParameters.add(new ExtraConstantIntParameter(classIdentifier));
       }
       extraParameters.addAll(Collections.nCopies(extraNulls, new ExtraUnusedNullParameter()));
       lensBuilder.mapMergedConstructor(
-          instanceInitializer.getReference(), newMethodReference, extraParameters);
+          oldInstanceInitializer.getReference(), newConstructorReference, extraParameters);
     }
 
-    DexEncodedMethod newInstanceInitializer =
-        new DexEncodedMethod(
-            newMethodReference,
-            getNewAccessFlags(),
-            MethodTypeSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            ParameterAnnotationsList.empty(),
-            getNewCode(newMethodReference, syntheticMethodReference, needsClassId, extraNulls),
-            true,
-            getNewClassFileVersion());
-    classMethodsBuilder.addDirectMethod(newInstanceInitializer);
+    // Add a mapping from a synthetic name to the synthetic constructor.
+    lensBuilder.recordNewMethodSignature(bridgeConstructorReference, newConstructorReference);
+
+    classMethodsBuilder.addDirectMethod(newConstructor);
+  }
+
+  private void mergeTrivial(
+      ClassMethodsBuilder classMethodsBuilder, HorizontalClassMergerGraphLens.Builder lensBuilder) {
+    ProgramMethod representative = ListUtils.first(instanceInitializers);
+    DexMethod newMethodReference =
+        representative.getReference().withHolder(group.getTarget(), dexItemFactory);
+    lensBuilder.moveMethods(instanceInitializers, newMethodReference, representative);
+
+    DexEncodedMethod newMethod =
+        representative.getHolder() == group.getTarget()
+            ? representative.getDefinition()
+            : representative.getDefinition().toTypeSubstitutedMethod(newMethodReference);
+    fixupAccessFlagsForTrivialMerge(newMethod.getAccessFlags());
+
+    classMethodsBuilder.addDirectMethod(newMethod);
+  }
+
+  private void fixupAccessFlagsForTrivialMerge(MethodAccessFlags accessFlags) {
+    if (!accessFlags.isPublic()) {
+      accessFlags.unsetPrivate();
+      accessFlags.unsetProtected();
+      accessFlags.setPublic();
+    }
   }
 }
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 b5e485b..ed7e099 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -10,11 +10,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.horizontalclassmerging.InstanceInitializerMerger.Builder;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.LinkedHashMap;
@@ -32,15 +30,14 @@
       List<InstanceInitializerMerger> instanceInitializerMergers,
       Map<InstanceInitializerDescription, InstanceInitializerMerger>
           equivalentInstanceInitializerMergers) {
+    assert equivalentInstanceInitializerMergers.isEmpty();
     this.instanceInitializerMergers = instanceInitializerMergers;
     this.equivalentInstanceInitializerMergers = equivalentInstanceInitializerMergers;
   }
 
   public static InstanceInitializerMergerCollection create(
       AppView<? extends AppInfoWithClassHierarchy> appView,
-      Reference2IntMap<DexType> classIdentifiers,
       MergeGroup group,
-      ClassInstanceFieldsMerger instanceFieldsMerger,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Mode mode) {
     // Create an instance initializer merger for each group of instance initializers that are
@@ -53,16 +50,12 @@
                 DexEncodedMethod::isInstanceInitializer,
                 instanceInitializer -> {
                   InstanceInitializerDescription description =
-                      InstanceInitializerAnalysis.analyze(
-                          appView, group, instanceInitializer, instanceFieldsMerger, mode);
+                      InstanceInitializerAnalysis.analyze(instanceInitializer, lensBuilder);
                   if (description != null) {
                     buildersByDescription
                         .computeIfAbsent(
                             description,
-                            ignoreKey(
-                                () ->
-                                    new InstanceInitializerMerger.Builder(
-                                        appView, classIdentifiers, lensBuilder, mode)))
+                            ignoreKey(() -> new InstanceInitializerMerger.Builder(appView, mode)))
                         .addEquivalent(instanceInitializer);
                   } else {
                     buildersWithoutDescription.add(instanceInitializer);
@@ -93,9 +86,7 @@
               buildersByProto
                   .computeIfAbsent(
                       instanceInitializer.getDefinition().getProto(),
-                      ignore ->
-                          new InstanceInitializerMerger.Builder(
-                              appView, classIdentifiers, lensBuilder, mode))
+                      ignore -> new InstanceInitializerMerger.Builder(appView, mode))
                   .add(instanceInitializer));
       for (InstanceInitializerMerger.Builder builder : buildersByProto.values()) {
         instanceInitializerMergers.addAll(builder.build(group));
@@ -104,8 +95,7 @@
       buildersWithoutDescription.forEach(
           instanceInitializer ->
               instanceInitializerMergers.addAll(
-                  new InstanceInitializerMerger.Builder(
-                          appView, classIdentifiers, lensBuilder, mode)
+                  new InstanceInitializerMerger.Builder(appView, mode)
                       .add(instanceInitializer)
                       .build(group)));
     }
@@ -120,6 +110,5 @@
 
   public void forEach(Consumer<InstanceInitializerMerger> consumer) {
     instanceInitializerMergers.forEach(consumer);
-    equivalentInstanceInitializerMergers.values().forEach(consumer);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
index 13df457..f2d8962 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
@@ -34,14 +34,6 @@
     this.nameComputationInfo = nameComputationInfo;
   }
 
-  public DexReference getItem() {
-    return item;
-  }
-
-  public NameComputationInfo<?> getNameComputationInfo() {
-    return nameComputationInfo;
-  }
-
   @Override
   public boolean isSingleDexItemBasedStringValue() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 4fc6914..aae7abe 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -87,10 +87,6 @@
     return true;
   }
 
-  public static <T> T last(T[] array) {
-    return array[array.length - 1];
-  }
-
   /**
    * Rewrites the input array based on the given function.
    *
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest.java
index 57108ff..5d9722b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest.java
@@ -52,8 +52,9 @@
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 1.
               assertEquals(
-                  1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("C", "D");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java
index 5297a09..a5d7e26 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java
@@ -53,8 +53,8 @@
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
 
-              // TODO(b/189296638): Enable constructor merging by changing the constructor
-              // arguments.
+              // TODO(b/189296638): Should be 1, but requires changing the order of arguments at
+              //  constructor call sites.
               assertEquals(
                   2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
             })
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.java
index 7a7a997..bfce2d6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.java
@@ -52,8 +52,9 @@
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 1.
               assertEquals(
-                  1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("CC", "DD");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
index 3917d56..5e144b0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.AssumptionViolatedException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -51,8 +52,9 @@
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 2.
               assertEquals(
-                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+                  3, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
 
               ClassSubject nullArgumentClassSubject =
                   inspector.allClasses().stream()
@@ -61,7 +63,11 @@
                               SyntheticItemsTestUtils.isHorizontalInitializerTypeArgument(
                                   clazz.getOriginalReference()))
                       .findFirst()
-                      .orElseThrow(RuntimeException::new);
+                      // TODO(b/189296638): Should throw RuntimeException.
+                      .orElseThrow(
+                          () ->
+                              new AssumptionViolatedException(
+                                  "Expected Horizontal initializer type argument"));
 
               assertThat(
                   aClassSubject.method("void", "<init>", "java.lang.Object", "int"), isPresent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdMergingTest.java
index 2bbd4e5..59756bf 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdMergingTest.java
@@ -50,8 +50,9 @@
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 1.
               assertEquals(
-                  1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("C", "D");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithInterfaceValueToParentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithInterfaceValueToParentTest.java
index c45274f..8785c85 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithInterfaceValueToParentTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithInterfaceValueToParentTest.java
@@ -61,6 +61,8 @@
 
   @NeverClassInline
   static class A extends Parent {
+    // When merging this initializer with B(L), it is important that we choose the signature AB(J)
+    // and not AB(I) or AB(Object), or the program will not verify.
     A(K k) {
       super(k);
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
deleted file mode 100644
index 48e6535..0000000
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright (c) 2021, 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.classmerging.horizontal;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.KeepUnusedArguments;
-import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.classmerging.horizontal.EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.A;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class EquivalentConstructorsWithPrimitiveAndReferencesParametersTest extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
-  }
-
-  public EquivalentConstructorsWithPrimitiveAndReferencesParametersTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void test() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .addKeepMainRule(Main.class)
-        .addHorizontallyMergedClassesInspector(
-            inspector ->
-                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
-        .enableInliningAnnotations()
-        .enableNeverClassInliningAnnotations()
-        .enableUnusedArgumentAnnotations()
-        .setMinApi(parameters.getApiLevel())
-        .compile()
-        .inspect(
-            inspector -> {
-              ClassSubject aClassSubject = inspector.clazz(A.class);
-              assertThat(aClassSubject, isPresent());
-              assertEquals(
-                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
-            })
-        .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("A", "B");
-  }
-
-  static class Main {
-
-    public static void main(String[] args) {
-      new A(42).m1();
-      new B(new Object()).m2();
-    }
-  }
-
-  @NeverClassInline
-  static class A {
-    @KeepUnusedArguments
-    A(int i) {}
-
-    @NeverInline
-    void m1() {
-      System.out.println("A");
-    }
-  }
-
-  @NeverClassInline
-  static class B {
-    @KeepUnusedArguments
-    B(Object o) {}
-
-    @NeverInline
-    void m2() {
-      System.out.println("B");
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java
index 4a66189..d429057 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java
@@ -54,8 +54,9 @@
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 1.
               assertEquals(
-                  1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("C", "D");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdMergingTest.java
index 090699b..a8b85e0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdMergingTest.java
@@ -52,8 +52,9 @@
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 1.
               assertEquals(
-                  1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("C", "D");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
index c39b7b5..e785238 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
@@ -44,9 +44,6 @@
         .addKeepMainRule(Main.class)
         .addKeepAttributeLineNumberTable()
         .addKeepAttributeSourceFile()
-        .addHorizontallyMergedClassesInspector(
-            inspector ->
-                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
         .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -55,22 +52,22 @@
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              StackTrace expectedStackTraceWithMergedConstructor =
-                  StackTrace.builder()
-                      .add(expectedStackTrace)
-                      .add(
-                          2,
-                          StackTraceLine.builder()
-                              .setClassName(A.class.getTypeName())
-                              // TODO(b/124483578): The synthetic method should not be part of the
-                              //  retraced stack trace.
-                              .setMethodName("$r8$init$synthetic")
-                              .setFileName(getClass().getSimpleName() + ".java")
-                              .setLineNumber(0)
-                              .build())
-                      .build();
-              assertThat(stackTrace, isSame(expectedStackTraceWithMergedConstructor));
-              assertThat(codeInspector.clazz(B.class), not(isPresent()));
+                StackTrace expectedStackTraceWithMergedConstructor =
+                    StackTrace.builder()
+                        .add(expectedStackTrace)
+                        .add(
+                            2,
+                            StackTraceLine.builder()
+                                .setClassName(A.class.getTypeName())
+                                // TODO(b/124483578): The synthetic method should not be part of the
+                                //  retraced stack trace.
+                                .setMethodName("$r8$init$bridge")
+                                .setFileName(getClass().getSimpleName() + ".java")
+                                .setLineNumber(0)
+                                .build())
+                        .build();
+                assertThat(stackTrace, isSame(expectedStackTraceWithMergedConstructor));
+                assertThat(codeInspector.clazz(B.class), not(isPresent()));
             });
   }
 
@@ -85,18 +82,12 @@
 
   @NeverClassInline
   static class A extends Parent {
-    A() {
-      // To avoid constructor equivalence.
-      System.out.println("A");
-    }
+    A() {}
   }
 
   @NeverClassInline
   static class B extends Parent {
-    B() {
-      // To avoid constructor equivalence.
-      System.out.println("B");
-    }
+    B() {}
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorWithEquivalenceStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorWithEquivalenceStackTraceTest.java
deleted file mode 100644
index 9b5b87d..0000000
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorWithEquivalenceStackTraceTest.java
+++ /dev/null
@@ -1,107 +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.classmerging.horizontal;
-
-import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NoVerticalClassMerging;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.classmerging.horizontal.MergedConstructorStackTraceTest.A;
-import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
-import org.junit.Before;
-import org.junit.Test;
-
-public class MergedConstructorWithEquivalenceStackTraceTest extends HorizontalClassMergingTestBase {
-
-  public StackTrace expectedStackTrace;
-
-  public MergedConstructorWithEquivalenceStackTraceTest(TestParameters parameters) {
-    super(parameters);
-  }
-
-  @Before
-  public void setup() throws Exception {
-    // Get the expected stack trace by running on the JVM.
-    expectedStackTrace =
-        testForJvm()
-            .addTestClasspath()
-            .run(CfRuntime.getSystemRuntime(), Main.class)
-            .assertFailure()
-            .map(StackTrace::extractFromJvm);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .addKeepMainRule(Main.class)
-        .addKeepAttributeLineNumberTable()
-        .addKeepAttributeSourceFile()
-        .addHorizontallyMergedClassesInspector(
-            inspector ->
-                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
-        .enableNoVerticalClassMergingAnnotations()
-        .enableNeverClassInliningAnnotations()
-        .setMinApi(parameters.getApiLevel())
-        .compile()
-        .run(parameters.getRuntime(), Main.class)
-        .inspectStackTrace(
-            (stackTrace, codeInspector) -> {
-              assertThat(codeInspector.clazz(A.class), isPresent());
-              StackTrace expectedStackTraceWithMergedConstructor =
-                  StackTrace.builder()
-                      .add(expectedStackTrace)
-                      // TODO(b/124483578): Stack trace lines from the merging of equivalent
-                      //  constructors should retrace to the set of lines from each of the
-                      //  individual source constructors.
-                      .map(1, stackTraceLine -> stackTraceLine.builderOf().setLineNumber(0).build())
-                      // TODO(b/124483578): The synthetic method should not be part of the retraced
-                      //  stack trace.
-                      .add(
-                          2,
-                          StackTraceLine.builder()
-                              .setClassName(A.class.getTypeName())
-                              .setMethodName("$r8$init$synthetic")
-                              .setFileName(getClass().getSimpleName() + ".java")
-                              .setLineNumber(0)
-                              .build())
-                      .build();
-              assertThat(stackTrace, isSame(expectedStackTraceWithMergedConstructor));
-              assertThat(codeInspector.clazz(B.class), not(isPresent()));
-            });
-  }
-
-  @NoVerticalClassMerging
-  static class Parent {
-    Parent() {
-      if (System.currentTimeMillis() >= 0) {
-        throw new RuntimeException();
-      }
-    }
-  }
-
-  @NeverClassInline
-  static class A extends Parent {
-    A() {}
-  }
-
-  @NeverClassInline
-  static class B extends Parent {
-    B() {}
-  }
-
-  static class Main {
-    public static void main(String[] args) {
-      new A();
-      new B();
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index a4c52ad..fdb7faa 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -115,11 +115,15 @@
     private List<Path> additionalProgramFiles = new ArrayList<>();
     private Consumer<InternalOptions> optionsModifier = ConsumerUtils.emptyConsumer();
     private Path desugarJDKLibs = ToolHelper.getDesugarJDKLibs();
-    private Path desugarJDKLibsConfiguration = null;
+    private Path desugarJDKLibsConfiguration = ToolHelper.DESUGAR_LIB_CONVERSIONS;
     private StringResource desugaredLibraryConfiguration =
         StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting());
     private List<Path> libraryFiles = new ArrayList<>();
 
+    public static L8TestBuilder builder(AndroidApiLevel apiLevel, TemporaryFolder temp) {
+      return new L8TestBuilder(apiLevel, temp);
+    }
+
     private L8TestBuilder(AndroidApiLevel apiLevel, TemporaryFolder temp) {
       this.apiLevel = apiLevel;
       this.state = new TestState(temp);
@@ -235,11 +239,11 @@
     }
 
     private Collection<Path> getProgramFiles() {
-      ImmutableList.Builder<Path> builder = ImmutableList.<Path>builder().add(desugarJDKLibs);
-      if (desugarJDKLibsConfiguration != null) {
-        builder.add(desugarJDKLibsConfiguration);
-      }
-      return builder.addAll(additionalProgramFiles).build();
+      return ImmutableList.<Path>builder()
+          .add(desugarJDKLibs)
+          .add(desugarJDKLibsConfiguration)
+          .addAll(additionalProgramFiles)
+          .build();
     }
 
     private Collection<Path> getLibraryFiles() {
@@ -296,7 +300,6 @@
               },
               L8TestBuilder::setDebug)
           .addOptionsModifier(optionsModifier)
-          .setDesugarJDKLibsConfiguration(ToolHelper.DESUGAR_LIB_CONVERSIONS)
           // If we compile extended library here, it means we use TestNG. TestNG requires
           // annotations, hence we disable annotation removal. This implies that extra warnings are
           // generated.
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
deleted file mode 100644
index c1182bd..0000000
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.internal;
-
-import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
-import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
-import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.assertRewrittenProtoSchemasMatch;
-import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepAllProtosRule;
-import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepDynamicMethodSignatureRule;
-import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepNewMessageInfoSignatureRule;
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.L8TestCompileResult;
-import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.StringResource;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.concurrent.ExecutionException;
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class YouTubeV1620Test extends YouTubeCompilationTestBase {
-
-  private static final int MAX_APPLICATION_SIZE = 29750000;
-  private static final int MAX_DESUGARED_LIBRARY_SIZE = 425000;
-
-  private final TestParameters parameters;
-
-  private final Path dumpDirectory = Paths.get("YouTubeV1620-" + System.currentTimeMillis());
-  private final Reporter reporter = new Reporter();
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntime(Version.V9_0_0)
-        .withApiLevel(AndroidApiLevel.L)
-        .build();
-  }
-
-  public YouTubeV1620Test(TestParameters parameters) {
-    super(16, 20, parameters.getApiLevel());
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void test() throws Exception {
-    assumeTrue(isLocalDevelopment());
-    assumeTrue(shouldRunSlowTests());
-
-    KeepRuleConsumer keepRuleConsumer = new PresentKeepRuleConsumer();
-    R8TestCompileResult r8CompileResult = compileApplicationWithR8(keepRuleConsumer);
-    L8TestCompileResult l8CompileResult = compileDesugaredLibraryWithL8(keepRuleConsumer);
-
-    inspect(r8CompileResult, l8CompileResult);
-
-    if (isLocalDevelopment()) {
-      dump(r8CompileResult, l8CompileResult);
-    }
-  }
-
-  @Test
-  public void testProtoRewriting() throws Exception {
-    assumeTrue(shouldRunSlowTests());
-
-    StringConsumer keepRuleConsumer = StringConsumer.emptyConsumer();
-    R8TestCompileResult r8CompileResult =
-        compileApplicationWithR8(
-            keepRuleConsumer,
-            builder ->
-                builder
-                    .addKeepRules(
-                        keepAllProtosRule(),
-                        keepDynamicMethodSignatureRule(),
-                        keepNewMessageInfoSignatureRule())
-                    .allowCheckDiscardedErrors(true));
-    assertRewrittenProtoSchemasMatch(
-        new CodeInspector(getProgramFiles()), r8CompileResult.inspector());
-  }
-
-  @After
-  public void teardown() {
-    reporter.failIfPendingErrors();
-  }
-
-  private R8TestCompileResult compileApplicationWithR8(StringConsumer keepRuleConsumer)
-      throws IOException, CompilationFailedException {
-    return compileApplicationWithR8(keepRuleConsumer, ThrowableConsumer.empty());
-  }
-
-  private R8TestCompileResult compileApplicationWithR8(
-      StringConsumer keepRuleConsumer, ThrowableConsumer<R8FullTestBuilder> configuration)
-      throws IOException, CompilationFailedException {
-    return testForR8(parameters.getBackend())
-        .addProgramFiles(getProgramFiles())
-        .addLibraryFiles(getLibraryFiles())
-        .addKeepRuleFiles(getKeepRuleFiles())
-        .addDontWarn("android.app.Activity$TranslucentConversionListener")
-        .allowDiagnosticMessages()
-        .allowUnusedDontWarnPatterns()
-        .allowUnusedProguardConfigurationRules()
-        .apply(configuration)
-        .setMinApi(getApiLevel())
-        .enableCoreLibraryDesugaring(
-            getApiLevel(),
-            keepRuleConsumer,
-            StringResource.fromFile(getDesugaredLibraryConfiguration()))
-        .compile()
-        .assertAllInfoMessagesMatch(
-            anyOf(
-                containsString("Ignoring option: -optimizations"),
-                containsString("Proguard configuration rule does not match anything"),
-                containsString("Invalid signature")))
-        .apply(this::printProtoStats);
-  }
-
-  private L8TestCompileResult compileDesugaredLibraryWithL8(KeepRuleConsumer keepRuleConsumer)
-      throws CompilationFailedException, IOException, ExecutionException {
-    return testForL8(getApiLevel())
-        .setDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
-        .setDesugarJDKLibs(getDesugaredLibraryJDKLibs())
-        .addGeneratedKeepRules(keepRuleConsumer.get())
-        .addKeepRuleFiles(getDesugaredLibraryKeepRuleFiles())
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-        .compile();
-  }
-
-  private void inspect(R8TestCompileResult r8CompileResult, L8TestCompileResult l8CompileResult)
-      throws IOException, ResourceException {
-    r8CompileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
-    l8CompileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
-
-    int applicationSize = r8CompileResult.getApp().applicationSize();
-    if (applicationSize > MAX_APPLICATION_SIZE) {
-      reporter.error(
-          "Expected application size to be <="
-              + MAX_APPLICATION_SIZE
-              + ", but was "
-              + applicationSize);
-    }
-
-    int desugaredLibrarySize = l8CompileResult.getApp().applicationSize();
-    if (desugaredLibrarySize > MAX_DESUGARED_LIBRARY_SIZE) {
-      reporter.error(
-          "Expected desugared library size to be <="
-              + MAX_DESUGARED_LIBRARY_SIZE
-              + ", but was "
-              + desugaredLibrarySize);
-    }
-
-    if (isLocalDevelopment()) {
-      System.out.println("Dex size (application, excluding desugared library): " + applicationSize);
-      System.out.println("Dex size (desugared library): " + desugaredLibrarySize);
-      System.out.println("Dex size (total): " + (applicationSize + desugaredLibrarySize));
-    }
-  }
-
-  private void dump(R8TestCompileResult r8CompileResult, L8TestCompileResult l8CompileResult)
-      throws IOException {
-    assertTrue(isLocalDevelopment());
-    Files.createDirectories(dumpDirectory);
-    r8CompileResult
-        .writeToDirectory(dumpDirectory)
-        .writeProguardMap(dumpDirectory.resolve("mapping.txt"));
-    l8CompileResult
-        .writeSingleDexOutputToFile(dumpDirectory.resolve("classes5.dex"))
-        .writeGeneratedKeepRules(dumpDirectory.resolve("l8-keep.txt"))
-        .writeProguardMap(dumpDirectory.resolve("l8-mapping.txt"));
-  }
-}