blob: 51ef31eae4746131c4b886062c75bfd96e18f72b [file] [log] [blame]
// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.horizontalclassmerging;
import com.android.tools.r8.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;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Map;
/**
* The class merger is responsible for moving methods from {@link ClassMerger#toMergeGroup} into the
* class {@link ClassMerger#target}. While performing merging, this class tracks which methods have
* been moved, as well as which fields have been remapped in the {@link ClassMerger#lensBuilder}.
*/
class ClassMerger {
private final AppView<?> appView;
private final DexProgramClass target;
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.dexItemFactory = appView.dexItemFactory();
// TODO(b/165498187): ensure the name for the field is fresh
classIdField = dexItemFactory.createField(target.type, dexItemFactory.intType, "$r8$classId");
buildClassIdentifierMap();
}
Wrapper<DexMethod> bySignature(DexMethod method) {
return MethodSignatureEquivalence.get().wrap(method);
}
void addConstructor(DexEncodedMethod method) {
assert method.isInstanceInitializer();
constructorMergers
.computeIfAbsent(method.proto(), ignore -> new ConstructorMerger.Builder())
.add(method);
}
void buildClassIdentifierMap() {
classIdentifiers.put(target.type, 0);
for (DexProgramClass toMerge : toMergeGroup) {
classIdentifiers.put(toMerge.type, classIdentifiers.size());
}
}
void merge(DexProgramClass toMerge) {
toMerge.forEachProgramMethod(
programMethod -> {
DexEncodedMethod method = programMethod.getDefinition();
assert !method.isClassInitializer();
if (method.isInstanceInitializer()) {
addConstructor(method);
} else {
// TODO(b/166427795): Ensure that overriding relationships are not changed.
assert method.isVirtualMethod();
DexMethod newMethod = renameMethod(programMethod);
// TODO(b/165000217): Add all methods to `target` in one go using addVirtualMethods().;
target.addVirtualMethod(method.toTypeSubstitutedMethod(newMethod));
lensBuilder.moveMethod(method.method, newMethod);
}
});
// Clear the members of the class to be merged since they have now been moved to the target.
toMerge.setVirtualMethods(null);
toMerge.setDirectMethods(null);
toMerge.setInstanceFields(null);
toMerge.setStaticFields(null);
}
/**
* Find a new name for the method.
*
* @param method The class the method originally belonged to.
*/
DexMethod renameMethod(ProgramMethod method) {
return dexItemFactory.createFreshMethodName(
method.getDefinition().method.name.toSourceString(),
method.getHolderType(),
method.getDefinition().proto(),
target.type,
tryMethod -> target.lookupMethod(tryMethod) == null);
}
void mergeConstructors() {
for (ConstructorMerger.Builder builder : constructorMergers.values()) {
ConstructorMerger constructorMerger = builder.build(appView, target, classIdField);
constructorMerger.merge(lensBuilder, fieldAccessChangesBuilder, classIdentifiers);
}
}
/**
* To ensure constructor merging happens correctly, add all of the target constructors methods to
* constructor mergers.
*/
void addTargetConstructors() {
target.forEachProgramDirectMethod(
programMethod -> {
DexEncodedMethod method = programMethod.getDefinition();
if (method.isInstanceInitializer()) {
addConstructor(method);
}
});
}
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);
}
mergeConstructors();
}
}