blob: 73d1e6801b1b5113d63e04067d8895c40a8b3e7f [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.AppInfoWithLiveness;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
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}.
*/
public class ClassMerger {
public static final String CLASS_ID_FIELD_NAME = "$r8$classId";
private final AppView<AppInfoWithLiveness> appView;
private final DexProgramClass target;
private final Collection<DexProgramClass> toMergeGroup;
private final DexItemFactory dexItemFactory;
private final HorizontalClassMergerGraphLens.Builder lensBuilder;
private final HorizontallyMergedClasses.Builder mergedClassesBuilder;
private final FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder;
private final Reference2IntMap<DexType> classIdentifiers = new Reference2IntOpenHashMap<>();
private final Collection<VirtualMethodMerger> virtualMethodMergers;
private final Collection<ConstructorMerger> constructorMergers;
private final DexField classIdField;
private ClassMerger(
AppView<AppInfoWithLiveness> appView,
HorizontalClassMergerGraphLens.Builder lensBuilder,
HorizontallyMergedClasses.Builder mergedClassesBuilder,
FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
DexProgramClass target,
Collection<DexProgramClass> toMergeGroup,
DexField classIdField,
Collection<VirtualMethodMerger> virtualMethodMergers,
Collection<ConstructorMerger> constructorMergers) {
this.appView = appView;
this.lensBuilder = lensBuilder;
this.mergedClassesBuilder = mergedClassesBuilder;
this.fieldAccessChangesBuilder = fieldAccessChangesBuilder;
this.target = target;
this.toMergeGroup = toMergeGroup;
this.classIdField = classIdField;
this.virtualMethodMergers = virtualMethodMergers;
this.constructorMergers = constructorMergers;
this.dexItemFactory = appView.dexItemFactory();
buildClassIdentifierMap();
}
/** Returns an iterable over all classes that should be merged into the target class. */
public Iterable<DexProgramClass> getToMergeClasses() {
return toMergeGroup;
}
/**
* Returns an iterable over both the target class as well as all classes that should be merged
* into the target class.
*/
public Iterable<DexProgramClass> getClasses() {
return Iterables.concat(Collections.singleton(target), getToMergeClasses());
}
void buildClassIdentifierMap() {
classIdentifiers.put(target.type, 0);
for (DexProgramClass toMerge : toMergeGroup) {
classIdentifiers.put(toMerge.type, classIdentifiers.size());
}
}
void merge(DexProgramClass toMerge) {
toMerge.forEachProgramDirectMethod(
method -> {
DexEncodedMethod definition = method.getDefinition();
assert !definition.isClassInitializer();
if (!definition.isInstanceInitializer()) {
DexMethod newMethod = renameMethod(method);
// TODO(b/165000217): Add all methods to `target` in one go using addDirectMethods().
target.addDirectMethod(definition.toTypeSubstitutedMethod(newMethod));
lensBuilder.moveMethod(definition.getReference(), 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(SyntheticArgumentClass syntheticArgumentClass) {
for (ConstructorMerger merger : constructorMergers) {
merger.merge(
lensBuilder, fieldAccessChangesBuilder, classIdentifiers, syntheticArgumentClass);
}
}
void mergeVirtualMethods() {
for (VirtualMethodMerger merger : virtualMethodMergers) {
merger.merge(lensBuilder, fieldAccessChangesBuilder, classIdentifiers);
}
}
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(SyntheticArgumentClass syntheticArgumentClass) {
appendClassIdField();
mergedClassesBuilder.addMergeGroup(target, toMergeGroup);
for (DexProgramClass clazz : toMergeGroup) {
merge(clazz);
}
mergeConstructors(syntheticArgumentClass);
mergeVirtualMethods();
}
public static class Builder {
private final DexProgramClass target;
private final Collection<DexProgramClass> toMergeGroup = new ArrayList<>();
private final Map<DexProto, ConstructorMerger.Builder> constructorMergerBuilders =
new IdentityHashMap<>();
private final Map<Wrapper<DexMethod>, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
new LinkedHashMap<>();
public Builder(DexProgramClass target) {
this.target = target;
setupForMethodMerging(target);
}
public Builder mergeClass(DexProgramClass toMerge) {
setupForMethodMerging(toMerge);
toMergeGroup.add(toMerge);
return this;
}
public Builder addClassesToMerge(Collection<DexProgramClass> toMerge) {
toMerge.forEach(this::mergeClass);
return this;
}
void setupForMethodMerging(DexProgramClass toMerge) {
toMerge.forEachProgramDirectMethod(
method -> {
DexEncodedMethod definition = method.getDefinition();
assert !definition.isClassInitializer();
if (definition.isInstanceInitializer()) {
addConstructor(method);
}
});
toMerge.forEachProgramVirtualMethod(this::addVirtualMethod);
}
void addConstructor(ProgramMethod method) {
assert method.getDefinition().isInstanceInitializer();
constructorMergerBuilders
.computeIfAbsent(
method.getDefinition().getProto(), ignore -> new ConstructorMerger.Builder())
.add(method.getDefinition());
}
void addVirtualMethod(ProgramMethod method) {
assert method.getDefinition().isNonPrivateVirtualMethod();
virtualMethodMergerBuilders
.computeIfAbsent(
MethodSignatureEquivalence.get().wrap(method.getReference()),
ignore -> new VirtualMethodMerger.Builder())
.add(method);
}
public ClassMerger build(
AppView<AppInfoWithLiveness> appView,
HorizontallyMergedClasses.Builder mergedClassesBuilder,
HorizontalClassMergerGraphLens.Builder lensBuilder,
FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
// TODO(b/165498187): ensure the name for the field is fresh
DexField classIdField =
dexItemFactory.createField(target.type, dexItemFactory.intType, CLASS_ID_FIELD_NAME);
Collection<VirtualMethodMerger> virtualMethodMergers =
new ArrayList<>(virtualMethodMergerBuilders.size());
for (VirtualMethodMerger.Builder builder : virtualMethodMergerBuilders.values()) {
virtualMethodMergers.add(builder.build(appView, target, classIdField));
}
Collection<ConstructorMerger> constructorMergers =
new ArrayList<>(constructorMergerBuilders.size());
for (ConstructorMerger.Builder builder : constructorMergerBuilders.values()) {
constructorMergers.add(builder.build(appView, target, classIdField));
}
return new ClassMerger(
appView,
lensBuilder,
mergedClassesBuilder,
fieldAccessChangesBuilder,
target,
toMergeGroup,
classIdField,
virtualMethodMergers,
constructorMergers);
}
}
}