blob: 5462dd174be8f4aefd3df2bb4609a9b6ba0d7bb2 [file] [log] [blame]
// Copyright (c) 2023, 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.graph.fixup;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClasspathOrLibraryClass;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodSignature;
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.ProgramMethod;
import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
import com.android.tools.r8.optimize.utils.ConcurrentNonProgramMethodsCollection;
import com.android.tools.r8.optimize.utils.NonProgramMethodsCollection;
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.Timing;
import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
public class ConcurrentMethodFixup {
private final AppView<AppInfoWithLiveness> appView;
private final NonProgramMethodsCollection nonProgramVirtualMethods;
private final ProgramClassFixer programClassFixer;
public ConcurrentMethodFixup(
AppView<AppInfoWithLiveness> appView, ProgramClassFixer programClassFixer) {
this.appView = appView;
this.nonProgramVirtualMethods =
ConcurrentNonProgramMethodsCollection.createVirtualMethodsCollection(appView);
this.programClassFixer = programClassFixer;
}
public void fixupClassesConcurrentlyByConnectedProgramComponents(
Timing timing, ExecutorService executorService) throws ExecutionException {
timing.begin("Concurrent method fixup");
timing.begin("Compute strongly connected components");
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.create(appView);
List<Set<DexProgramClass>> connectedComponents =
new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
.computeStronglyConnectedComponents();
timing.end();
timing.begin("Process strongly connected components");
ThreadUtils.processItems(
connectedComponents,
this::processConnectedProgramComponents,
appView.options().getThreadingModule(),
executorService);
timing.end();
timing.end();
}
public interface ProgramClassFixer {
// When a class is fixed-up, it is guaranteed that its supertype and interfaces were processed
// before. In addition, all interfaces are processed before any class is processed.
void fixupProgramClass(DexProgramClass clazz, MethodNamingUtility namingUtility);
// Answers true if the method should be reserved as itself.
boolean shouldReserveAsIfPinned(ProgramMethod method);
}
private void processConnectedProgramComponents(Set<DexProgramClass> classes) {
List<DexProgramClass> sorted = new ArrayList<>(classes);
sorted.sort(Comparator.comparing(DexClass::getType));
DexMethodSignatureBiMap<DexMethodSignature> componentSignatures =
new DexMethodSignatureBiMap<>();
// 1) Reserve all library overrides and pinned virtual methods.
reserveComponentPinnedAndInterfaceMethodSignatures(sorted, componentSignatures);
// 2) Map all interfaces top-down updating the componentSignatures.
Set<DexProgramClass> processedInterfaces = Sets.newIdentityHashSet();
for (DexProgramClass clazz : sorted) {
if (clazz.isInterface()) {
processInterface(clazz, processedInterfaces, componentSignatures);
}
}
// 3) Map all classes top-down propagating the inherited signatures.
// The componentSignatures are already fully computed and should not be updated anymore.
// TODO(b/279707790): Consider changing the processing to have a different componentSignatures
// per subtree.
Set<DexProgramClass> processedClasses = Sets.newIdentityHashSet();
for (DexProgramClass clazz : sorted) {
if (!clazz.isInterface()) {
processClass(clazz, processedClasses, componentSignatures);
}
}
}
private void processClass(
DexProgramClass clazz,
Set<DexProgramClass> processedClasses,
DexMethodSignatureBiMap<DexMethodSignature> componentSignatures) {
assert !clazz.isInterface();
if (!processedClasses.add(clazz)) {
return;
}
// We need to process first the super-type for the top-down propagation of inherited signatures.
DexProgramClass superClass = asProgramClassOrNull(appView.definitionFor(clazz.superType));
if (superClass != null) {
processClass(superClass, processedClasses, componentSignatures);
}
MethodNamingUtility utility = createMethodNamingUtility(componentSignatures, clazz);
programClassFixer.fixupProgramClass(clazz, utility);
}
private void processInterface(
DexProgramClass clazz,
Set<DexProgramClass> processedInterfaces,
DexMethodSignatureBiMap<DexMethodSignature> componentSignatures) {
assert clazz.isInterface();
if (!processedInterfaces.add(clazz)) {
return;
}
// We need to process first all super-interfaces to avoid generating collisions by renaming
// private or static methods into inherited virtual method signatures.
for (DexType superInterface : clazz.getInterfaces()) {
DexProgramClass superInterfaceClass =
asProgramClassOrNull(appView.definitionFor(superInterface));
if (superInterfaceClass != null) {
processInterface(superInterfaceClass, processedInterfaces, componentSignatures);
}
}
MethodNamingUtility utility = createMethodNamingUtility(componentSignatures, clazz);
programClassFixer.fixupProgramClass(clazz, utility);
}
private boolean shouldReserveAsPinned(ProgramMethod method) {
KeepMethodInfo keepInfo = appView.getKeepInfo(method);
return !keepInfo.isOptimizationAllowed(appView.options())
|| !keepInfo.isShrinkingAllowed(appView.options())
|| programClassFixer.shouldReserveAsIfPinned(method);
}
private MethodNamingUtility createMethodNamingUtility(
DexMethodSignatureBiMap<DexMethodSignature> componentSignatures, DexProgramClass clazz) {
BiMap<DexMethod, DexMethod> localSignatures = HashBiMap.create();
clazz.forEachProgramInstanceInitializer(
method -> {
if (shouldReserveAsPinned(method)) {
localSignatures.put(method.getReference(), method.getReference());
}
});
return new MethodNamingUtility(appView.dexItemFactory(), componentSignatures, localSignatures);
}
private void reserveComponentPinnedAndInterfaceMethodSignatures(
List<DexProgramClass> stronglyConnectedProgramClasses,
DexMethodSignatureBiMap<DexMethodSignature> componentSignatures) {
Set<ClasspathOrLibraryClass> seenNonProgramClasses = Sets.newIdentityHashSet();
for (DexProgramClass clazz : stronglyConnectedProgramClasses) {
// If a private or static method is pinned, we need to reserve the mapping to avoid creating
// a collision with a changed virtual method.
clazz.forEachProgramMethodMatching(
m -> !m.isInstanceInitializer(),
method -> {
if (shouldReserveAsPinned(method)) {
componentSignatures.put(method.getMethodSignature(), method.getMethodSignature());
}
});
clazz.forEachImmediateSuperClassMatching(
appView,
(supertype, superclass) ->
superclass != null
&& !superclass.isProgramClass()
&& seenNonProgramClasses.add(superclass.asClasspathOrLibraryClass()),
(supertype, superclass) ->
componentSignatures.putAllToIdentity(
nonProgramVirtualMethods.getOrComputeNonProgramMethods(
superclass.asClasspathOrLibraryClass())));
}
}
}