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);
+ }
+ }
+}