| // Copyright (c) 2023, 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.ir.optimize.info; |
| |
| import static com.google.common.base.Predicates.not; |
| |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexClassAndMethod; |
| 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.ImmediateProgramSubtypingInfo; |
| import com.android.tools.r8.graph.MethodResolutionResult; |
| import com.android.tools.r8.graph.ObjectAllocationInfoCollection; |
| import com.android.tools.r8.ir.analysis.type.DynamicType; |
| import com.android.tools.r8.ir.analysis.type.TypeElement; |
| import com.android.tools.r8.ir.analysis.value.AbstractValue; |
| import com.android.tools.r8.ir.conversion.PostMethodProcessor; |
| import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal; |
| import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph; |
| import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.shaking.KeepMethodInfo; |
| import com.android.tools.r8.utils.BitSetUtils; |
| import com.android.tools.r8.utils.MapUtils; |
| import com.android.tools.r8.utils.NumberUtils; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import com.android.tools.r8.utils.collections.DexMethodSignatureMap; |
| import com.android.tools.r8.utils.collections.DexMethodSignatureSet; |
| import java.util.BitSet; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| |
| public class MethodResolutionOptimizationInfoAnalysis { |
| |
| public static void run( |
| AppView<AppInfoWithLiveness> appView, |
| ExecutorService executorService, |
| PostMethodProcessor.Builder postMethodProcessorBuilder) |
| throws ExecutionException { |
| ImmediateProgramSubtypingInfo immediateSubtypingInfo = |
| ImmediateProgramSubtypingInfo.create(appView); |
| List<Set<DexProgramClass>> stronglyConnectedComponents = |
| new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo) |
| .computeStronglyConnectedComponents(); |
| MethodResolutionOptimizationInfoCollection.Builder builder = |
| MethodResolutionOptimizationInfoCollection.builder(); |
| ThreadUtils.processItems( |
| stronglyConnectedComponents, |
| stronglyConnectedComponent -> |
| new Traversal(appView, builder, immediateSubtypingInfo).run(stronglyConnectedComponent), |
| appView.options().getThreadingModule(), |
| executorService); |
| MethodResolutionOptimizationInfoCollection methodResolutionOptimizationInfoCollection = |
| builder.build(); |
| appView.setMethodResolutionOptimizationInfoCollection( |
| methodResolutionOptimizationInfoCollection); |
| if (!methodResolutionOptimizationInfoCollection.isEmpty()) { |
| new MethodResolutionOptimizationInfoReprocessingEnqueuer(appView) |
| .enqueueMethodForReprocessing(executorService, postMethodProcessorBuilder); |
| } |
| } |
| |
| private static class Traversal extends DepthFirstTopDownClassHierarchyTraversal { |
| |
| private final MethodResolutionOptimizationInfoCollection.Builder builder; |
| private final Map<DexProgramClass, TraversalState> states = new IdentityHashMap<>(); |
| |
| private Traversal( |
| AppView<AppInfoWithLiveness> appView, |
| MethodResolutionOptimizationInfoCollection.Builder builder, |
| ImmediateProgramSubtypingInfo immediateSubtypingInfo) { |
| super(appView, immediateSubtypingInfo); |
| this.builder = builder; |
| } |
| |
| /** |
| * Build up a state that describes which virtual methods need to have their optimization info |
| * pushed upwards in the subsequent upwards traversal (instead of naively pushing optimization |
| * info upwards for *all* virtual methods). |
| * |
| * <p>Example: In the below piece of code, we want to push the optimization for C.m() up to |
| * class B, but we do not want to push the optimization info for B.m() and C.m() up to class A, |
| * since class A does not declare the method m(). |
| * |
| * <pre> |
| * class A {} |
| * class B extends A { |
| * void m() { ... } |
| * } |
| * class C extends B { |
| * void m() { ... } |
| * } |
| * </pre> |
| */ |
| @Override |
| public void visit(DexProgramClass clazz) { |
| DownwardsTraversalState state = new DownwardsTraversalState(DexMethodSignatureSet.create()); |
| immediateSubtypingInfo.forEachImmediateProgramSuperClass( |
| clazz, |
| superClass -> { |
| DownwardsTraversalState superState = |
| states |
| .getOrDefault(superClass, DownwardsTraversalState.empty()) |
| .asDownwardsTraversalState(); |
| state.add(superState); |
| for (DexEncodedMethod method : superClass.virtualMethods()) { |
| // No need to request optimization info for the (non-existing) overrides of final |
| // methods. |
| if (method.isFinal()) { |
| continue; |
| } |
| // If the method is not abstract and does not have any optimization info, there is no |
| // need to request the optimization info for overrides in subclasses, since the join |
| // of the optimization info becomes unknown anyway. |
| if (method.isAbstract() || !method.getOptimizationInfo().isDefault()) { |
| state.virtualMethodsInSuperClasses.add(method); |
| } |
| } |
| }); |
| if (!state.isEmpty()) { |
| states.put(clazz, state); |
| } |
| } |
| |
| @Override |
| public void prune(DexProgramClass clazz) { |
| // Extract the downwards traversal state that describes what info needs to be pushed upwards. |
| DownwardsTraversalState state = |
| MapUtils.removeOrDefault(states, clazz, DownwardsTraversalState.empty()) |
| .asDownwardsTraversalState(); |
| |
| // Join the optimization info for all methods in all immediate subclasses. |
| UpwardsTraversalState newState = new UpwardsTraversalState(DexMethodSignatureMap.create()); |
| forEachSubClass( |
| clazz, |
| subClass -> { |
| UpwardsTraversalState subClassState = |
| states |
| .getOrDefault(subClass, UpwardsTraversalState.empty()) |
| .asUpwardsTraversalState(); |
| newState.join(appView, subClassState); |
| |
| // If the current class is an interface and the current subclass is not, then we need |
| // special handling to account for the fact that invoke-interface instructions may |
| // dispatch to virtual "sibling" methods that are declared on a class that is not a |
| // subtype of the interface declaring the interface method. |
| if (clazz.isInterface() && !subClass.isInterface()) { |
| handleInvokeInterfaceToSiblingMethod(clazz, subClass, state, newState); |
| } |
| }); |
| ObjectAllocationInfoCollection objectAllocationInfoCollection = |
| appView.appInfo().getObjectAllocationInfoCollection(); |
| if (objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(clazz)) { |
| for (DexEncodedMethod method : clazz.virtualMethods()) { |
| newState.joinMethodOptimizationInfo( |
| appView, method.getSignature(), DefaultMethodOptimizationInfo.getInstance()); |
| } |
| } else { |
| for (DexEncodedMethod method : clazz.virtualMethods()) { |
| KeepMethodInfo keepInfo = appView.getKeepInfo().getMethodInfo(method, clazz); |
| if (!keepInfo.isShrinkingAllowed(appView.options())) { |
| // Method is kept and could be overridden outside app (e.g., in tests). Verify we don't |
| // have any optimization info recorded for non-abstract methods. |
| assert method.isAbstract() |
| || method.getOptimizationInfo().isDefault() |
| || method.getOptimizationInfo().returnValueHasBeenPropagated(); |
| newState.joinMethodOptimizationInfo( |
| appView, method.getSignature(), DefaultMethodOptimizationInfo.getInstance()); |
| } else if (!method.isAbstract()) { |
| newState.joinMethodOptimizationInfo( |
| appView, method.getSignature(), method.getOptimizationInfo()); |
| } |
| } |
| } |
| |
| // Record the computed optimization info for all non-final virtual methods on the current |
| // class. |
| if (!clazz.isFinal()) { |
| for (DexEncodedMethod method : clazz.virtualMethods(not(DexEncodedMethod::isFinal))) { |
| MethodOptimizationInfo info = newState.getMethodOptimizationInfo(method); |
| assert info.isDefault() || info.isMethodResolutionOptimizationInfo(); |
| builder.add(method.getReference(), info); |
| } |
| } |
| |
| // Prune out everything that does not need to be pushed upwards and store the resulting state. |
| newState |
| .infos |
| .keySet() |
| .removeIf(method -> !state.virtualMethodsInSuperClasses.contains(method)); |
| if (!newState.isEmpty()) { |
| states.put(clazz, newState); |
| } |
| } |
| |
| private void handleInvokeInterfaceToSiblingMethod( |
| DexProgramClass iface, |
| DexProgramClass subClass, |
| DownwardsTraversalState state, |
| UpwardsTraversalState newState) { |
| assert iface.isInterface(); |
| assert !subClass.isInterface(); |
| |
| DexMethodSignatureSet interfaceMethodsInClassOrAbove = |
| DexMethodSignatureSet.create(state.virtualMethodsInSuperClasses); |
| interfaceMethodsInClassOrAbove.addAllMethods(iface.virtualMethods()); |
| |
| for (DexMethodSignature method : interfaceMethodsInClassOrAbove) { |
| MethodResolutionResult resolutionResult = |
| appView.appInfo().resolveMethodOnClass(subClass, method); |
| if (resolutionResult.isFailedResolution()) { |
| assert resolutionResult.asFailedResolution().hasMethodsCausingError(); |
| continue; |
| } |
| |
| if (resolutionResult.isMultiMethodResolutionResult()) { |
| // Conservatively drop the current optimization info. |
| newState.joinMethodOptimizationInfo( |
| appView, method, DefaultMethodOptimizationInfo.getInstance()); |
| continue; |
| } |
| |
| assert resolutionResult.isSingleResolution(); |
| DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair(); |
| if (!resolvedMethod.getHolder().isInterface() && resolvedMethod.getHolder() != subClass) { |
| newState.joinMethodOptimizationInfo( |
| appView, method, resolvedMethod.getOptimizationInfo()); |
| } |
| } |
| } |
| } |
| |
| private abstract static class TraversalState { |
| |
| DownwardsTraversalState asDownwardsTraversalState() { |
| return null; |
| } |
| |
| UpwardsTraversalState asUpwardsTraversalState() { |
| return null; |
| } |
| } |
| |
| // State that is used for each class during the downwards traversal over the class hierarchy. |
| private static class DownwardsTraversalState extends TraversalState { |
| |
| private static final DownwardsTraversalState EMPTY = |
| new DownwardsTraversalState(DexMethodSignatureSet.empty()); |
| |
| // The set of virtual methods in the super classes of the current class. For each method in this |
| // set we want the subsequent upwards traversal to include the optimization info for any |
| // overrides. |
| DexMethodSignatureSet virtualMethodsInSuperClasses; |
| |
| DownwardsTraversalState(DexMethodSignatureSet virtualMethodsInSuperClasses) { |
| this.virtualMethodsInSuperClasses = virtualMethodsInSuperClasses; |
| } |
| |
| static DownwardsTraversalState empty() { |
| return EMPTY; |
| } |
| |
| void add(DownwardsTraversalState state) { |
| virtualMethodsInSuperClasses.addAll(state.virtualMethodsInSuperClasses); |
| } |
| |
| boolean isEmpty() { |
| return virtualMethodsInSuperClasses.isEmpty(); |
| } |
| |
| @Override |
| DownwardsTraversalState asDownwardsTraversalState() { |
| return this; |
| } |
| } |
| |
| private static class UpwardsTraversalState extends TraversalState { |
| |
| private static final UpwardsTraversalState EMPTY = |
| new UpwardsTraversalState(DexMethodSignatureMap.empty()); |
| |
| private final DexMethodSignatureMap<MethodOptimizationInfo> infos; |
| |
| UpwardsTraversalState(DexMethodSignatureMap<MethodOptimizationInfo> infos) { |
| this.infos = infos; |
| } |
| |
| static UpwardsTraversalState empty() { |
| return EMPTY; |
| } |
| |
| void join(AppView<AppInfoWithLiveness> appView, UpwardsTraversalState state) { |
| state.infos.forEach((method, info) -> joinMethodOptimizationInfo(appView, method, info)); |
| } |
| |
| void joinMethodOptimizationInfo( |
| AppView<AppInfoWithLiveness> appView, |
| DexMethodSignature method, |
| MethodOptimizationInfo info) { |
| infos.compute( |
| method, |
| (m, existingInfo) -> existingInfo == null ? info : join(appView, m, existingInfo, info)); |
| } |
| |
| static MethodOptimizationInfo join( |
| AppView<AppInfoWithLiveness> appView, |
| DexMethodSignature method, |
| MethodOptimizationInfo info, |
| MethodOptimizationInfo otherInfo) { |
| if (info.isDefault() || otherInfo.isDefault()) { |
| return DefaultMethodOptimizationInfo.getInstance(); |
| } |
| |
| // Join dynamic return type. |
| DynamicType dynamicReturnType; |
| if (method.getReturnType().isVoidType()) { |
| dynamicReturnType = DynamicType.unknown(); |
| } else { |
| dynamicReturnType = info.getDynamicType().join(appView, otherInfo.getDynamicType()); |
| if (method.getReturnType().isClassType()) { |
| dynamicReturnType = |
| WideningUtils.widenDynamicNonReceiverType( |
| appView, dynamicReturnType, method.getReturnType()); |
| } else if (method.getReturnType().isArrayType()) { |
| // TODO: also widen array types. |
| } else { |
| assert method.getReturnType().isPrimitiveType(); |
| assert dynamicReturnType.isUnknown(); |
| } |
| } |
| |
| // Join abstract return value. |
| AbstractValue abstractReturnValue; |
| if (method.getReturnType().isVoidType()) { |
| abstractReturnValue = AbstractValue.unknown(); |
| } else { |
| TypeElement staticReturnType = method.getReturnType().toTypeElement(appView); |
| abstractReturnValue = |
| appView |
| .getAbstractValueConstantPropagationJoiner() |
| .join( |
| info.getAbstractReturnValue(), |
| otherInfo.getAbstractReturnValue(), |
| staticReturnType); |
| } |
| |
| // Join returned argument index. |
| int returnedArgument = |
| info.returnsArgument() && otherInfo.returnsArgument() |
| ? NumberUtils.getIfEqualsOrDefault( |
| info.getReturnedArgument(), otherInfo.getReturnedArgument(), -1) |
| : -1; |
| |
| // Join non-null-param-on-normal-exits information. |
| BitSet nonNullParamOnNormalExits = |
| BitSetUtils.intersectNullableBitSets( |
| info.getNonNullParamOnNormalExits(), otherInfo.getNonNullParamOnNormalExits()); |
| assert nonNullParamOnNormalExits == null || !nonNullParamOnNormalExits.isEmpty(); |
| |
| // Join non-null-param-or-throw information. |
| BitSet nonNullParamOrThrow = |
| BitSetUtils.intersectNullableBitSets( |
| info.getNonNullParamOrThrow(), otherInfo.getNonNullParamOrThrow()); |
| assert nonNullParamOrThrow == null || !nonNullParamOrThrow.isEmpty(); |
| |
| // Join flags. |
| boolean mayNotHaveSideEffects = !info.mayHaveSideEffects() && !otherInfo.mayHaveSideEffects(); |
| boolean neverReturnsNormally = |
| info.neverReturnsNormally() && otherInfo.neverReturnsNormally(); |
| boolean returnValueOnlyDependsOnArguments = |
| info.returnValueOnlyDependsOnArguments() && otherInfo.returnValueOnlyDependsOnArguments(); |
| |
| return MethodResolutionOptimizationInfo.builder() |
| .setAbstractReturnValue(abstractReturnValue) |
| .setDynamicReturnType(dynamicReturnType) |
| .setMayHaveSideEffects(!mayNotHaveSideEffects) |
| .setNeverReturnsNormally(neverReturnsNormally) |
| .setNonNullParamOnNormalExits(nonNullParamOnNormalExits) |
| .setNonNullParamOrThrow(nonNullParamOrThrow) |
| .setReturnedArgument(returnedArgument) |
| .setReturnValueOnlyDependsOnArguments(returnValueOnlyDependsOnArguments) |
| .build(); |
| } |
| |
| MethodOptimizationInfo getMethodOptimizationInfo(DexEncodedMethod method) { |
| MethodOptimizationInfo optimizationInfo = |
| infos.getOrDefault(method, DefaultMethodOptimizationInfo.getInstance()); |
| if (optimizationInfo.isMutableOptimizationInfo()) { |
| return MethodResolutionOptimizationInfo.create( |
| optimizationInfo.asMutableMethodOptimizationInfo()); |
| } |
| assert optimizationInfo.isDefault() || optimizationInfo.isMethodResolutionOptimizationInfo(); |
| return optimizationInfo; |
| } |
| |
| boolean isEmpty() { |
| return infos.isEmpty(); |
| } |
| |
| @Override |
| UpwardsTraversalState asUpwardsTraversalState() { |
| return this; |
| } |
| } |
| } |