blob: 84685ae1c12e169e6dfab48705be2cdb80a6f448 [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.ir.conversion.callgraph;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.MethodProcessorWithWave;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.classhierarchy.MethodOverridesCollector;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public abstract class CallSiteInformation {
/**
* Check if the <code>method</code> is guaranteed to only have a single call site.
*
* <p>For pinned methods (methods kept through Proguard keep rules) this will always answer <code>
* false</code>.
*/
public abstract boolean hasSingleCallSite(ProgramMethod method, ProgramMethod context);
/**
* Checks if the given method only has a single call without considering context.
*
* <p>For pinned methods (methods kept through Proguard keep rules) and methods that override a
* library method this always returns false.
*/
public abstract boolean hasSingleCallSite(ProgramMethod method);
public abstract boolean isMultiCallerInlineCandidate(ProgramMethod method);
public abstract void notifyMethodInlined(ProgramMethod caller, ProgramMethod callee);
public abstract void unsetCallSiteInformation(ProgramMethod method);
public static CallSiteInformation empty() {
return EmptyCallSiteInformation.EMPTY_INFO;
}
private static class EmptyCallSiteInformation extends CallSiteInformation {
private static final EmptyCallSiteInformation EMPTY_INFO = new EmptyCallSiteInformation();
@Override
public boolean hasSingleCallSite(ProgramMethod method, ProgramMethod context) {
return false;
}
@Override
public boolean hasSingleCallSite(ProgramMethod method) {
return false;
}
@Override
public boolean isMultiCallerInlineCandidate(ProgramMethod method) {
return false;
}
@Override
public void notifyMethodInlined(ProgramMethod caller, ProgramMethod callee) {}
@Override
public void unsetCallSiteInformation(ProgramMethod method) {
// Intentionally empty.
}
}
static class CallGraphBasedCallSiteInformation extends CallSiteInformation {
private final MethodProcessorWithWave methodProcessor;
// Single callers track their calling context to ensure that the predicate is stable after
// inlining of the caller.
private final MutableBidirectionalManyToOneMap<DexMethod, DexMethod> singleCallerMethods =
BidirectionalManyToOneHashMap.newIdentityHashMap();
private final Set<DexMethod> multiCallerInlineCandidates = Sets.newIdentityHashSet();
private final Set<DexMethod> noSingleCallerInliningInto = ConcurrentHashMap.newKeySet();
CallGraphBasedCallSiteInformation(
AppView<AppInfoWithLiveness> appView,
CallGraph graph,
MethodProcessorWithWave methodProcessor) {
this.methodProcessor = methodProcessor;
InternalOptions options = appView.options();
ProgramMethodSet pinned =
MethodOverridesCollector.findAllMethodsAndOverridesThatMatches(
appView,
ImmediateProgramSubtypingInfo.create(appView),
appView.appInfo().classes(),
method -> {
KeepMethodInfo keepInfo = appView.getKeepInfo(method);
return !keepInfo.isClosedWorldReasoningAllowed(options)
|| keepInfo.isPinned(options);
});
for (Node node : graph.getNodes()) {
ProgramMethod method = node.getProgramMethod();
DexMethod reference = method.getReference();
// For non-pinned methods and methods that override library methods we do not know the exact
// number of call sites.
if (pinned.contains(method)) {
continue;
}
if (appView.options().inlinerOptions().disableInliningOfLibraryMethodOverrides
&& method.getDefinition().isLibraryMethodOverride().isTrue()) {
continue;
}
if (method.getDefinition().isDefaultInstanceInitializer()
&& appView.hasProguardCompatibilityActions()
&& appView.getProguardCompatibilityActions().isCompatInstantiated(method.getHolder())) {
continue;
}
int numberOfCallSites = node.getNumberOfCallSites();
if (numberOfCallSites == 1) {
if (!appView.getKeepInfo(method).isSingleCallerInliningAllowed(options)) {
continue;
}
if (methodProcessor.isPostMethodProcessor()) {
SyntheticItems syntheticItems = appView.getSyntheticItems();
if (syntheticItems.hasKindThatMatches(
method.getHolderType(),
(kind, naming) -> !kind.isSingleCallerInlineableInPostMethodProcessor(naming))) {
continue;
}
}
Set<Node> callersWithDeterministicOrder = node.getCallersWithDeterministicOrder();
DexMethod caller = reference;
// We can have recursive methods where the recursive call is the only call site. We do
// not track callers for these.
if (!callersWithDeterministicOrder.isEmpty()) {
assert callersWithDeterministicOrder.size() == 1;
caller = callersWithDeterministicOrder.iterator().next().getMethod().getReference();
}
assert !singleCallerMethods.containsKey(reference);
singleCallerMethods.put(reference, caller);
} else if (numberOfCallSites > 1 && methodProcessor.isPrimaryMethodProcessor()) {
multiCallerInlineCandidates.add(reference);
}
}
}
/**
* Checks if the given method only has a single call site with the given context.
*
* <p>For pinned methods (methods kept through Proguard keep rules) and methods that override a
* library method this always returns false.
*/
@Override
public boolean hasSingleCallSite(ProgramMethod method, ProgramMethod context) {
return !noSingleCallerInliningInto.contains(context.getReference())
&& context.getReference().isIdenticalTo(singleCallerMethods.get(method.getReference()));
}
/**
* Checks if the given method only has a single call without considering context.
*
* <p>For pinned methods (methods kept through Proguard keep rules) and methods that override a
* library method this always returns false.
*/
@Override
public boolean hasSingleCallSite(ProgramMethod method) {
return singleCallerMethods.containsKey(method.getReference());
}
/**
* Checks if the given method only has two call sites.
*
* <p>For pinned methods (methods kept through Proguard keep rules) and methods that override a
* library method this always returns false.
*/
@Override
public boolean isMultiCallerInlineCandidate(ProgramMethod method) {
return multiCallerInlineCandidates.contains(method.getReference());
}
@Override
public void notifyMethodInlined(ProgramMethod caller, ProgramMethod callee) {
if (methodProcessor.isPrimaryMethodProcessor()) {
return;
}
assert methodProcessor.isPostMethodProcessor();
// If the callee is the single caller of another method, then disallow that single caller
// inlining, since we have now duplicated the call site as a result of this inlining.
if (singleCallerMethods.containsValue(callee.getReference())) {
noSingleCallerInliningInto.add(callee.getReference());
}
}
@Override
public void unsetCallSiteInformation(ProgramMethod method) {
singleCallerMethods.remove(method.getReference());
multiCallerInlineCandidates.remove(method.getReference());
}
}
}