| // 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.MethodResolutionResult.SingleResolutionResult; |
| import com.android.tools.r8.graph.ParameterAnnotationsList; |
| import com.android.tools.r8.graph.ProgramMethod; |
| 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); |
| DexEncodedMethod representativeMethod = representative.getDefinition(); |
| 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, |
| representativeMethod.getApiLevelForDefinition(), |
| representativeMethod.getApiLevelForCode()); |
| 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); |
| } |
| } |