blob: 0e35bf344a93fddd0e963d6fd3cf45c2e7953b40 [file] [log] [blame]
// Copyright (c) 2021, 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.policies.deadlock;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
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.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.collections.ProgramMethodMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
/**
* Stores the single caller (if any) for each non-virtual method. Virtual methods are not considered
* since computing single caller information for such methods is expensive (it involves computing
* the possible dispatch targets for each virtual invoke).
*
* <p>Unlike the {@link com.android.tools.r8.ir.conversion.CallGraph} that is used to determine if a
* method can be single caller inlined, this considers a method that is called from multiple call
* sites in the same method to have a single caller.
*/
// TODO(b/205611444): account for -keep rules.
public class SingleCallerInformation {
private final ProgramMethodMap<ProgramMethod> singleCallers;
private final Map<DexProgramClass, ProgramMethod> singleClinitCallers;
SingleCallerInformation(
ProgramMethodMap<ProgramMethod> singleCallers,
Map<DexProgramClass, ProgramMethod> singleClinitCallers) {
this.singleCallers = singleCallers;
this.singleClinitCallers = singleClinitCallers;
}
public static Builder builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
return new Builder(appView);
}
public ProgramMethod getSingleCaller(ProgramMethod method) {
return singleCallers.get(method);
}
public ProgramMethod getSingleClassInitializerCaller(DexProgramClass clazz) {
return singleClinitCallers.get(clazz);
}
public static class Builder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
// The single callers for each method and class initializer.
// If a method is not in the map, then a call to that method has never been seen.
// If a method is mapped to Optional.empty(), then the method has multiple calling contexts.
// If a method is mapped to Optional.of(m), then the method is only called from method m.
final ProgramMethodMap<Optional<ProgramMethod>> callers = ProgramMethodMap.createConcurrent();
final Map<DexProgramClass, Optional<ProgramMethod>> clinitCallers = new ConcurrentHashMap<>();
Builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
}
public Builder analyze(ExecutorService executorService) throws ExecutionException {
ThreadUtils.processItems(
appView.appInfo()::forEachMethod, this::processMethod, executorService);
return this;
}
public SingleCallerInformation build() {
ProgramMethodMap<ProgramMethod> singleCallers = ProgramMethodMap.create();
callers.forEach(
(method, callers) -> callers.ifPresent(caller -> singleCallers.put(method, caller)));
Map<DexProgramClass, ProgramMethod> singleClinitCallers = new IdentityHashMap<>();
clinitCallers.forEach(
(clazz, callers) -> callers.ifPresent(caller -> singleClinitCallers.put(clazz, caller)));
return new SingleCallerInformation(singleCallers, singleClinitCallers);
}
private void processMethod(ProgramMethod method) {
method.registerCodeReferences(new InvokeExtractor(appView, method));
}
private class InvokeExtractor extends UseRegistry<ProgramMethod> {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
InvokeExtractor(AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
super(appView, context);
this.appView = appView;
}
private void recordDispatchTarget(ProgramMethod target) {
callers.compute(
target,
(key, value) -> {
if (value == null) {
// This target is now called from the current context (only).
return Optional.of(getContext());
}
// If the target is only called from the current context, then that is still the
// case.
if (value.orElse(null) == getContext()) {
return value;
}
// The target is now called from more than one place.
return Optional.empty();
});
}
private void triggerClassInitializerIfNotAlreadyTriggeredInContext(DexType type) {
DexProgramClass clazz = type.asProgramClass(appView);
if (clazz != null) {
triggerClassInitializerIfNotAlreadyTriggeredInContext(clazz);
}
}
private void triggerClassInitializerIfNotAlreadyTriggeredInContext(DexProgramClass clazz) {
if (!isClassAlreadyInitializedInCurrentContext(clazz)) {
triggerClassInitializer(clazz);
}
}
private boolean isClassAlreadyInitializedInCurrentContext(DexProgramClass clazz) {
return appView.appInfo().isSubtype(getContext().getHolder(), clazz);
}
private void triggerClassInitializer(DexType type) {
DexProgramClass clazz = type.asProgramClass(appView);
if (clazz != null) {
triggerClassInitializer(clazz);
}
}
private void triggerClassInitializer(DexProgramClass clazz) {
Optional<ProgramMethod> callers = clinitCallers.get(clazz);
if (callers != null) {
if (!callers.isPresent()) {
// Optional.empty() represents that this class initializer has multiple (unknown)
// callers. Since this <clinit> and all of the parent <clinit>s are already triggered
// from multiple places, there is no need to record it is also triggered from the
// current context.
return;
}
if (callers.get() == getContext()) {
// This <clinit> is already triggered from the current context. No need to record this
// again.
return;
}
}
// Record that the given class is now initialized from the current context.
clinitCallers.compute(
clazz,
(key, value) -> {
if (value == null) {
// This <clinit> was not triggered before.
return Optional.of(getContext());
}
// This <clinit> was triggered from another context than the current.
assert value.orElse(null) != getContext();
return Optional.empty();
});
// Repeat for the parent classes.
triggerClassInitializer(clazz.getSuperType());
}
@Override
public void registerInitClass(DexType type) {
DexType rewrittenType = appView.graphLens().lookupType(type);
triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenType);
}
@Override
public void registerInstanceFieldRead(DexField field) {
// Intentionally empty.
}
@Override
public void registerInstanceFieldWrite(DexField field) {
// Intentionally empty.
}
@Override
public void registerInvokeDirect(DexMethod method) {
DexMethod rewrittenMethod =
appView.graphLens().lookupInvokeDirect(method, getContext()).getReference();
DexProgramClass holder = rewrittenMethod.getHolderType().asProgramClass(appView);
ProgramMethod target = rewrittenMethod.lookupOnProgramClass(holder);
if (target != null) {
recordDispatchTarget(target);
}
}
@Override
public void registerInvokeInterface(DexMethod method) {
// Intentionally empty, as we don't aim to collect single caller information for virtual
// methods.
}
@Override
public void registerInvokeStatic(DexMethod method) {
DexMethod rewrittenMethod =
appView.graphLens().lookupInvokeDirect(method, getContext()).getReference();
ProgramMethod target =
appView
.appInfo()
.unsafeResolveMethodDueToDexFormat(rewrittenMethod)
.getResolvedProgramMethod();
if (target != null) {
recordDispatchTarget(target);
triggerClassInitializerIfNotAlreadyTriggeredInContext(target.getHolder());
}
}
@Override
public void registerInvokeSuper(DexMethod method) {
// Intentionally empty, as we don't aim to collect single caller information for virtual
// methods.
}
@Override
public void registerInvokeVirtual(DexMethod method) {
// Intentionally empty, as we don't aim to collect single caller information for virtual
// methods.
}
@Override
public void registerNewInstance(DexType type) {
DexType rewrittenType = appView.graphLens().lookupType(type);
triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenType);
}
@Override
public void registerStaticFieldRead(DexField field) {
DexField rewrittenField = appView.graphLens().lookupField(field);
triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenField.getHolderType());
}
@Override
public void registerStaticFieldWrite(DexField field) {
DexField rewrittenField = appView.graphLens().lookupField(field);
triggerClassInitializerIfNotAlreadyTriggeredInContext(rewrittenField.getHolderType());
}
@Override
public void registerTypeReference(DexType type) {
// Intentionally empty.
}
}
}
}