blob: 29ed27332686accdeff4f44113a82c02a5eb8c75 [file] [log] [blame]
// Copyright (c) 2022, 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.proto;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
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.DexString;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
public class ProtoNormalizer {
private final AppView<AppInfoWithLiveness> appView;
private final DexItemFactory dexItemFactory;
private final InternalOptions options;
public ProtoNormalizer(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
this.options = appView.options();
}
public void run(ExecutorService executorService, Timing timing) throws ExecutionException {
if (options.testing.enableExperimentalProtoNormalization) {
timing.time("Proto normalization", () -> run(executorService));
}
}
private void run(ExecutorService executorService) throws ExecutionException {
GlobalReservationState globalReservationState = computeGlobalReservationState(executorService);
// TODO(b/173398086): This uses a single LocalReservationState for the entire program. This
// should process the strongly connected program components in parallel, each with their own
// LocalReservationState.
LocalReservationState localReservationState = new LocalReservationState();
ProtoNormalizerGraphLens.Builder lensBuilder = ProtoNormalizerGraphLens.builder(appView);
for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
clazz
.getMethodCollection()
.replaceMethods(
method -> {
DexMethodSignature methodSignature = method.getSignature();
DexMethodSignature newMethodSignature =
localReservationState.getNewMethodSignature(
methodSignature, dexItemFactory, globalReservationState);
if (methodSignature.equals(newMethodSignature)) {
return method;
}
DexMethod newMethodReference = newMethodSignature.withHolder(clazz, dexItemFactory);
lensBuilder.recordNewMethodSignature(method, newMethodReference);
// TODO(b/195112263): Fixup any optimization info and parameter annotations.
return method.toTypeSubstitutedMethod(newMethodReference);
});
}
if (!lensBuilder.isEmpty()) {
appView.rewriteWithLens(lensBuilder.build());
}
}
private GlobalReservationState computeGlobalReservationState(ExecutorService executorService)
throws ExecutionException {
// Tracks how many different parameter lists can be optimized into the same parameter list.
// If only (B, A) can be rewritten into (A, B), then there is no need to rewrite parameter lists
// on the form (B, A) into (A, B), as that won't lead to any sharing of parameter lists.
Map<DexTypeList, Set<DexTypeList>> optimizableParameterLists = new ConcurrentHashMap<>();
// Used to track if a given parameter list should be mapped to a specific permutation instead of
// just sorting the parameter list. This is used to ensure that we will rewrite parameter lists
// such as (A, B) into (B, A) if there is an unoptimizable method with parameter list (B, A).
Map<DexTypeList, Set<DexTypeList>> reservedParameterLists = new ConcurrentHashMap<>();
// Tracks the set of unoptimizable method signatures. These must remain as-is.
DexMethodSignatureSet unoptimizableSignatures = DexMethodSignatureSet.createConcurrent();
ThreadUtils.processMethods(
appView,
method ->
computeReservationsFromMethod(
method, optimizableParameterLists, reservedParameterLists, unoptimizableSignatures),
executorService);
// Reserve parameter lists that won't lead to any sharing after normalization. Any method with
// such a parameter list must remain as-is.
Set<DexTypeList> unoptimizableParameterLists = new HashSet<>();
optimizableParameterLists.forEach(
(sortedParameters, parameterListsBeforeNormalization) -> {
int size = parameterListsBeforeNormalization.size();
if (size != 1) {
// There are multiple optimizable methods with different parameter lists that can be
// rewritten into having the same parameter list.
assert size > 1;
return;
}
DexTypeList parameters = parameterListsBeforeNormalization.iterator().next();
Set<DexTypeList> reservedParameters =
reservedParameterLists.getOrDefault(sortedParameters, Collections.emptySet());
if (!reservedParameters.isEmpty() && !reservedParameters.contains(parameters)) {
// There is at least one optimizable method that can be rewritten into having the same
// parameter list as an unoptimizable method.
return;
}
unoptimizableParameterLists.add(parameters);
});
ThreadUtils.processMethods(
appView,
method ->
computeExtraReservationsFromMethod(
method, unoptimizableParameterLists, unoptimizableSignatures),
executorService);
return new GlobalReservationState(reservedParameterLists, unoptimizableSignatures);
}
private void computeReservationsFromMethod(
ProgramMethod method,
Map<DexTypeList, Set<DexTypeList>> optimizableParameterLists,
Map<DexTypeList, Set<DexTypeList>> reservedParameterLists,
DexMethodSignatureSet unoptimizableSignatures) {
if (isUnoptimizable(method)) {
// Record that other optimizable methods with the same set of parameter types should be
// rewritten to have the same parameter list as this method.
reservedParameterLists
.computeIfAbsent(
method.getParameters().getSorted(), ignoreKey(Sets::newConcurrentHashSet))
.add(method.getParameters());
// Mark signature as unoptimizable.
unoptimizableSignatures.add(method);
} else {
// Record that the method's parameter list can be rewritten into any permutation.
optimizableParameterLists
.computeIfAbsent(
method.getParameters().getSorted(), ignoreKey(Sets::newConcurrentHashSet))
.add(method.getParameters());
}
}
private void computeExtraReservationsFromMethod(
ProgramMethod method,
Set<DexTypeList> unoptimizableParameterLists,
DexMethodSignatureSet unoptimizableSignatures) {
if (unoptimizableParameterLists.contains(method.getParameters())) {
unoptimizableSignatures.add(method.getMethodSignature());
}
}
private boolean isUnoptimizable(ProgramMethod method) {
// TODO(b/195112263): This is incomplete.
return appView.getKeepInfo(method).isPinned(options)
|| method.getDefinition().isLibraryMethodOverride().isPossiblyTrue();
}
static class GlobalReservationState {
// Used to track if a given parameter list should be mapped to a specific permutation instead of
// just sorting the parameter list. This is used to ensure that we will rewrite parameter lists
// such as (A, B) into (B, A) if there is an unoptimizable method with parameter list (B, A).
Map<DexTypeList, DexTypeList> reservedParameters;
// Tracks the set of unoptimizable method signatures. These must remain as-is.
DexMethodSignatureSet unoptimizableSignatures;
GlobalReservationState(
Map<DexTypeList, Set<DexTypeList>> reservedParameterLists,
DexMethodSignatureSet unoptimizableSignatures) {
this.reservedParameters = selectDeterministicTarget(reservedParameterLists);
this.unoptimizableSignatures = unoptimizableSignatures;
}
private static Map<DexTypeList, DexTypeList> selectDeterministicTarget(
Map<DexTypeList, Set<DexTypeList>> reservedParameterLists) {
Map<DexTypeList, DexTypeList> result = new HashMap<>();
reservedParameterLists.forEach(
(sortedParameters, candidates) -> {
Iterator<DexTypeList> iterator = candidates.iterator();
DexTypeList smallestCandidate = iterator.next();
while (iterator.hasNext()) {
DexTypeList candidate = iterator.next();
if (candidate.compareTo(smallestCandidate) < 0) {
smallestCandidate = candidate;
}
}
result.put(sortedParameters, smallestCandidate);
});
return result;
}
DexTypeList getReservedParameters(DexMethodSignature methodSignature) {
DexTypeList sortedParameters = methodSignature.getParameters().getSorted();
return reservedParameters.getOrDefault(sortedParameters, sortedParameters);
}
boolean isUnoptimizable(DexMethodSignature methodSignature) {
return unoptimizableSignatures.contains(methodSignature);
}
}
static class LocalReservationState {
MutableBidirectionalOneToOneMap<DexMethodSignature, DexMethodSignature> newMethodSignatures =
new BidirectionalOneToOneHashMap<>();
// TODO: avoid sorting multiple times.
DexMethodSignature getNewMethodSignature(
DexMethodSignature methodSignature,
DexItemFactory dexItemFactory,
GlobalReservationState globalReservationState) {
if (globalReservationState.isUnoptimizable(methodSignature)) {
assert !newMethodSignatures.containsKey(methodSignature);
return methodSignature;
}
DexMethodSignature reservedSignature = newMethodSignatures.get(methodSignature);
if (reservedSignature != null) {
assert reservedSignature
.getParameters()
.equals(globalReservationState.getReservedParameters(methodSignature));
return reservedSignature;
}
DexTypeList reservedParameters =
globalReservationState.getReservedParameters(methodSignature);
DexMethodSignature newMethodSignature =
methodSignature.withParameters(reservedParameters, dexItemFactory);
if (newMethodSignatures.containsValue(newMethodSignature)) {
int index = 1;
String newMethodBaseName = methodSignature.getName().toString();
do {
DexString newMethodName = dexItemFactory.createString(newMethodBaseName + "$" + index);
newMethodSignature = newMethodSignature.withName(newMethodName);
} while (newMethodSignatures.containsValue(newMethodSignature));
}
newMethodSignatures.put(methodSignature, newMethodSignature);
return newMethodSignature;
}
}
}