blob: 2ed2ab525c1afb85f81ed443f00330a14b17e54e [file] [log] [blame]
// Copyright (c) 2017, 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.optimize.redundantbridgeremoval;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
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.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.optimize.InvokeSingleTargetExtractor;
import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal;
import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
public class RedundantBridgeRemover {
private final AppView<AppInfoWithLiveness> appView;
private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
private final RedundantBridgeRemovalOptions redundantBridgeRemovalOptions;
private final RedundantBridgeRemovalLens.Builder lensBuilder =
new RedundantBridgeRemovalLens.Builder();
public RedundantBridgeRemover(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
this.immediateSubtypingInfo = ImmediateProgramSubtypingInfo.create(appView);
this.redundantBridgeRemovalOptions = appView.options().getRedundantBridgeRemovalOptions();
}
private DexClassAndMethod getTargetForRedundantBridge(ProgramMethod method) {
DexEncodedMethod definition = method.getDefinition();
BridgeInfo bridgeInfo = definition.getOptimizationInfo().getBridgeInfo();
boolean isBridge = definition.isBridge() || bridgeInfo != null;
if (!isBridge || definition.isAbstract()) {
return null;
}
InvokeSingleTargetExtractor targetExtractor = new InvokeSingleTargetExtractor(appView, method);
method.registerCodeReferences(targetExtractor);
DexMethod target = targetExtractor.getTarget();
// javac-generated visibility forward bridge method has same descriptor (name, signature and
// return type).
if (target == null || !target.match(method.getReference())) {
return null;
}
if (!isTargetingSuperMethod(method, targetExtractor.getKind(), target)) {
return null;
}
// This is a visibility forward, so check for the direct target.
DexClassAndMethod targetMethod =
appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(target).getResolutionPair();
if (targetMethod == null) {
return null;
}
if (!targetMethod
.getDefinition()
.isAtLeastAsVisibleAsOtherInSameHierarchy(method.getDefinition(), appView)) {
return null;
}
if (definition.isStatic()
&& method.getHolder().hasClassInitializer()
&& method
.getHolder()
.classInitializationMayHaveSideEffectsInContext(appView, targetMethod)) {
return null;
}
return targetMethod;
}
private boolean isTargetingSuperMethod(ProgramMethod method, InvokeKind kind, DexMethod target) {
if (kind == InvokeKind.ILLEGAL) {
return false;
}
if (kind == InvokeKind.DIRECT) {
return method.getDefinition().isInstanceInitializer()
&& appView.options().canHaveNonReboundConstructorInvoke()
&& appView.appInfo().isStrictSubtypeOf(method.getHolderType(), target.getHolderType());
}
assert !method.getAccessFlags().isPrivate();
assert !method.getDefinition().isInstanceInitializer();
if (kind == InvokeKind.SUPER) {
return true;
}
if (kind == InvokeKind.STATIC) {
return appView.appInfo().isStrictSubtypeOf(method.getHolderType(), target.holder);
}
if (kind == InvokeKind.VIRTUAL) {
return false;
}
assert false : "Unexpected invoke-kind for visibility bridge: " + kind;
return false;
}
public void run(
MemberRebindingIdentityLens memberRebindingIdentityLens, ExecutorService executorService)
throws ExecutionException {
assert memberRebindingIdentityLens == null
|| memberRebindingIdentityLens == appView.graphLens();
// Collect all redundant bridges to remove.
ProgramMethodSet bridgesToRemove = removeRedundantBridgesConcurrently(executorService);
if (bridgesToRemove.isEmpty()) {
return;
}
pruneApp(bridgesToRemove, executorService);
if (!lensBuilder.isEmpty()) {
appView.setGraphLens(lensBuilder.build(appView));
}
if (memberRebindingIdentityLens != null) {
for (ProgramMethod bridgeToRemove : bridgesToRemove) {
DexClassAndMethod resolvedMethod =
appView
.appInfo()
.resolveMethodOn(bridgeToRemove.getHolder(), bridgeToRemove.getReference())
.getResolutionPair();
memberRebindingIdentityLens.addNonReboundMethodReference(
bridgeToRemove.getReference(), resolvedMethod.getReference());
}
}
}
private ProgramMethodSet removeRedundantBridgesConcurrently(ExecutorService executorService)
throws ExecutionException {
// Compute the strongly connected program components for parallelization.
List<Set<DexProgramClass>> stronglyConnectedProgramComponents =
new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
.computeStronglyConnectedComponents();
// Process the components concurrently.
Collection<ProgramMethodSet> results =
ThreadUtils.processItemsWithResultsThatMatches(
stronglyConnectedProgramComponents,
this::removeRedundantBridgesInComponent,
removedBridges -> !removedBridges.isEmpty(),
executorService);
ProgramMethodSet removedBridges = ProgramMethodSet.create();
results.forEach(
result -> {
removedBridges.addAll(result);
result.clear();
});
return removedBridges;
}
private ProgramMethodSet removeRedundantBridgesInComponent(
Set<DexProgramClass> stronglyConnectedProgramComponent) {
// Remove bridges in a top-down traversal of the class hierarchy. This ensures that we don't map
// an invoke to a removed bridge method to a method in the superclass hierarchy, which is then
// also removed by bridge removal.
RedundantBridgeRemoverClassHierarchyTraversal traversal =
new RedundantBridgeRemoverClassHierarchyTraversal();
traversal.run(stronglyConnectedProgramComponent);
return traversal.getRemovedBridges();
}
private boolean isRedundantAbstractBridge(ProgramMethod method) {
if (!method.getAccessFlags().isAbstract() || method.getDefinition().getCode() != null) {
return false;
}
DexProgramClass holder = method.getHolder();
if (holder.getSuperType() == null) {
assert holder.getType() == appView.dexItemFactory().objectType;
return false;
}
MethodResolutionResult superTypeResolution =
appView.appInfo().resolveMethodOn(holder.getSuperType(), method.getReference(), false);
if (superTypeResolution.isMultiMethodResolutionResult()) {
return false;
}
// Check if there is a definition in the super type hieararchy that is also abstract and has the
// same visibility.
if (superTypeResolution.isSingleResolution()) {
DexClassAndMethod resolutionPair =
superTypeResolution.asSingleResolution().getResolutionPair();
return resolutionPair.getDefinition().isAbstract()
&& resolutionPair
.getDefinition()
.isAtLeastAsVisibleAsOtherInSameHierarchy(method.getDefinition(), appView)
&& (!resolutionPair.getHolder().isInterface() || holder.getInterfaces().isEmpty());
}
// Only check for interfaces if resolving the method on super type causes NoSuchMethodError.
FailedResolutionResult failedResolutionResult = superTypeResolution.asFailedResolution();
if (failedResolutionResult == null
|| !failedResolutionResult.isNoSuchMethodErrorResult(holder, appView, appView.appInfo())
|| holder.getInterfaces().isEmpty()) {
return false;
}
for (DexType iface : holder.getInterfaces()) {
SingleResolutionResult<?> singleIfaceResult =
appView
.appInfo()
.resolveMethodOn(iface, method.getReference(), true)
.asSingleResolution();
if (singleIfaceResult == null
|| !singleIfaceResult.getResolvedMethod().isAbstract()
|| !singleIfaceResult
.getResolvedMethod()
.isAtLeastAsVisibleAsOtherInSameHierarchy(method.getDefinition(), appView)) {
return false;
}
}
return true;
}
private void pruneApp(ProgramMethodSet bridgesToRemove, ExecutorService executorService)
throws ExecutionException {
PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder().setPrunedApp(appView.app());
bridgesToRemove.forEach(method -> prunedItemsBuilder.addRemovedMethod(method.getReference()));
appView.pruneItems(prunedItemsBuilder.build(), executorService);
}
class RedundantBridgeRemoverClassHierarchyTraversal
extends DepthFirstTopDownClassHierarchyTraversal {
private final ProgramMethodSet removedBridges = ProgramMethodSet.create();
RedundantBridgeRemoverClassHierarchyTraversal() {
super(
RedundantBridgeRemover.this.appView, RedundantBridgeRemover.this.immediateSubtypingInfo);
}
public ProgramMethodSet getRemovedBridges() {
return removedBridges;
}
@Override
public void visit(DexProgramClass clazz) {
ProgramMethodSet bridgesToRemoveForClass = ProgramMethodSet.create();
clazz.forEachProgramMethod(
method -> {
KeepMethodInfo keepInfo = appView.getKeepInfo(method);
if (!keepInfo.isShrinkingAllowed(appView.options())
|| !keepInfo.isOptimizationAllowed(appView.options())) {
return;
}
if (isRedundantAbstractBridge(method)) {
// Record that the redundant bridge should be removed.
bridgesToRemoveForClass.add(method);
return;
}
DexClassAndMethod target = getTargetForRedundantBridge(method);
if (target != null) {
// Record that the redundant bridge should be removed.
bridgesToRemoveForClass.add(method);
// Rewrite invokes to the bridge to the target if it is accessible.
if (canRetargetInvokesToTargetMethod(method, target)) {
lensBuilder.map(method, target);
}
}
});
if (!bridgesToRemoveForClass.isEmpty()) {
clazz.getMethodCollection().removeMethods(bridgesToRemoveForClass.toDefinitionSet());
removedBridges.addAll(bridgesToRemoveForClass);
}
}
private boolean canRetargetInvokesToTargetMethod(
ProgramMethod method, DexClassAndMethod target) {
// Check if constructor retargeting is enabled.
if (method.getDefinition().isInstanceInitializer()
&& !redundantBridgeRemovalOptions.isRetargetingOfConstructorBridgeCallsEnabled()) {
return false;
}
// Check if all possible contexts that have access to the holder of the redundant bridge
// method also have access to the holder of the target method.
DexProgramClass methodHolder = method.getHolder();
DexClass targetHolder = target.getHolder();
if (!targetHolder.getAccessFlags().isPublic()) {
if (methodHolder.getAccessFlags().isPublic() || !method.isSamePackage(target)) {
return false;
}
}
// Check if all possible contexts that have access to the redundant bridge method also have
// access to the target method.
if (target.getAccessFlags().isPublic()) {
return true;
}
MethodAccessFlags methodAccessFlags = method.getAccessFlags();
MethodAccessFlags targetAccessFlags = target.getAccessFlags();
if (methodAccessFlags.isPackagePrivate()
&& !targetAccessFlags.isPrivate()
&& method.isSamePackage(target)) {
return true;
}
return methodAccessFlags.isProtected()
&& targetAccessFlags.isProtected()
&& method.isSamePackage(target);
}
@Override
public void prune(DexProgramClass clazz) {
// Empty.
}
}
}