blob: ab305a33e26a9cac1db7b7c6b0b44ebd9c03e60e [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.policies;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.OptionalBool;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Policy that enforces that methods are only merged if they have the same visibility and library
* method override information.
*/
public class PreserveMethodCharacteristics extends MultiClassPolicy {
@Override
public String getName() {
return "PreserveMethodCharacteristics";
}
static class MethodCharacteristics {
private final MethodAccessFlags accessFlags;
private final boolean isAssumeNoSideEffectsMethod;
private final OptionalBool isLibraryMethodOverride;
private final boolean isMainDexRoot;
private MethodCharacteristics(
DexEncodedMethod method, boolean isAssumeNoSideEffectsMethod, boolean isMainDexRoot) {
this.accessFlags =
MethodAccessFlags.builder()
.setPrivate(method.getAccessFlags().isPrivate())
.setProtected(method.getAccessFlags().isProtected())
.setPublic(method.getAccessFlags().isPublic())
.setStrict(method.getAccessFlags().isStrict())
.setSynchronized(method.getAccessFlags().isSynchronized())
.build();
this.isAssumeNoSideEffectsMethod = isAssumeNoSideEffectsMethod;
this.isLibraryMethodOverride = method.isLibraryMethodOverride();
this.isMainDexRoot = isMainDexRoot;
}
static MethodCharacteristics create(
AppView<AppInfoWithLiveness> appView, DexEncodedMethod method) {
return new MethodCharacteristics(
method,
appView.appInfo().isAssumeNoSideEffectsMethod(method.getReference()),
appView.appInfo().getMainDexInfo().isTracedMethodRoot(method.getReference()));
}
@Override
public int hashCode() {
return Objects.hash(
accessFlags,
isAssumeNoSideEffectsMethod,
isLibraryMethodOverride.ordinal(),
isMainDexRoot);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MethodCharacteristics characteristics = (MethodCharacteristics) obj;
return accessFlags.equals(characteristics.accessFlags)
&& isAssumeNoSideEffectsMethod == characteristics.isAssumeNoSideEffectsMethod
&& isLibraryMethodOverride == characteristics.isLibraryMethodOverride
&& isMainDexRoot == characteristics.isMainDexRoot;
}
}
private final AppView<AppInfoWithLiveness> appView;
public PreserveMethodCharacteristics(AppView<AppInfoWithLiveness> appView, Mode mode) {
// This policy checks that method merging does invalidate various properties. Thus there is no
// reason to run this policy if method merging is not allowed.
assert mode.isInitial();
this.appView = appView;
}
public static class TargetGroup {
private final MergeGroup group = new MergeGroup();
private final Map<DexMethodSignature, MethodCharacteristics> methodMap = new HashMap<>();
public MergeGroup getGroup() {
return group;
}
public boolean tryAdd(AppView<AppInfoWithLiveness> appView, DexProgramClass clazz) {
Map<DexMethodSignature, MethodCharacteristics> newMethods = new HashMap<>();
for (DexEncodedMethod method : clazz.methods(this::isSubjectToMethodMerging)) {
DexMethodSignature signature = method.getSignature();
MethodCharacteristics existingCharacteristics = methodMap.get(signature);
MethodCharacteristics methodCharacteristics = MethodCharacteristics.create(appView, method);
if (existingCharacteristics == null) {
newMethods.put(signature, methodCharacteristics);
continue;
}
if (!methodCharacteristics.equals(existingCharacteristics)) {
return false;
}
}
methodMap.putAll(newMethods);
group.add(clazz);
return true;
}
private boolean isSubjectToMethodMerging(DexEncodedMethod method) {
if (method.isStatic() || (method.isPrivate() && !method.isInstanceInitializer())) {
// Static and private instance methods can easily be renamed and do not require merging.
return false;
}
return true;
}
}
@Override
public Collection<MergeGroup> apply(MergeGroup group) {
List<TargetGroup> groups = new ArrayList<>();
for (DexProgramClass clazz : group) {
boolean added = Iterables.any(groups, targetGroup -> targetGroup.tryAdd(appView, clazz));
if (!added) {
TargetGroup newGroup = new TargetGroup();
added = newGroup.tryAdd(appView, clazz);
assert added;
groups.add(newGroup);
}
}
LinkedList<MergeGroup> newGroups = new LinkedList<>();
for (TargetGroup newGroup : groups) {
if (!newGroup.getGroup().isTrivial()) {
newGroups.add(newGroup.getGroup());
}
}
return newGroups;
}
}