blob: cc8f5b1f9077cf6edbc6838563802b708c1e0c92 [file] [log] [blame]
// Copyright (c) 2018, 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.optimize;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ArgumentUse;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.NestedGraphLens;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.SymbolGenerationUtils;
import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
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.BidirectionalOneToOneMap;
import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Streams;
import java.util.BitSet;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
public class UnusedArgumentsCollector {
private static final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
private final AppView<AppInfoWithLiveness> appView;
private final MethodPoolCollection methodPoolCollection;
private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> methodMapping =
new BidirectionalOneToOneHashMap<>();
private final Map<DexMethod, ArgumentInfoCollection> removedArguments = new IdentityHashMap<>();
public static class UnusedArgumentsGraphLens extends NestedGraphLens {
private final Map<DexMethod, ArgumentInfoCollection> removedArguments;
UnusedArgumentsGraphLens(
AppView<?> appView,
BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap,
Map<DexMethod, ArgumentInfoCollection> removedArguments) {
super(appView, EMPTY_FIELD_MAP, methodMap, EMPTY_TYPE_MAP);
this.removedArguments = removedArguments;
}
@Override
protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
return prototypeChanges.withRemovedArguments(
removedArguments.getOrDefault(method, ArgumentInfoCollection.empty()));
}
}
public UnusedArgumentsCollector(
AppView<AppInfoWithLiveness> appView, MethodPoolCollection methodPoolCollection) {
this.appView = appView;
this.methodPoolCollection = methodPoolCollection;
}
public UnusedArgumentsGraphLens run(ExecutorService executorService, Timing timing)
throws ExecutionException {
ThreadUtils.awaitFutures(
Streams.stream(appView.appInfo().classes())
.map(this::runnableForClass)
.map(executorService::submit)
// Materialize list such that all runnables are submitted to the executor service
// before calling awaitFutures().
.collect(Collectors.toList()));
// Build method pool collection to enable unused argument removal for virtual methods.
methodPoolCollection.buildAll(executorService, timing);
// Visit classes in deterministic order to ensure deterministic output.
appView.appInfo().classesWithDeterministicOrder().forEach(this::processVirtualMethods);
if (!methodMapping.isEmpty()) {
return new UnusedArgumentsGraphLens(appView, methodMapping, removedArguments);
}
return null;
}
private class UsedSignatures {
private final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
private final Set<Wrapper<DexMethod>> usedSignatures = new HashSet<>();
private boolean isMethodSignatureAvailable(DexMethod method) {
return !usedSignatures.contains(equivalence.wrap(method));
}
private void markSignatureAsUsed(DexMethod method) {
usedSignatures.add(equivalence.wrap(method));
}
DexMethod getNewSignature(DexEncodedMethod method, DexProto newProto) {
DexMethod newSignature;
int count = 0;
DexString newName = null;
do {
if (newName == null) {
newName = method.getReference().name;
} else if (!appView.dexItemFactory().isConstructor(method.getReference())) {
newName =
appView
.dexItemFactory()
.createString(
SymbolGenerationUtils.numberToIdentifier(
count,
MixedCasing.USE_MIXED_CASE,
method.getReference().name.toSourceString().toCharArray()));
} else {
// Constructors must be named `<init>`.
return null;
}
newSignature =
appView.dexItemFactory().createMethod(method.getHolderType(), newProto, newName);
count++;
} while (!isMethodSignatureAvailable(newSignature));
return newSignature;
}
DexEncodedMethod removeArguments(
DexEncodedMethod method, DexMethod newSignature, ArgumentInfoCollection unused) {
boolean removed = usedSignatures.remove(equivalence.wrap(method.getReference()));
assert removed;
markSignatureAsUsed(newSignature);
return method.toTypeSubstitutedMethod(
newSignature, unused.createParameterAnnotationsRemover(method));
}
}
private class GloballyUsedSignatures {
private final MemberPool<DexMethod> methodPool;
GloballyUsedSignatures(MemberPool<DexMethod> methodPool) {
this.methodPool = methodPool;
}
DexMethod getNewSignature(DexEncodedMethod method, DexProto newProto) {
DexMethod newSignature;
int count = 0;
DexString newName = null;
do {
if (newName == null) {
newName = method.getReference().name;
} else if (!appView.dexItemFactory().isConstructor(method.getReference())) {
newName =
appView
.dexItemFactory()
.createString(method.getReference().name.toSourceString() + count);
} else {
// Constructors must be named `<init>`.
return null;
}
newSignature =
appView.dexItemFactory().createMethod(method.getHolderType(), newProto, newName);
count++;
} while (methodPool.hasSeen(equivalence.wrap(newSignature)));
return newSignature;
}
DexEncodedMethod removeArguments(
DexEncodedMethod method, DexMethod newSignature, ArgumentInfoCollection unused) {
methodPool.seen(equivalence.wrap(newSignature));
return method.toTypeSubstitutedMethod(
newSignature, unused.createParameterAnnotationsRemover(method));
}
}
private Runnable runnableForClass(DexProgramClass clazz) {
return () -> this.processDirectMethods(clazz);
}
private void processDirectMethods(DexProgramClass clazz) {
UsedSignatures signatures = new UsedSignatures();
for (DexEncodedMethod method : clazz.methods()) {
signatures.markSignatureAsUsed(method.getReference());
}
clazz
.getMethodCollection()
.replaceDirectMethods(
method -> {
// If this is a method with known resolution issues, then don't remove any unused
// arguments.
if (appView.appInfo().isFailedResolutionTarget(method.getReference())) {
return method;
}
ArgumentInfoCollection unused = collectUnusedArguments(method);
if (unused != null && unused.hasRemovedArguments()) {
DexProto newProto = createProtoWithRemovedArguments(method, unused);
DexMethod newSignature = signatures.getNewSignature(method, newProto);
if (newSignature == null) {
assert appView.dexItemFactory().isConstructor(method.getReference());
return method;
}
DexEncodedMethod newMethod =
signatures.removeArguments(method, newSignature, unused);
synchronized (this) {
methodMapping.put(method.getReference(), newMethod.getReference());
removedArguments.put(newMethod.getReference(), unused);
}
return newMethod;
}
return method;
});
}
private void processVirtualMethods(DexProgramClass clazz) {
MemberPool<DexMethod> methodPool = methodPoolCollection.get(clazz);
GloballyUsedSignatures signatures = new GloballyUsedSignatures(methodPool);
clazz
.getMethodCollection()
.replaceVirtualMethods(
method -> {
ArgumentInfoCollection unused = collectUnusedArguments(method, methodPool);
if (unused != null && unused.hasRemovedArguments()) {
DexProto newProto = createProtoWithRemovedArguments(method, unused);
DexMethod newSignature = signatures.getNewSignature(method, newProto);
// Double-check that the new method signature is in fact available.
assert !methodPool.hasSeenStrictlyAbove(equivalence.wrap(newSignature));
assert !methodPool.hasSeenStrictlyBelow(equivalence.wrap(newSignature));
DexEncodedMethod newMethod =
signatures.removeArguments(
method, signatures.getNewSignature(method, newProto), unused);
methodMapping.put(method.getReference(), newMethod.getReference());
removedArguments.put(newMethod.getReference(), unused);
return newMethod;
}
return method;
});
}
private ArgumentInfoCollection collectUnusedArguments(DexEncodedMethod method) {
return collectUnusedArguments(method, null);
}
private ArgumentInfoCollection collectUnusedArguments(
DexEncodedMethod method, MemberPool<DexMethod> methodPool) {
if (ArgumentRemovalUtils.isPinned(method, appView)
|| appView.appInfo().isKeepUnusedArgumentsMethod(method.getReference())) {
return null;
}
// Only process classfile code objects.
if (method.getCode() == null || !method.getCode().isCfCode()) {
return null;
}
if (method.isNonPrivateVirtualMethod()) {
// Abort if the method overrides another method, or if the method is overridden. In both cases
// an unused argument cannot be removed unless it is unused in all of the related methods in
// the hierarchy.
assert methodPool != null;
Wrapper<DexMethod> wrapper = equivalence.wrap(method.getReference());
if (methodPool.hasSeenStrictlyAbove(wrapper) || methodPool.hasSeenStrictlyBelow(wrapper)) {
return null;
}
}
int offset = method.accessFlags.isStatic() ? 0 : 1;
int argumentCount = method.getReference().proto.parameters.size() + offset;
CollectUsedArguments collector = new CollectUsedArguments();
if (!method.accessFlags.isStatic()) {
// TODO(65810338): The receiver cannot be removed without transforming the method to being
// static.
collector.register(0);
}
method.getCode().registerArgumentReferences(method, collector);
BitSet used = collector.getUsedArguments();
if (used.cardinality() < argumentCount) {
ArgumentInfoCollection.Builder argInfosBuilder = ArgumentInfoCollection.builder();
for (int argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) {
if (!used.get(argumentIndex)) {
RemovedArgumentInfo removedArg =
RemovedArgumentInfo.builder()
.setType(method.getReference().proto.parameters.values[argumentIndex - offset])
.build();
argInfosBuilder.addArgumentInfo(argumentIndex, removedArg);
}
}
return argInfosBuilder.build();
}
return null;
}
private DexProto createProtoWithRemovedArguments(
DexEncodedMethod encodedMethod, ArgumentInfoCollection unused) {
DexType[] parameters = unused.rewriteParameters(encodedMethod);
return appView
.dexItemFactory()
.createProto(encodedMethod.getReference().proto.returnType, parameters);
}
private static class CollectUsedArguments extends ArgumentUse {
private final BitSet used = new BitSet();
BitSet getUsedArguments() {
return used;
}
@Override
public boolean register(int argument) {
used.set(argument);
return true;
}
}
}