Add class id field to horizontally merged classes

Bug: 166427795
Bug: 163311975
Change-Id: I8628755d4fbc0849e0ddade86a0e8b230de07ce1
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 26eb08a..51ef31e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -4,14 +4,20 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -31,24 +37,30 @@
   private final Collection<DexProgramClass> toMergeGroup;
   private final DexItemFactory dexItemFactory;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
+  private final FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder;
 
   private final Reference2IntMap<DexType> classIdentifiers = new Reference2IntOpenHashMap<>();
-
   private final Map<DexProto, ConstructorMerger.Builder> constructorMergers;
+  private final DexField classIdField;
 
   ClassMerger(
       AppView<?> appView,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
+      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       DexProgramClass target,
       Collection<DexProgramClass> toMergeGroup) {
     this.appView = appView;
     this.lensBuilder = lensBuilder;
+    this.fieldAccessChangesBuilder = fieldAccessChangesBuilder;
     this.target = target;
     this.toMergeGroup = toMergeGroup;
-    this.constructorMergers = new IdentityHashMap<>();
 
+    this.constructorMergers = new IdentityHashMap<>();
     this.dexItemFactory = appView.dexItemFactory();
 
+    // TODO(b/165498187): ensure the name for the field is fresh
+    classIdField = dexItemFactory.createField(target.type, dexItemFactory.intType, "$r8$classId");
+
     buildClassIdentifierMap();
   }
 
@@ -112,8 +124,8 @@
 
   void mergeConstructors() {
     for (ConstructorMerger.Builder builder : constructorMergers.values()) {
-      ConstructorMerger constructorMerger = builder.build(appView, target);
-      constructorMerger.merge(lensBuilder, classIdentifiers);
+      ConstructorMerger constructorMerger = builder.build(appView, target, classIdField);
+      constructorMerger.merge(lensBuilder, fieldAccessChangesBuilder, classIdentifiers);
     }
   }
 
@@ -131,8 +143,20 @@
         });
   }
 
