blob: 338634948521806fbcb8ec9f4f9f14fae3bfd7c5 [file] [log] [blame]
// Copyright (c) 2019, 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 static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import com.android.tools.r8.graph.DexCallSite;
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.DexField;
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.FieldAccessInfo;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.function.Predicate;
class InvokeExtractor extends UseRegistry<ProgramMethod> {
private final CallGraphBuilderBase callGraphBuilderBase;
private final Node currentMethod;
private final Predicate<ProgramMethod> targetTester;
InvokeExtractor(
CallGraphBuilderBase callGraphBuilderBase,
Node currentMethod,
Predicate<ProgramMethod> targetTester) {
super(callGraphBuilderBase.appView, currentMethod.getProgramMethod());
this.callGraphBuilderBase = callGraphBuilderBase;
this.currentMethod = currentMethod;
this.targetTester = targetTester;
}
private void addClassInitializerTarget(DexProgramClass clazz) {
assert clazz != null;
if (clazz.hasClassInitializer()) {
addCallEdge(clazz.getProgramClassInitializer(), false);
}
}
private void addClassInitializerTarget(DexType type) {
assert type.isClassType();
DexProgramClass clazz = asProgramClassOrNull(callGraphBuilderBase.appView.definitionFor(type));
if (clazz != null) {
addClassInitializerTarget(clazz);
}
}
private void addCallEdge(ProgramMethod callee, boolean likelySpuriousCallEdge) {
if (!targetTester.test(callee)) {
return;
}
if (callee.getDefinition().isAbstract()) {
// Not a valid target.
return;
}
if (callee.getDefinition().isNative()) {
// We don't care about calls to native methods.
return;
}
if (!callGraphBuilderBase
.appView
.getKeepInfo(callee)
.isInliningAllowed(callGraphBuilderBase.appView.options())) {
// Since the callee is kept and optimizations are disallowed, we cannot inline it into the
// caller, and we also cannot collect any optimization info for the method. Therefore, we
// drop the call edge to reduce the total number of call graph edges, which should lead to
// fewer call graph cycles.
return;
}
callGraphBuilderBase
.getOrCreateNode(callee)
.addCallerConcurrently(currentMethod, likelySpuriousCallEdge);
}
private void addFieldReadEdge(ProgramMethod writer) {
assert !writer.getDefinition().isAbstract();
if (!targetTester.test(writer)) {
return;
}
callGraphBuilderBase.getOrCreateNode(writer).addReaderConcurrently(currentMethod);
}
private void processInvoke(Invoke.Type originalType, DexMethod originalMethod) {
ProgramMethod context = currentMethod.getProgramMethod();
MethodLookupResult result =
callGraphBuilderBase
.appView
.graphLens()
.lookupMethod(
originalMethod,
context.getReference(),
originalType,
callGraphBuilderBase.codeLens);
DexMethod method = result.getReference();
Invoke.Type type = result.getType();
if (type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL) {
// For virtual and interface calls add all potential targets that could be called.
MethodResolutionResult resolutionResult =
callGraphBuilderBase
.appView
.appInfo()
.resolveMethod(method, type == Invoke.Type.INTERFACE);
DexEncodedMethod target = resolutionResult.getSingleTarget();
if (target != null) {
processInvokeWithDynamicDispatch(type, target, context);
}
} else {
ProgramMethod singleTarget =
callGraphBuilderBase
.appView
.appInfo()
.lookupSingleProgramTarget(type, method, context, callGraphBuilderBase.appView);
if (singleTarget != null) {
assert !context.getDefinition().isBridge()
|| singleTarget.getDefinition() != context.getDefinition();
// For static invokes, the class could be initialized.
if (type.isStatic()) {
addClassInitializerTarget(singleTarget.getHolder());
}
addCallEdge(singleTarget, false);
}
}
}
private void processInvokeWithDynamicDispatch(
Invoke.Type type, DexEncodedMethod encodedTarget, ProgramMethod context) {
DexMethod target = encodedTarget.getReference();
DexClass clazz = callGraphBuilderBase.appView.definitionFor(target.holder);
if (clazz == null) {
assert false : "Unable to lookup holder of `" + target.toSourceString() + "`";
return;
}
if (!callGraphBuilderBase.appView.options().testing.addCallEdgesForLibraryInvokes) {
if (clazz.isLibraryClass()) {
// Likely to have many possible targets.
return;
}
}
boolean isInterface = type == Invoke.Type.INTERFACE;
ProgramMethodSet possibleProgramTargets =
callGraphBuilderBase.possibleProgramTargetsCache.computeIfAbsent(
target,
method -> {
MethodResolutionResult resolution =
callGraphBuilderBase.appView.appInfo().resolveMethod(method, isInterface);
if (resolution.isVirtualTarget()) {
LookupResult lookupResult =
resolution.lookupVirtualDispatchTargets(
context.getHolder(), callGraphBuilderBase.appView.appInfo());
if (lookupResult.isLookupResultSuccess()) {
ProgramMethodSet targets = ProgramMethodSet.create();
lookupResult
.asLookupResultSuccess()
.forEach(
methodTarget -> {
if (methodTarget.isProgramMethod()) {
targets.add(methodTarget.asProgramMethod());
}
},
lambdaTarget -> {
// The call target will ultimately be the implementation method.
DexClassAndMethod implementationMethod =
lambdaTarget.getImplementationMethod();
if (implementationMethod.isProgramMethod()) {
targets.add(implementationMethod.asProgramMethod());
}
});
return targets;
}
}
return null;
});
if (possibleProgramTargets != null) {
boolean likelySpuriousCallEdge =
possibleProgramTargets.size()
>= callGraphBuilderBase.appView.options().callGraphLikelySpuriousCallEdgeThreshold;
for (ProgramMethod possibleTarget : possibleProgramTargets) {
addCallEdge(possibleTarget, likelySpuriousCallEdge);
}
}
}
private void processFieldRead(DexField reference) {
if (!callGraphBuilderBase.includeFieldReadWriteEdges) {
return;
}
DexField rewrittenReference =
callGraphBuilderBase
.appView
.graphLens()
.lookupField(reference, callGraphBuilderBase.codeLens);
if (!rewrittenReference.getHolderType().isClassType()) {
return;
}
ProgramField field =
callGraphBuilderBase.appView.appInfo().resolveField(rewrittenReference).getProgramField();
if (field == null || callGraphBuilderBase.appView.appInfo().isPinned(field)) {
return;
}
// Each static field access implicitly triggers the class initializer.
if (field.getAccessFlags().isStatic()) {
addClassInitializerTarget(field.getHolder());
}
FieldAccessInfo fieldAccessInfo =
callGraphBuilderBase.fieldAccessInfoCollection.get(field.getReference());
if (fieldAccessInfo != null && fieldAccessInfo.hasKnownWriteContexts()) {
if (fieldAccessInfo.getNumberOfWriteContexts() == 1) {
fieldAccessInfo.forEachWriteContext(this::addFieldReadEdge);
}
}
}
private void processFieldWrite(DexField reference) {
if (!callGraphBuilderBase.includeFieldReadWriteEdges) {
return;
}
DexField rewrittenReference =
callGraphBuilderBase
.appView
.graphLens()
.lookupField(reference, callGraphBuilderBase.codeLens);
if (!rewrittenReference.getHolderType().isClassType()) {
return;
}
ProgramField field =
callGraphBuilderBase.appView.appInfo().resolveField(rewrittenReference).getProgramField();
if (field == null || callGraphBuilderBase.appView.appInfo().isPinned(field)) {
return;
}
// Each static field access implicitly triggers the class initializer.
if (field.getAccessFlags().isStatic()) {
addClassInitializerTarget(field.getHolder());
}
}
private void processInitClass(DexType type) {
DexType rewrittenType = callGraphBuilderBase.appView.graphLens().lookupType(type);
DexProgramClass clazz =
asProgramClassOrNull(callGraphBuilderBase.appView.definitionFor(rewrittenType));
if (clazz == null) {
assert false;
return;
}
addClassInitializerTarget(clazz);
}
@Override
public GraphLens getCodeLens() {
return callGraphBuilderBase.codeLens;
}
@Override
public void registerInitClass(DexType clazz) {
processInitClass(clazz);
}
@Override
public void registerInvokeVirtual(DexMethod method) {
processInvoke(Invoke.Type.VIRTUAL, method);
}
@Override
public void registerInvokeDirect(DexMethod method) {
processInvoke(Invoke.Type.DIRECT, method);
}
@Override
public void registerInvokeStatic(DexMethod method) {
processInvoke(Invoke.Type.STATIC, method);
}
@Override
public void registerInvokeInterface(DexMethod method) {
processInvoke(Invoke.Type.INTERFACE, method);
}
@Override
public void registerInvokeSuper(DexMethod method) {
processInvoke(Invoke.Type.SUPER, method);
}
@Override
public void registerInstanceFieldRead(DexField field) {
processFieldRead(field);
}
@Override
public void registerInstanceFieldWrite(DexField field) {
processFieldWrite(field);
}
@Override
public void registerNewInstance(DexType type) {
if (type.isClassType()) {
addClassInitializerTarget(type);
}
}
@Override
public void registerStaticFieldRead(DexField field) {
processFieldRead(field);
}
@Override
public void registerStaticFieldWrite(DexField field) {
processFieldWrite(field);
}
@Override
public void registerTypeReference(DexType type) {}
@Override
public void registerInstanceOf(DexType type) {}
@Override
public void registerCallSite(DexCallSite callSite) {
registerMethodHandle(
callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
}
}