blob: d1e98c764d7fc0c243f3392d35fc13ead418aeff [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 static com.google.common.base.Predicates.not;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
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.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.code.ClassInitializerMerger;
import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The class merger is responsible for moving methods from the sources in {@link ClassMerger#group}
* into the target of {@link ClassMerger#group}. 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 static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final Mode mode;
private final MergeGroup group;
private final DexItemFactory dexItemFactory;
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 Collection<VirtualMethodMerger> virtualMethodMergers;
private ClassMerger(
AppView<? extends AppInfoWithClassHierarchy> appView,
IRCodeProvider codeProvider,
Mode mode,
HorizontalClassMergerGraphLens.Builder lensBuilder,
MergeGroup group,
Collection<VirtualMethodMerger> virtualMethodMergers) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
this.group = group;
this.lensBuilder = lensBuilder;
this.mode = mode;
// Field mergers.
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, codeProvider, group, lensBuilder, mode);
this.virtualMethodMergers = virtualMethodMergers;
buildClassIdentifierMap();
}
void buildClassIdentifierMap() {
classIdentifiers.put(group.getTarget().getType(), 0);
group.forEachSource(clazz -> classIdentifiers.put(clazz.getType(), classIdentifiers.size()));
}
void mergeDirectMethods(
SyntheticArgumentClass syntheticArgumentClass,
SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
mergeInstanceInitializers(syntheticArgumentClass, syntheticInitializerConverterBuilder);
mergeStaticClassInitializers(syntheticInitializerConverterBuilder);
group.forEach(this::mergeDirectMethods);
}
void mergeStaticClassInitializers(
SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
if (classInitializerMerger.isEmpty()) {
return;
}
// Synthesize a new class initializer with a fresh synthetic original name.
DexMethod newMethodReference =
dexItemFactory.createClassInitializer(group.getTarget().getType());
DexMethod syntheticMethodReference =
newMethodReference.withName("$r8$clinit$synthetic", dexItemFactory);
lensBuilder.recordNewMethodSignature(syntheticMethodReference, newMethodReference, true);
DexEncodedMethod definition =
new DexEncodedMethod(
newMethodReference,
MethodAccessFlags.createForClassInitializer(),
MethodTypeSignature.noSignature(),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
classInitializerMerger.getCode(syntheticMethodReference),
DexEncodedMethod.D8_R8_SYNTHESIZED,
classInitializerMerger.getCfVersion());
classMethodsBuilder.addDirectMethod(definition);
// In case we didn't synthesize CF code, we register the class initializer for conversion to dex
// after merging.
if (!definition.getCode().isCfCode()) {
assert appView.options().isGeneratingDex();
assert mode.isFinal();
syntheticInitializerConverterBuilder.add(new ProgramMethod(group.getTarget(), definition));
}
}
void mergeDirectMethods(DexProgramClass toMerge) {
toMerge.forEachProgramDirectMethod(
method -> {
DexEncodedMethod definition = method.getDefinition();
if (definition.isClassInitializer()) {
lensBuilder.moveMethod(
method.getReference(),
dexItemFactory.createClassInitializer(group.getTarget().getType()));
} else if (!definition.isInstanceInitializer()) {
DexMethod newMethod =
method.getReference().withHolder(group.getTarget().getType(), dexItemFactory);
if (!classMethodsBuilder.isFresh(newMethod)) {
newMethod = renameDirectMethod(method);
}
classMethodsBuilder.addDirectMethod(definition.toTypeSubstitutedMethod(newMethod));
if (definition.getReference() != 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.getMethodCollection().clearDirectMethods();
}
/**
* Find a new name for the method.
*
* @param method The class the method originally belonged to.
*/
DexMethod renameDirectMethod(ProgramMethod method) {
assert method.getDefinition().belongsToDirectPool();
return dexItemFactory.createFreshMethodNameWithoutHolder(
method.getName().toSourceString(),
method.getProto(),
group.getTarget().getType(),
classMethodsBuilder::isFresh);
}
void mergeInstanceInitializers(
SyntheticArgumentClass syntheticArgumentClass,
SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
instanceInitializerMergers.forEach(
merger ->
merger.merge(
classMethodsBuilder, syntheticArgumentClass, syntheticInitializerConverterBuilder));
}
void mergeMethods(
SyntheticArgumentClass syntheticArgumentClass,
SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
mergeVirtualMethods();
mergeDirectMethods(syntheticArgumentClass, syntheticInitializerConverterBuilder);
classMethodsBuilder.setClassMethods(group.getTarget());
}
void mergeVirtualMethods() {
virtualMethodMergers.forEach(
merger -> merger.merge(classMethodsBuilder, lensBuilder, classIdentifiers));
group.forEachSource(clazz -> clazz.getMethodCollection().clearVirtualMethods());
}
void appendClassIdField() {
assert appView.hasLiveness();
assert mode.isInitial();
boolean deprecated = false;
boolean d8R8Synthesized = true;
DexEncodedField classIdField =
new DexEncodedField(
group.getClassIdField(),
FieldAccessFlags.createPublicFinalSynthetic(),
FieldTypeSignature.noSignature(),
DexAnnotationSet.empty(),
null,
deprecated,
d8R8Synthesized);
// For the $r8$classId synthesized fields, we try to over-approximate the set of values it may
// have. For example, for a merge group of size 4, we may compute the set {0, 2, 3}, if the
// instances with $r8$classId == 1 ends up dead as a result of optimizations). If no instances
// end up being dead, we would compute the set {0, 1, 2, 3}. The latter information does not
// provide any value, and therefore we should not save it in the optimization info. In order to
// be able to recognize that {0, 1, 2, 3} is useless, we record that the value of the field is
// known to be in [0; 3] here.
NumberFromIntervalValue abstractValue = new NumberFromIntervalValue(0, group.size() - 1);
feedback.recordFieldHasAbstractValue(classIdField, appView.withLiveness(), abstractValue);
classInstanceFieldsMerger.setClassIdField(classIdField);
}
void fixAccessFlags() {
if (Iterables.any(group.getSources(), not(DexProgramClass::isAbstract))) {
group.getTarget().getAccessFlags().demoteFromAbstract();
}
if (Iterables.any(group.getSources(), not(DexProgramClass::isFinal))) {
group.getTarget().getAccessFlags().demoteFromFinal();
}
}
void fixNestMemberAttributes() {
if (group.getTarget().isInANest() && !group.getTarget().hasNestMemberAttributes()) {
for (DexProgramClass clazz : group.getSources()) {
if (clazz.hasNestMemberAttributes()) {
// The nest host has been merged into a nest member.
group.getTarget().clearNestHost();
group.getTarget().setNestMemberAttributes(clazz.getNestMembersClassAttributes());
group
.getTarget()
.removeNestMemberAttributes(
nestMemberAttribute ->
nestMemberAttribute.getNestMember() == group.getTarget().getType());
break;
}
}
}
}
private void mergeAnnotations() {
assert group.getClasses().stream().filter(DexDefinition::hasAnnotations).count() <= 1;
for (DexProgramClass clazz : group.getSources()) {
if (clazz.hasAnnotations()) {
group.getTarget().setAnnotations(clazz.annotations());
break;
}
}
}
private void mergeInterfaces() {
Set<DexType> interfaces = Sets.newLinkedHashSet();
if (group.isInterfaceGroup()) {
// Add all implemented interfaces from the merge group to the target class, ignoring
// implemented interfaces that are part of the merge group.
Set<DexType> groupTypes =
SetUtils.newImmutableSet(
builder -> group.forEach(clazz -> builder.accept(clazz.getType())));
group.forEach(
clazz -> {
for (DexType itf : clazz.getInterfaces()) {
if (!groupTypes.contains(itf)) {
interfaces.add(itf);
}
}
});
} else {
// Add all implemented interfaces from the merge group to the target class.
group.forEach(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
}
group.getTarget().setInterfaces(DexTypeList.create(interfaces));
}
void mergeFields() {
if (group.hasClassIdField()) {
appendClassIdField();
}
mergeInstanceFields();
mergeStaticFields();
}
void mergeInstanceFields() {
group.forEachSource(DexClass::clearInstanceFields);
group.getTarget().setInstanceFields(classInstanceFieldsMerger.merge());
}
void mergeStaticFields() {
group.forEachSource(classStaticFieldsMerger::addFields);
classStaticFieldsMerger.merge(group.getTarget());
group.forEachSource(clazz -> clazz.setStaticFields(null));
}
public void mergeGroup(
SyntheticArgumentClass syntheticArgumentClass,
SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
fixAccessFlags();
fixNestMemberAttributes();
mergeAnnotations();
mergeInterfaces();
mergeFields();
mergeMethods(syntheticArgumentClass, syntheticInitializerConverterBuilder);
}
public static class Builder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final IRCodeProvider codeProvider;
private Mode mode;
private final MergeGroup group;
public Builder(
AppView<? extends AppInfoWithClassHierarchy> appView,
IRCodeProvider codeProvider,
MergeGroup group,
Mode mode) {
this.appView = appView;
this.codeProvider = codeProvider;
this.group = group;
this.mode = mode;
}
private List<VirtualMethodMerger> createVirtualMethodMergers() {
Map<DexMethodSignature, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
new LinkedHashMap<>();
group.forEach(
clazz ->
clazz.forEachProgramVirtualMethod(
virtualMethod ->
virtualMethodMergerBuilders
.computeIfAbsent(
virtualMethod.getReference().getSignature(),
ignore -> new VirtualMethodMerger.Builder())
.add(virtualMethod)));
List<VirtualMethodMerger> virtualMethodMergers =
new ArrayList<>(virtualMethodMergerBuilders.size());
for (VirtualMethodMerger.Builder builder : virtualMethodMergerBuilders.values()) {
virtualMethodMergers.add(builder.build(appView, group));
}
return virtualMethodMergers;
}
private void createClassIdField() {
// TODO(b/165498187): ensure the name for the field is fresh
DexItemFactory dexItemFactory = appView.dexItemFactory();
group.setClassIdField(
dexItemFactory.createField(
group.getTarget().getType(), dexItemFactory.intType, CLASS_ID_FIELD_NAME));
}
public ClassMerger build(
HorizontalClassMergerGraphLens.Builder lensBuilder) {
List<VirtualMethodMerger> virtualMethodMergers = createVirtualMethodMergers();
boolean requiresClassIdField =
virtualMethodMergers.stream()
.anyMatch(virtualMethodMerger -> !virtualMethodMerger.isNopOrTrivial());
if (requiresClassIdField) {
assert mode.isInitial();
createClassIdField();
}
return new ClassMerger(appView, codeProvider, mode, lensBuilder, group, virtualMethodMergers);
}
}
}