+  void appendClassIdField() {
+    DexEncodedField encodedField =
+        new DexEncodedField(
+            classIdField,
+            FieldAccessFlags.fromSharedAccessFlags(
+                Constants.ACC_PUBLIC + Constants.ACC_FINAL + Constants.ACC_SYNTHETIC),
+            DexAnnotationSet.empty(),
+            null);
+    target.appendInstanceField(encodedField);
+  }
+
   public void mergeGroup() {
     addTargetConstructors();
+    appendClassIdField();
 
     for (DexProgramClass clazz : toMergeGroup) {
       merge(clazz);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
index 58fe0b7..ff2a898 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
@@ -4,17 +4,19 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
 import com.android.tools.r8.utils.IntBox;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map.Entry;
-import java.util.SortedMap;
 
 /**
  * Generate code of the form: <code>
@@ -32,23 +34,54 @@
  * </code>
  */
 public class ConstructorEntryPoint extends SyntheticSourceCode {
-  private final SortedMap<Integer, DexMethod> typeConstructors;
+  private final DexField classIdField;
+  private final Int2ReferenceSortedMap<DexMethod> typeConstructors;
 
   public ConstructorEntryPoint(
-      SortedMap<Integer, DexMethod> typeConstructors,
-      DexMethod method,
+      Int2ReferenceSortedMap<DexMethod> typeConstructors,
+      DexMethod newConstructor,
+      DexField classIdField,
       Position callerPosition,
       DexMethod originalMethod) {
-    super(method.holder, method, callerPosition, originalMethod);
+    super(newConstructor.holder, newConstructor, callerPosition, originalMethod);
 
     this.typeConstructors = typeConstructors;
+    this.classIdField = classIdField;
   }
 
-  @Override
-  protected void prepareInstructions() {
+  void addConstructorInvoke(DexMethod typeConstructor) {
+    add(
+        builder -> {
+          List<Value> arguments = new ArrayList<>(typeConstructor.getArity() + 1);
+          arguments.add(builder.getReceiverValue());
+
+          // If there are any arguments add them to the list.
+          for (int i = 0; i < typeConstructor.getArity(); i++) {
+            arguments.add(builder.getArgumentValues().get(i));
+          }
+
+          builder.addInvoke(Type.DIRECT, typeConstructor, typeConstructor.proto, arguments, false);
+        });
+  }
+
+  /** Assign the given register to the class id field. */
+  void addRegisterClassIdAssignment(int idRegister) {
+    add(builder -> builder.addInstancePut(idRegister, getReceiverRegister(), classIdField));
+  }
+
+  /** Assign the given constant integer value to the class id field. */
+  void addConstantRegisterClassIdAssignment(int classId) {
+    int idRegister = nextRegister(ValueType.INT);
+    add(builder -> builder.addIntConst(idRegister, classId));
+    addRegisterClassIdAssignment(idRegister);
+  }
+
+  protected void prepareMultiConstructorInstructions() {
     int typeConstructorCount = typeConstructors.size();
     int idRegister = getParamRegister(method.getArity() - 1);
 
+    addRegisterClassIdAssignment(idRegister);
+
     int[] keys = new int[typeConstructorCount - 1];
     int[] offsets = new int[typeConstructorCount - 1];
     IntBox fallthrough = new IntBox();
@@ -57,10 +90,9 @@
         builder -> builder.addSwitch(idRegister, keys, fallthrough.get(), offsets),
         builder -> endsSwitch(builder, switchIndex, fallthrough.get(), offsets));
 
-
     int index = 0;
-    for (Entry<Integer, DexMethod> entry : typeConstructors.entrySet()) {
-      int classId = entry.getKey();
+    for (Entry<DexMethod> entry : typeConstructors.int2ReferenceEntrySet()) {
+      int classId = entry.getIntKey();
       DexMethod typeConstructor = entry.getValue();
 
       if (index == 0) {
@@ -72,23 +104,26 @@
         offsets[index - 1] = nextInstructionIndex();
       }
 
-      add(
-          builder -> {
-            List<Value> arguments = new ArrayList<>(typeConstructor.getArity());
-            arguments.add(builder.getReceiverValue());
-            int paramIndex = 0;
-            for (Value argument : builder.getArgumentValues()) {
-              if (paramIndex++ >= typeConstructor.getArity()) {
-                break;
-              }
-              arguments.add(argument);
-            }
-            builder.addInvoke(
-                Type.DIRECT, typeConstructor, typeConstructor.proto, arguments, false);
-          });
+      addConstructorInvoke(typeConstructor);
       add(IRBuilder::addReturn, endsBlock);
 
       index++;
     }
   }
+
+  protected void prepareSingleConstructorInstructions() {
+    Entry<DexMethod> entry = typeConstructors.int2ReferenceEntrySet().first();
+    addConstantRegisterClassIdAssignment(entry.getIntKey());
+    addConstructorInvoke(entry.getValue());
+    add(IRBuilder::addReturn, endsBlock);
+  }
+
+  @Override
+  protected void prepareInstructions() {
+    if (typeConstructors.size() > 1) {
+      prepareMultiConstructorInstructions();
+    } else {
+      prepareSingleConstructorInstructions();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPointSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPointSynthesizedCode.java
index a2ac6d8..7d250bb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPointSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPointSynthesizedCode.java
@@ -4,30 +4,35 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
-import java.util.SortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.function.Consumer;
 
 public class ConstructorEntryPointSynthesizedCode extends AbstractSynthesizedCode {
-  private DexMethod newConstructor;
-  private DexMethod originalMethod;
-  private final SortedMap<Integer, DexMethod> typeConstructors;
+  private final DexMethod newConstructor;
+  private final DexMethod originalMethod;
+  private final DexField classIdField;
+  private final Int2ReferenceSortedMap<DexMethod> typeConstructors;
 
   public ConstructorEntryPointSynthesizedCode(
-      SortedMap<Integer, DexMethod> typeConstructors,
+      Int2ReferenceSortedMap<DexMethod> typeConstructors,
       DexMethod newConstructor,
+      DexField classIdField,
       DexMethod originalMethod) {
     this.typeConstructors = typeConstructors;
     this.newConstructor = newConstructor;
+    this.classIdField = classIdField;
     this.originalMethod = originalMethod;
   }
 
   @Override
   public SourceCodeProvider getSourceCodeProvider() {
     return callerPosition ->
-        new ConstructorEntryPoint(typeConstructors, newConstructor, callerPosition, originalMethod);
+        new ConstructorEntryPoint(
+            typeConstructors, newConstructor, classIdField, callerPosition, originalMethod);
   }
 
   @Override
@@ -36,7 +41,6 @@
   }
 
   private void registerReachableDefinitions(UseRegistry registry) {
-    registry.registerInvokeDirect(newConstructor);
     for (DexMethod typeConstructor : typeConstructors.values()) {
       registry.registerInvokeDirect(typeConstructor);
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index b9cb999..76f4814 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexProgramClass;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -28,12 +30,17 @@
   private final DexProgramClass target;
   private final Collection<DexEncodedMethod> constructors;
   private final DexItemFactory dexItemFactory;
+  private final DexField classIdField;
 
   ConstructorMerger(
-      AppView<?> appView, DexProgramClass target, Collection<DexEncodedMethod> constructors) {
+      AppView<?> appView,
+      DexProgramClass target,
+      Collection<DexEncodedMethod> constructors,
+      DexField classIdField) {
     this.appView = appView;
     this.target = target;
     this.constructors = constructors;
+    this.classIdField = classIdField;
 
     // Constructors should not be empty and all constructors should have the same prototype.
     assert !constructors.isEmpty();
@@ -54,11 +61,16 @@
       return this;
     }
 
-    public ConstructorMerger build(AppView<?> appView, DexProgramClass target) {
-      return new ConstructorMerger(appView, target, constructors);
+    public ConstructorMerger build(
+        AppView<?> appView, DexProgramClass target, DexField classIdField) {
+      return new ConstructorMerger(appView, target, constructors, classIdField);
     }
   }
 
+  private boolean isTrivialMerge() {
+    return constructors.size() == 1;
+  }
+
   private DexMethod moveConstructor(DexEncodedMethod constructor) {
     DexMethod method =
         dexItemFactory.createFreshMethodName(
@@ -82,6 +94,10 @@
     DexEncodedMethod firstConstructor = constructors.stream().findFirst().get();
     DexProto oldProto = firstConstructor.getProto();
 
+    if (isTrivialMerge()) {
+      return oldProto;
+    }
+
     List<DexType> parameters = new ArrayList<>();
     Collections.addAll(parameters, oldProto.parameters.values);
     parameters.add(dexItemFactory.intType);
@@ -95,8 +111,9 @@
   }
 
   /** Synthesize a new method which selects the constructor based on a parameter type. */
-  void mergeMany(
+  void merge(
       HorizontalClassMergerGraphLens.Builder lensBuilder,
+      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       Reference2IntMap<DexType> classIdentifiers) {
     // Tree map as must be sorted.
     Int2ReferenceSortedMap<DexMethod> typeConstructorClassMap = new Int2ReferenceAVLTreeMap<>();
@@ -119,7 +136,10 @@
         appView.dexItemFactory().createMethod(target.type, newProto, dexItemFactory.initMethodName);
     ConstructorEntryPointSynthesizedCode synthesizedCode =
         new ConstructorEntryPointSynthesizedCode(
-            typeConstructorClassMap, newConstructorReference, originalConstructorReference);
+            typeConstructorClassMap,
+            newConstructorReference,
+            classIdField,
+            originalConstructorReference);
     DexEncodedMethod newConstructor =
         new DexEncodedMethod(
             newConstructorReference,
@@ -130,46 +150,24 @@
             classFileVersion,
             true);
 
-    // Map each old constructor to the newly synthesized constructor in the graph lens.
-    for (DexEncodedMethod oldConstructor : constructors) {
-      lensBuilder.mapConstructor(
-          oldConstructor.method,
-          newConstructorReference,
-          classIdentifiers.getInt(oldConstructor.getHolderType()));
+    if (isTrivialMerge()) {
+      // The constructor does not require the additional argument, just map it like a regular
+      // method.
+      lensBuilder.mapMethod(constructors.iterator().next().method, newConstructorReference);
+    } else {
+      // Map each old constructor to the newly synthesized constructor in the graph lens.
+      for (DexEncodedMethod oldConstructor : constructors) {
+        lensBuilder.mapMergedConstructor(
+            oldConstructor.method,
+            newConstructorReference,
+            classIdentifiers.getInt(oldConstructor.getHolderType()));
+      }
     }
     // Map the first constructor to the newly synthesized constructor.
     lensBuilder.recordExtraOriginalSignature(originalConstructorReference, newConstructorReference);
 
     target.addDirectMethod(newConstructor);
-  }
 
-  /**
-   * The constructor does not conflict with any other constructors. Add the constructor (if any) to
-   * the target directly.
-   */
-  void mergeTrivial(HorizontalClassMergerGraphLens.Builder lensBuilder) {
-    assert constructors.size() <= 1;
-
-    if (!constructors.isEmpty()) {
-      DexEncodedMethod constructor = constructors.iterator().next();
-
-      // Only move the constructor if it is not already in the target type.
-      if (constructor.holder() != target.type) {
-        DexEncodedMethod newConstructor =
-            constructor.toRenamedHolderMethod(target.type, dexItemFactory);
-        target.addDirectMethod(constructor);
-        lensBuilder.moveConstructor(constructor.method, newConstructor.method);
-      }
-    }
-  }
-
-  public void merge(
-      HorizontalClassMergerGraphLens.Builder lensBuilder,
-      Reference2IntMap<DexType> classIdentifiers) {
-    if (constructors.size() <= 1) {
-      mergeTrivial(lensBuilder);
-    } else {
-      mergeMany(lensBuilder, classIdentifiers);
-    }
+    fieldAccessChangesBuilder.fieldWrittenByMethod(classIdField, newConstructorReference);
   }
 }
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 b843e09..f7ac858 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ClassMergingEnqueuerExtension;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -76,6 +77,8 @@
     Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
     HorizontalClassMergerGraphLens.Builder lensBuilder =
         new HorizontalClassMergerGraphLens.Builder();
+    FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder =
+        new FieldAccessInfoCollectionModifier.Builder();
 
     // TODO(b/166577694): Replace Collection<DexProgramClass> with MergeGroup
     for (Collection<DexProgramClass> group : groups) {
@@ -88,12 +91,14 @@
         mergedClasses.put(clazz.type, target.type);
       }
 
-      ClassMerger merger = new ClassMerger(appView, lensBuilder, target, group);
+      ClassMerger merger =
+          new ClassMerger(appView, lensBuilder, fieldAccessChangesBuilder, target, group);
       merger.mergeGroup();
     }
 
     HorizontalClassMergerGraphLens lens =
-        new TreeFixer(appView, lensBuilder, mergedClasses).fixupTypeReferences();
+        new TreeFixer(appView, lensBuilder, fieldAccessChangesBuilder, mergedClasses)
+            .fixupTypeReferences();
     return lens;
   }
 }
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 bd2751c..4389282 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -126,16 +126,13 @@
       return this;
     }
 
+    /** Unidirectional mapping from one method to another. */
     public Builder recordExtraOriginalSignature(DexMethod from, DexMethod to) {
       extraOriginalMethodSignatures.put(to, extraOriginalMethodSignatures.getOrDefault(from, from));
       return this;
     }
 
-    /**
-     * Unidirectional mapping from one method to another. This seems to only be used by synthesized
-     * constructors so is private for now. See {@link Builder#moveConstructor(DexMethod,
-     * DexMethod)}.
-     */
+    /** Unidirectional mapping from one method to another. */
     public Builder mapMethod(DexMethod from, DexMethod to) {
       for (DexMethod existingFrom :
           completeInverseMethodMap.getOrDefault(from, Collections.emptySet())) {
@@ -151,7 +148,7 @@
       return this;
     }
 
-    public boolean hasOriginalConstructorSignatureMappingFor(DexMethod method) {
+    public boolean hasExtraSignatureMappingFor(DexMethod method) {
       return extraOriginalMethodSignatures.containsKey(method);
     }
 
@@ -167,18 +164,10 @@
      * @param constructorId The id that must be appended to the constructor call to ensure the
      *     correct constructor is called.
      */
-    public Builder mapConstructor(DexMethod from, DexMethod to, int constructorId) {
+    public Builder mapMergedConstructor(DexMethod from, DexMethod to, int constructorId) {
       mapMethod(from, to);
       constructorIds.put(from, constructorId);
       return this;
     }
-
-    /**
-     * Bidirectional mapping from one constructor to another. When a single constructor is simply
-     * moved from one class to another, we can uniquely map the new constructor back to the old one.
-     */
-    public Builder moveConstructor(DexMethod from, DexMethod to) {
-      return moveMethod(from, to);
-    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 2d9be81..7d0d5c0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.AnnotationFixer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.utils.OptionalBool;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -30,14 +31,17 @@
   private final Map<DexType, DexType> mergedClasses;
   private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
+  private final FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder;
   private final AppView<AppInfoWithLiveness> appView;
 
   public TreeFixer(
       AppView<AppInfoWithLiveness> appView,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
+      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       Map<DexType, DexType> mergedClasses) {
     this.mergedClasses = mergedClasses;
     this.lensBuilder = lensBuilder;
+    this.fieldAccessChangesBuilder = fieldAccessChangesBuilder;
     this.appView = appView;
   }
 
@@ -50,6 +54,8 @@
     }
     HorizontalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
 
+    fieldAccessChangesBuilder.build(this::fixupMethod).modify(appView);
+
     if (lens != null) {
       new AnnotationFixer(lens).run(appView.appInfo().classes());
     }
@@ -65,7 +71,7 @@
 
     // If the method is a synthesized method, then don't record the original signature.
     if ((method.getCode() instanceof ConstructorEntryPointSynthesizedCode)) {
-      assert lensBuilder.hasOriginalConstructorSignatureMappingFor(methodReference);
+      assert lensBuilder.hasExtraSignatureMappingFor(methodReference);
       lensBuilder.recordExtraOriginalSignature(methodReference, newMethodReference);
       lensBuilder.mapMethod(methodReference, newMethodReference);
     } else {
diff --git a/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java b/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
new file mode 100644
index 0000000..4e9d977
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
@@ -0,0 +1,96 @@
+// 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.shaking;
+
+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.FieldAccessInfoCollectionImpl;
+import com.android.tools.r8.graph.FieldAccessInfoImpl;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+public class FieldAccessInfoCollectionModifier {
+
+  static class FieldReferences {
+    final List<DexMethod> writeContexts = new ArrayList<>();
+    final List<DexMethod> readContexts = new ArrayList<>();
+
+    void fixUpMethods(List<DexMethod> methods, Function<DexMethod, DexMethod> fixUpMethod) {
+      for (int i = 0; i < methods.size(); i++) {
+        DexMethod method = methods.get(i);
+        DexMethod newMethod = fixUpMethod.apply(method);
+        if (method != newMethod) {
+          methods.set(i, newMethod);
+        }
+      }
+    }
+
+    void fixUp(Function<DexMethod, DexMethod> fixUpMethod) {
+      fixUpMethods(writeContexts, fixUpMethod);
+      fixUpMethods(readContexts, fixUpMethod);
+    }
+  }
+
+  final Map<DexField, FieldReferences> newFieldAccesses;
+
+  FieldAccessInfoCollectionModifier(Map<DexField, FieldReferences> newFieldAccesses) {
+    this.newFieldAccesses = newFieldAccesses;
+  }
+
+  void forEachFieldAccess(
+      AppView<?> appView,
+      Collection<DexMethod> methods,
+      DexField field,
+      BiConsumer<DexField, ProgramMethod> record) {
+    for (DexMethod method : methods) {
+      ProgramMethod programMethod =
+          appView.definitionFor(method.holder).asProgramClass().lookupProgramMethod(method);
+      record.accept(field, programMethod);
+    }
+  }
+
+  public void modify(AppView<AppInfoWithLiveness> appView) {
+    FieldAccessInfoCollectionImpl impl = appView.appInfo().getMutableFieldAccessInfoCollection();
+    newFieldAccesses.forEach(
+        (field, info) -> {
+          FieldAccessInfoImpl fieldAccessInfo = new FieldAccessInfoImpl(field);
+          forEachFieldAccess(appView, info.readContexts, field, fieldAccessInfo::recordRead);
+          forEachFieldAccess(appView, info.writeContexts, field, fieldAccessInfo::recordWrite);
+          impl.extend(field, fieldAccessInfo);
+        });
+  }
+
+  public static class Builder {
+    final Map<DexField, FieldReferences> newFieldAccesses = new IdentityHashMap<>();
+
+    public Builder() {}
+
+    public FieldAccessInfoCollectionModifier build(Function<DexMethod, DexMethod> fixupMethod) {
+      for (FieldReferences fieldReference : newFieldAccesses.values()) {
+        fieldReference.fixUp(fixupMethod);
+      }
+      return new FieldAccessInfoCollectionModifier(newFieldAccesses);
+    }
+
+    FieldReferences getFieldReferences(DexField field) {
+      return newFieldAccesses.computeIfAbsent(field, ignore -> new FieldReferences());
+    }
+
+    public void fieldReadByMethod(DexField field, DexMethod method) {
+      getFieldReferences(field).readContexts.add(method);
+    }
+
+    public void fieldWrittenByMethod(DexField field, DexMethod method) {
+      getFieldReferences(field).writeContexts.add(method);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
new file mode 100644
index 0000000..226af98
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
@@ -0,0 +1,79 @@
+// 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.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.ConstructorMergingTest.A;
+import com.android.tools.r8.classmerging.horizontal.ConstructorMergingTest.B;
+import com.android.tools.r8.classmerging.horizontal.ConstructorMergingTest.Main;
+import org.junit.Test;
+
+public class MergedConstructorForwardingTest extends HorizontalClassMergingTestBase {
+
+  public MergedConstructorForwardingTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("42", "13", "21", "39")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+                // TODO(b/165517236): Explicitly check classes have been merged.
+              } else {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      this(42);
+    }
+
+    public A(long x) {
+      System.out.println(x);
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B() {
+      this(7);
+    }
+
+    public B(long y) {
+      System.out.println(y * 3);
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      a = new A(13);
+      B b = new B();
+      b = new B(13);
+    }
+  }
+}