blob: 394c8848dae64938c672c29f7433a026bee2f318 [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.cf.CfVersion;
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.DexEncodedMethod;
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.DexType;
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.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.horizontalclassmerging.code.VirtualMethodEntryPointSynthesizedCode;
import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.structural.Ordered;
import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.ArrayList;
import java.util.List;
public class VirtualMethodMerger {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final DexItemFactory dexItemFactory;
private final MergeGroup group;
private final List<ProgramMethod> methods;
private final DexMethod superMethod;
public VirtualMethodMerger(
AppView<? extends AppInfoWithClassHierarchy> appView,
MergeGroup group,
List<ProgramMethod> methods,
DexMethod superMethod) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
this.group = group;
this.methods = methods;
this.superMethod = superMethod;
}
public static class Builder {
private final List<ProgramMethod> methods = new ArrayList<>();
public Builder add(ProgramMethod method) {
methods.add(method);
return this;
}
/** Get the super method handle if this method overrides a parent method. */
private DexMethod superMethod(
AppView<? extends AppInfoWithClassHierarchy> appView, DexProgramClass target) {
DexMethod template = methods.iterator().next().getReference();
SingleResolutionResult resolutionResult =
appView
.appInfo()
.resolveMethodOnClass(template, target.getSuperType())
.asSingleResolution();
if (resolutionResult == null || resolutionResult.getResolvedMethod().isAbstract()) {
// If there is no super method or the method is abstract it should not be called.
return null;
}
if (resolutionResult.getResolvedHolder().isInterface()) {
// Ensure that invoke virtual isn't called on an interface method.
return resolutionResult
.getResolvedMethod()
.getReference()
.withHolder(target.getSuperType(), appView.dexItemFactory());
}
return resolutionResult.getResolvedMethod().getReference();
}
public VirtualMethodMerger build(
AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
// If not all the classes are in the merge group, find the fallback super method to call.
DexMethod superMethod =
methods.size() < group.size() ? superMethod(appView, group.getTarget()) : null;
return new VirtualMethodMerger(appView, group, methods, superMethod);
}
}
public DexMethod getMethodReference() {
return methods.iterator().next().getReference();
}
public int getArity() {
return getMethodReference().getArity();
}
private DexMethod moveMethod(ClassMethodsBuilder classMethodsBuilder, ProgramMethod oldMethod) {
DexMethod oldMethodReference = oldMethod.getReference();
DexMethod method =
dexItemFactory.createFreshMethodNameWithHolder(
oldMethodReference.name.toSourceString(),
oldMethod.getHolderType(),
oldMethodReference.proto,
group.getTarget().getType(),
classMethodsBuilder::isFresh);
DexEncodedMethod encodedMethod = oldMethod.getDefinition().toTypeSubstitutedMethod(method);
MethodAccessFlags flags = encodedMethod.getAccessFlags();
flags.unsetProtected();
flags.unsetPublic();
flags.setPrivate();
classMethodsBuilder.addDirectMethod(encodedMethod);
return encodedMethod.getReference();
}
private MethodAccessFlags getAccessFlags() {
Iterable<MethodAccessFlags> allFlags =
Iterables.transform(methods, ProgramMethod::getAccessFlags);
MethodAccessFlags result = allFlags.iterator().next().copy();
assert Iterables.all(allFlags, flags -> !flags.isNative());
assert !result.isStrict() || Iterables.all(allFlags, MethodAccessFlags::isStrict);
assert !result.isSynchronized() || Iterables.all(allFlags, MethodAccessFlags::isSynchronized);
if (result.isAbstract() && Iterables.any(allFlags, flags -> !flags.isAbstract())) {
result.unsetAbstract();
}
if (result.isBridge() && Iterables.any(allFlags, flags -> !flags.isBridge())) {
result.unsetBridge();
}
if (result.isFinal()) {
if (methods.size() < group.size() || Iterables.any(allFlags, flags -> !flags.isFinal())) {
result.unsetFinal();
}
}
if (result.isSynthetic() && Iterables.any(allFlags, flags -> !flags.isSynthetic())) {
result.unsetSynthetic();
}
if (result.isVarargs() && Iterables.any(allFlags, flags -> !flags.isVarargs())) {
result.unsetVarargs();
}
result.unsetDeclaredSynchronized();
return result;
}
private DexMethod getNewMethodReference() {
return ListUtils.first(methods).getReference().withHolder(group.getTarget(), dexItemFactory);
}
/**
* If there is a super method and all methods are abstract, then we can simply remove all abstract
* methods.
*/
private boolean isNop() {
return superMethod != null
&& Iterables.all(methods, method -> method.getDefinition().isAbstract());
}
/**
* If the method is present on all classes in the merge group, and there is at most one
* non-abstract method, then we can simply move that method (or the first abstract method) to the
* target class.
*/
private boolean isTrivial() {
if (superMethod != null) {
return false;
}
if (methods.size() == 1) {
return true;
}
int numberOfNonAbstractMethods =
Iterables.size(Iterables.filter(methods, method -> !method.getDefinition().isAbstract()));
return numberOfNonAbstractMethods <= 1;
}
boolean isNopOrTrivial() {
return isNop() || isTrivial();
}
/**
* If there is only a single method that does not override anything then it is safe to just move
* it to the target type if it is not already in it.
*/
private void mergeTrivial(
ClassMethodsBuilder classMethodsBuilder, HorizontalClassMergerGraphLens.Builder lensBuilder) {
DexMethod newMethodReference = getNewMethodReference();
// Find the first non-abstract method. If all are abstract, then select the first method.
ProgramMethod representative =
Iterables.find(methods, method -> !method.getDefinition().isAbstract(), null);
if (representative == null) {
representative = ListUtils.first(methods);
}
for (ProgramMethod method : methods) {
if (method.getReference() == representative.getReference()) {
lensBuilder.moveMethod(method.getReference(), newMethodReference);
} else {
lensBuilder.mapMethod(method.getReference(), newMethodReference);
}
}
DexEncodedMethod newMethod;
if (representative.getHolder() == group.getTarget()) {
newMethod = representative.getDefinition();
} else {
// If the method is not in the target type, move it.
OptionalBool isLibraryMethodOverride =
representative.getDefinition().isLibraryMethodOverride();
newMethod =
representative
.getDefinition()
.toTypeSubstitutedMethod(
newMethodReference,
builder -> builder.setIsLibraryMethodOverrideIfKnown(isLibraryMethodOverride));
}
newMethod.getAccessFlags().unsetFinal();
classMethodsBuilder.addVirtualMethod(newMethod);
}
public void merge(
ClassMethodsBuilder classMethodsBuilder,
HorizontalClassMergerGraphLens.Builder lensBuilder,
Reference2IntMap<DexType> classIdentifiers) {
assert !methods.isEmpty();
// Handle trivial merges.
if (isNopOrTrivial()) {
mergeTrivial(classMethodsBuilder, lensBuilder);
return;
}
Int2ReferenceSortedMap<DexMethod> classIdToMethodMap = new Int2ReferenceAVLTreeMap<>();
CfVersion classFileVersion = null;
ProgramMethod representative = null;
for (ProgramMethod method : methods) {
if (method.getDefinition().isAbstract()) {
continue;
}
if (method.getDefinition().hasClassFileVersion()) {
CfVersion methodVersion = method.getDefinition().getClassFileVersion();
classFileVersion = Ordered.maxIgnoreNull(classFileVersion, methodVersion);
}
DexMethod newMethod = moveMethod(classMethodsBuilder, method);
lensBuilder.recordNewMethodSignature(method.getReference(), newMethod);
classIdToMethodMap.put(classIdentifiers.getInt(method.getHolderType()), newMethod);
if (representative == null) {
representative = method;
}
}
assert representative != null;
// Use the first of the original methods as the original method for the merged constructor.
DexMethod originalMethodReference =
appView.graphLens().getOriginalMethodSignature(representative.getReference());
DexMethod bridgeMethodReference =
dexItemFactory.createFreshMethodNameWithoutHolder(
originalMethodReference.getName().toSourceString() + "$bridge",
originalMethodReference.proto,
originalMethodReference.getHolderType(),
classMethodsBuilder::isFresh);
DexMethod newMethodReference = getNewMethodReference();
AbstractSynthesizedCode synthesizedCode =
new VirtualMethodEntryPointSynthesizedCode(
classIdToMethodMap,
group.getClassIdField(),
superMethod,
newMethodReference,
bridgeMethodReference,
appView.dexItemFactory());
DexEncodedMethod newMethod =
new DexEncodedMethod(
newMethodReference,
getAccessFlags(),
MethodTypeSignature.noSignature(),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
synthesizedCode,
true,
classFileVersion);
if (!representative.getDefinition().isLibraryMethodOverride().isUnknown()) {
newMethod.setLibraryMethodOverride(representative.getDefinition().isLibraryMethodOverride());
}
// Map each old non-abstract method to the newly synthesized method in the graph lens.
for (ProgramMethod oldMethod : methods) {
lensBuilder.mapMethod(oldMethod.getReference(), newMethodReference);
}
// Add a mapping from a synthetic name to the synthetic merged method.
lensBuilder.recordNewMethodSignature(bridgeMethodReference, newMethodReference);
classMethodsBuilder.addVirtualMethod(newMethod);
}
}