blob: 3b8681bc2bacc2ea614c13850016939165dad0c5 [file] [log] [blame]
// Copyright (c) 2020, 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;
import static com.android.tools.r8.utils.collections.ThrowingMap.isThrowingMap;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.collections.ThrowingMap;
import com.google.common.collect.Sets;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class MethodAccessInfoCollection {
private Map<DexMethod, ProgramMethodSet> directInvokes;
private Map<DexMethod, ProgramMethodSet> interfaceInvokes;
private Map<DexMethod, ProgramMethodSet> staticInvokes;
private Map<DexMethod, ProgramMethodSet> superInvokes;
private Map<DexMethod, ProgramMethodSet> virtualInvokes;
private boolean fullyDestroyed = false;
private MethodAccessInfoCollection(
Map<DexMethod, ProgramMethodSet> directInvokes,
Map<DexMethod, ProgramMethodSet> interfaceInvokes,
Map<DexMethod, ProgramMethodSet> staticInvokes,
Map<DexMethod, ProgramMethodSet> superInvokes,
Map<DexMethod, ProgramMethodSet> virtualInvokes) {
this.directInvokes = directInvokes;
this.interfaceInvokes = interfaceInvokes;
this.staticInvokes = staticInvokes;
this.superInvokes = superInvokes;
this.virtualInvokes = virtualInvokes;
}
public static ConcurrentBuilder concurrentBuilder() {
return new ConcurrentBuilder();
}
public static IdentityBuilder identityBuilder() {
return new IdentityBuilder();
}
public void destroy() {
assert !fullyDestroyed;
directInvokes = ThrowingMap.get();
interfaceInvokes = ThrowingMap.get();
staticInvokes = ThrowingMap.get();
superInvokes = ThrowingMap.get();
virtualInvokes = ThrowingMap.get();
fullyDestroyed = true;
}
public void destroyNonDirectNonSuperInvokes() {
interfaceInvokes = ThrowingMap.get();
staticInvokes = ThrowingMap.get();
virtualInvokes = ThrowingMap.get();
}
public void destroySuperInvokes() {
superInvokes = ThrowingMap.get();
}
public Modifier modifier() {
return new Modifier(
directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes);
}
public void forEachMethodReference(Consumer<DexMethod> method) {
Set<DexMethod> seen = Sets.newIdentityHashSet();
directInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
interfaceInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
staticInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
superInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
virtualInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
}
public void forEachDirectInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
directInvokes.forEach(consumer);
}
public void forEachInterfaceInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
interfaceInvokes.forEach(consumer);
}
public void forEachStaticInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
staticInvokes.forEach(consumer);
}
public void forEachSuperInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
superInvokes.forEach(consumer);
}
public boolean hasSuperInvoke(DexMethod method) {
return !superInvokes.getOrDefault(method, ProgramMethodSet.empty()).isEmpty();
}
public void forEachSuperInvokeContext(DexMethod method, Consumer<ProgramMethod> consumer) {
superInvokes.getOrDefault(method, ProgramMethodSet.empty()).forEach(consumer);
}
public void forEachVirtualInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
virtualInvokes.forEach(consumer);
}
public void forEachVirtualInvokeContext(DexMethod method, Consumer<ProgramMethod> consumer) {
virtualInvokes.getOrDefault(method, ProgramMethodSet.empty()).forEach(consumer);
}
public boolean isVirtualInvokesDestroyed() {
return isThrowingMap(virtualInvokes);
}
public MethodAccessInfoCollection rewrittenWithLens(
DexDefinitionSupplier definitions, GraphLens lens, Timing timing) {
timing.begin("Rewrite MethodAccessInfoCollection");
MethodAccessInfoCollection result;
if (fullyDestroyed) {
result = this;
} else if (isThrowingMap(interfaceInvokes)) {
assert isThrowingMap(staticInvokes);
assert isThrowingMap(virtualInvokes);
assert !isThrowingMap(directInvokes);
MethodAccessInfoCollection.Builder<?> builder = identityBuilder();
rewriteInvokesWithLens(builder, directInvokes, definitions, lens, InvokeType.DIRECT);
rewriteInvokesWithLens(builder, superInvokes, definitions, lens, InvokeType.DIRECT);
result = builder.build();
result.destroyNonDirectNonSuperInvokes();
if (isThrowingMap(superInvokes)) {
result.destroySuperInvokes();
}
} else {
MethodAccessInfoCollection.Builder<?> builder = identityBuilder();
rewriteInvokesWithLens(builder, directInvokes, definitions, lens, InvokeType.DIRECT);
rewriteInvokesWithLens(builder, interfaceInvokes, definitions, lens, InvokeType.INTERFACE);
rewriteInvokesWithLens(builder, staticInvokes, definitions, lens, InvokeType.STATIC);
rewriteInvokesWithLens(builder, superInvokes, definitions, lens, InvokeType.SUPER);
rewriteInvokesWithLens(builder, virtualInvokes, definitions, lens, InvokeType.VIRTUAL);
result = builder.build();
}
timing.end();
return result;
}
private static void rewriteInvokesWithLens(
MethodAccessInfoCollection.Builder<?> builder,
Map<DexMethod, ProgramMethodSet> invokes,
DexDefinitionSupplier definitions,
GraphLens lens,
InvokeType type) {
if (!isThrowingMap(invokes)) {
invokes.forEach(
(reference, contexts) -> {
ProgramMethodSet newContexts = contexts.rewrittenWithLens(definitions, lens);
for (ProgramMethod newContext : newContexts) {
MethodLookupResult methodLookupResult =
lens.lookupMethod(reference, newContext.getReference(), type);
DexMethod newReference = methodLookupResult.getReference();
InvokeType newType = methodLookupResult.getType();
builder.registerInvokeInContext(newReference, newContext, newType);
}
});
}
}
public MethodAccessInfoCollection withoutPrunedItems(PrunedItems prunedItems) {
if (!fullyDestroyed) {
pruneItems(prunedItems, directInvokes);
pruneItems(prunedItems, interfaceInvokes);
pruneItems(prunedItems, staticInvokes);
pruneItems(prunedItems, superInvokes);
pruneItems(prunedItems, virtualInvokes);
}
return this;
}
private static void pruneItems(
PrunedItems prunedItems, Map<DexMethod, ProgramMethodSet> invokes) {
if (isThrowingMap(invokes)) {
return;
}
invokes
.values()
.removeIf(
contexts -> {
contexts.removeIf(
context -> {
if (prunedItems.isRemoved(context.getReference())) {
return true;
}
assert prunedItems.getPrunedApp().definitionFor(context.getReference()) != null
: "Expected method to be present: "
+ context.getReference().toSourceString();
return false;
});
return contexts.isEmpty();
});
}
public boolean verify(AppView<AppInfoWithLiveness> appView) {
assert verifyNoNonResolving(appView);
return true;
}
public boolean verifyNoNonResolving(AppView<AppInfoWithLiveness> appView) {
verifyNoNonResolving(appView, directInvokes);
verifyNoNonResolving(appView, interfaceInvokes);
verifyNoNonResolving(appView, staticInvokes);
verifyNoNonResolving(appView, superInvokes);
verifyNoNonResolving(appView, virtualInvokes);
return true;
}
private void verifyNoNonResolving(
AppView<AppInfoWithLiveness> appView, Map<DexMethod, ?> invokes) {
if (!isThrowingMap(invokes)) {
for (DexMethod method : invokes.keySet()) {
MethodResolutionResult result =
appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method);
assert !result.isFailedResolution()
: "Unexpected method that does not resolve: " + method.toSourceString();
assert !result.isSignaturePolymorphicResolution(method, appView.dexItemFactory())
: "Unexpected signature polymorphic resolution: " + method.toSourceString();
}
}
}
public abstract static class Builder<T extends Map<DexMethod, ProgramMethodSet>> {
private final T directInvokes;
private final T interfaceInvokes;
private final T staticInvokes;
private final T superInvokes;
private final T virtualInvokes;
private Builder(Supplier<T> factory) {
this(factory.get(), factory.get(), factory.get(), factory.get(), factory.get());
}
private Builder(
T directInvokes, T interfaceInvokes, T staticInvokes, T superInvokes, T virtualInvokes) {
this.directInvokes = directInvokes;
this.interfaceInvokes = interfaceInvokes;
this.staticInvokes = staticInvokes;
this.superInvokes = superInvokes;
this.virtualInvokes = virtualInvokes;
}
public T getDirectInvokes() {
return directInvokes;
}
public T getInterfaceInvokes() {
return interfaceInvokes;
}
public T getStaticInvokes() {
return staticInvokes;
}
public T getSuperInvokes() {
return superInvokes;
}
public T getVirtualInvokes() {
return virtualInvokes;
}
public boolean registerInvokeInContext(
DexMethod invokedMethod, ProgramMethod context, InvokeType type) {
switch (type) {
case DIRECT:
return registerInvokeDirectInContext(invokedMethod, context);
case INTERFACE:
return registerInvokeInterfaceInContext(invokedMethod, context);
case STATIC:
return registerInvokeStaticInContext(invokedMethod, context);
case SUPER:
return registerInvokeSuperInContext(invokedMethod, context);
case VIRTUAL:
return registerInvokeVirtualInContext(invokedMethod, context);
default:
assert false;
return false;
}
}
public boolean registerInvokeDirectInContext(DexMethod invokedMethod, ProgramMethod context) {
return registerInvokeMethodInContext(invokedMethod, context, directInvokes);
}
public void registerInvokeDirectInContexts(DexMethod invokedMethod, ProgramMethodSet contexts) {
registerInvokeMethodInContexts(invokedMethod, contexts, directInvokes);
}
public boolean registerInvokeInterfaceInContext(
DexMethod invokedMethod, ProgramMethod context) {
return registerInvokeMethodInContext(invokedMethod, context, interfaceInvokes);
}
public void registerInvokeInterfaceInContexts(
DexMethod invokedMethod, ProgramMethodSet contexts) {
registerInvokeMethodInContexts(invokedMethod, contexts, interfaceInvokes);
}
public boolean registerInvokeStaticInContext(DexMethod invokedMethod, ProgramMethod context) {
return registerInvokeMethodInContext(invokedMethod, context, staticInvokes);
}
public void registerInvokeStaticInContexts(DexMethod invokedMethod, ProgramMethodSet contexts) {
registerInvokeMethodInContexts(invokedMethod, contexts, staticInvokes);
}
public boolean registerInvokeSuperInContext(DexMethod invokedMethod, ProgramMethod context) {
return registerInvokeMethodInContext(invokedMethod, context, superInvokes);
}
public void registerInvokeSuperInContexts(DexMethod invokedMethod, ProgramMethodSet contexts) {
registerInvokeMethodInContexts(invokedMethod, contexts, superInvokes);
}
public boolean registerInvokeVirtualInContext(DexMethod invokedMethod, ProgramMethod context) {
return registerInvokeMethodInContext(invokedMethod, context, virtualInvokes);
}
public void registerInvokeVirtualInContexts(
DexMethod invokedMethod, ProgramMethodSet contexts) {
registerInvokeMethodInContexts(invokedMethod, contexts, virtualInvokes);
}
private static boolean registerInvokeMethodInContext(
DexMethod invokedMethod, ProgramMethod context, Map<DexMethod, ProgramMethodSet> invokes) {
return invokes
.computeIfAbsent(invokedMethod, ignore -> ProgramMethodSet.create())
.add(context);
}
private static void registerInvokeMethodInContexts(
DexMethod invokedMethod,
ProgramMethodSet contexts,
Map<DexMethod, ProgramMethodSet> invokes) {
ProgramMethodSet existingContexts = invokes.put(invokedMethod, contexts);
if (existingContexts != null) {
if (existingContexts.size() > contexts.size()) {
invokes.put(invokedMethod, existingContexts);
existingContexts.addAll(contexts);
} else {
contexts.addAll(existingContexts);
}
}
}
public void removeIf(Predicate<DexMethod> predicate) {
removeIf(predicate, directInvokes);
removeIf(predicate, interfaceInvokes);
removeIf(predicate, staticInvokes);
removeIf(predicate, superInvokes);
removeIf(predicate, virtualInvokes);
}
private static void removeIf(Predicate<DexMethod> predicate, Map<DexMethod, ?> invokes) {
invokes.keySet().removeIf(predicate);
}
public void removeNonResolving(
AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
// TODO(b/313365881): Should use non-legacy resolution, but this fails.
removeIf(
method -> {
MethodResolutionResult result =
appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method);
if (result.isFailedResolution()
|| result.isSignaturePolymorphicResolution(method, appView.dexItemFactory())) {
return true;
}
if (result.hasProgramResult()) {
ProgramMethod resolvedMethod = result.getResolvedProgramMethod();
// Guard against an unusual case where the invoke resolves to a program method but the
// invoke is invalid (e.g., invoke-interface to a non-interface), such that the
// resolved method is not retained after all.
if (!enqueuer.isMethodLive(resolvedMethod)
&& !enqueuer.isMethodTargeted(resolvedMethod)) {
return true;
}
}
return false;
});
}
public MethodAccessInfoCollection build() {
return new MethodAccessInfoCollection(
directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes);
}
}
public static class ConcurrentBuilder
extends Builder<ConcurrentHashMap<DexMethod, ProgramMethodSet>> {
private ConcurrentBuilder() {
super(ConcurrentHashMap::new);
}
}
public static class IdentityBuilder
extends Builder<IdentityHashMap<DexMethod, ProgramMethodSet>> {
private IdentityBuilder() {
super(IdentityHashMap::new);
}
}
public static class Modifier extends Builder<Map<DexMethod, ProgramMethodSet>> {
private Modifier(
Map<DexMethod, ProgramMethodSet> directInvokes,
Map<DexMethod, ProgramMethodSet> interfaceInvokes,
Map<DexMethod, ProgramMethodSet> staticInvokes,
Map<DexMethod, ProgramMethodSet> superInvokes,
Map<DexMethod, ProgramMethodSet> virtualInvokes) {
super(directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes);
}
public void addAll(MethodAccessInfoCollection collection) {
collection.forEachDirectInvoke(this::registerInvokeDirectInContexts);
collection.forEachInterfaceInvoke(this::registerInvokeInterfaceInContexts);
collection.forEachStaticInvoke(this::registerInvokeStaticInContexts);
collection.forEachSuperInvoke(this::registerInvokeSuperInContexts);
collection.forEachVirtualInvoke(this::registerInvokeVirtualInContexts);
}
}
}