blob: 0165f8e38a8683d07b479db3c240b06e911f29e4 [file] [log] [blame]
// Copyright (c) 2024, 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.argumentpropagation.codescanner;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.collections.DexMethodSignatureMap;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
/**
* Computes the set of virtual methods for which we can use a monomorphic method state as well as
* the mapping from virtual methods to their representative root methods.
*/
public class VirtualRootMethodsAnalysisBase extends DepthFirstTopDownClassHierarchyTraversal {
protected static class VirtualRootMethod {
private final VirtualRootMethod parent;
private final ProgramMethod method;
private Set<VirtualRootMethod> overrides = Collections.emptySet();
private List<VirtualRootMethod> siblings = Collections.emptyList();
private boolean mayDispatchOutsideProgram = false;
VirtualRootMethod(ProgramMethod root) {
this(root, null);
}
VirtualRootMethod(ProgramMethod method, VirtualRootMethod parent) {
assert method != null;
this.parent = parent;
this.method = method;
}
void addOverride(VirtualRootMethod override) {
assert !override.getMethod().isStructurallyEqualTo(method);
assert override.getMethod().getReference().match(method.getReference());
if (overrides.isEmpty()) {
overrides = Sets.newIdentityHashSet();
}
overrides.add(override);
if (hasParent()) {
getParent().addOverride(override);
}
}
void addSibling(VirtualRootMethod sibling) {
if (siblings.isEmpty()) {
siblings = new ArrayList<>(1);
}
siblings.add(sibling);
}
void setMayDispatchOutsideProgram() {
mayDispatchOutsideProgram = true;
}
boolean hasParent() {
return parent != null;
}
boolean hasSiblings() {
return !siblings.isEmpty();
}
VirtualRootMethod getParent() {
return parent;
}
VirtualRootMethod getRoot() {
return hasParent() ? getParent().getRoot() : this;
}
ProgramMethod getMethod() {
return method;
}
VirtualRootMethod getSingleDispatchTarget() {
assert !hasParent();
if (isMayDispatchOutsideProgramSet()) {
return null;
}
VirtualRootMethod singleDispatchTarget = isAbstract() ? null : this;
for (VirtualRootMethod override : overrides) {
if (!override.isAbstract()) {
if (singleDispatchTarget != null) {
// Not a single non-abstract method.
return null;
}
singleDispatchTarget = override;
}
}
assert singleDispatchTarget == null || !singleDispatchTarget.isAbstract();
return singleDispatchTarget;
}
void forEach(Consumer<VirtualRootMethod> consumer) {
consumer.accept(this);
overrides.forEach(consumer);
}
boolean hasOverrides() {
return !overrides.isEmpty();
}
boolean isAbstract() {
return method.getAccessFlags().isAbstract();
}
boolean isMayDispatchOutsideProgramSet() {
return mayDispatchOutsideProgram;
}
}
private final Map<DexProgramClass, DexMethodSignatureMap<VirtualRootMethod>>
virtualRootMethodsPerClass = new IdentityHashMap<>();
protected final ProgramMethodSet monomorphicVirtualRootMethods = ProgramMethodSet.create();
protected final ProgramMethodSet monomorphicVirtualNonRootMethods = ProgramMethodSet.create();
protected final Map<DexMethod, DexMethod> virtualRootMethods = new IdentityHashMap<>();
protected VirtualRootMethodsAnalysisBase(
AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
super(appView, immediateSubtypingInfo);
}
@Override
public void visit(DexProgramClass clazz) {
DexMethodSignatureMap<VirtualRootMethod> state = computeVirtualRootMethodsState(clazz);
virtualRootMethodsPerClass.put(clazz, state);
}
private DexMethodSignatureMap<VirtualRootMethod> computeVirtualRootMethodsState(
DexProgramClass clazz) {
DexMethodSignatureMap<VirtualRootMethod> virtualRootMethodsForClass =
DexMethodSignatureMap.create();
immediateSubtypingInfo.forEachImmediateProgramSuperClass(
clazz,
superclass -> {
DexMethodSignatureMap<VirtualRootMethod> virtualRootMethodsForSuperclass =
virtualRootMethodsPerClass.get(superclass);
virtualRootMethodsForSuperclass.forEach(
(signature, info) -> {
virtualRootMethodsForClass.compute(
signature,
(ignore, existing) -> {
if (existing == null || existing == info) {
return info;
} else {
// We iterate the immediate supertypes in-order using
// forEachImmediateProgramSuperClass. Therefore, the current method is
// guaranteed to be an interface method when existing != null.
assert info.getMethod().getHolder().isInterface();
if (!existing.getMethod().getHolder().isInterface()) {
existing.addSibling(info);
info.addOverride(existing);
}
return existing;
}
});
if (!clazz.isInterface() && superclass.isInterface()) {
DexClassAndMethod resolvedMethod =
appView.appInfo().resolveMethodOnClass(clazz, signature).getResolutionPair();
if (resolvedMethod != null
&& !resolvedMethod.isProgramMethod()
&& !resolvedMethod.getAccessFlags().isAbstract()) {
info.setMayDispatchOutsideProgram();
}
}
});
});
clazz.forEachProgramVirtualMethod(
method ->
virtualRootMethodsForClass.compute(
method,
(ignore, parent) -> {
if (parent == null) {
return new VirtualRootMethod(method);
} else {
VirtualRootMethod override = new VirtualRootMethod(method, parent);
parent.addOverride(override);
return override;
}
}));
return virtualRootMethodsForClass;
}
@Override
public void prune(DexProgramClass clazz) {
// Record the overrides for each virtual method that is rooted at this class.
DexMethodSignatureMap<VirtualRootMethod> virtualRootMethodsForClass =
virtualRootMethodsPerClass.remove(clazz);
clazz.forEachProgramVirtualMethod(
rootCandidate -> {
VirtualRootMethod virtualRootMethod =
virtualRootMethodsForClass.remove(rootCandidate.getMethodSignature());
acceptVirtualMethod(rootCandidate, virtualRootMethod);
if (virtualRootMethod.hasParent()
|| !rootCandidate.isStructurallyEqualTo(virtualRootMethod.getMethod())) {
return;
}
if (!virtualRootMethod.hasOverrides()
&& !virtualRootMethod.hasSiblings()
&& !virtualRootMethod.isMayDispatchOutsideProgramSet()) {
monomorphicVirtualRootMethods.add(rootCandidate);
return;
}
if (!rootCandidate.getHolder().isInterface()) {
VirtualRootMethod singleDispatchTarget = virtualRootMethod.getSingleDispatchTarget();
if (singleDispatchTarget != null) {
virtualRootMethod.forEach(
method -> setRootMethod(method, virtualRootMethod, singleDispatchTarget));
monomorphicVirtualNonRootMethods.add(singleDispatchTarget.getMethod());
return;
}
}
virtualRootMethod.forEach(
method -> setRootMethod(method, virtualRootMethod, virtualRootMethod));
});
}
private void setRootMethod(
VirtualRootMethod method, VirtualRootMethod currentRoot, VirtualRootMethod root) {
// Since the same method can have multiple roots due to interface methods, we only allow
// controlling the virtual root of methods that are rooted at the current root. Otherwise, we
// would be setting the virtual root of the same method multiple times, which could lead to
// non-determinism in the result (i.e., the `virtualRootMethods` map).
if (method.getRoot() == currentRoot) {
DexMethod rootReference = root.getMethod().getReference();
DexMethod previous = virtualRootMethods.put(method.getMethod().getReference(), rootReference);
assert previous == null || previous.isIdenticalTo(rootReference);
}
}
protected void acceptVirtualMethod(ProgramMethod method, VirtualRootMethod virtualRootMethod) {
// Intentionally empty.
}
}