blob: c4152fae7c15bdb8ae4929589d4e5ab3200debfe [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.synthesis;
import static java.util.Collections.emptyList;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
import com.android.tools.r8.synthesis.SyntheticItems.ContextsForGlobalSynthetics;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
/**
* Immutable collection of committed items.
*
* <p>This structure is to make it easier to pass the items from SyntheticItems to CommittedItems
* and back while also providing a builder for updating the committed synthetics.
*/
class CommittedSyntheticsCollection {
static class Builder {
private final CommittedSyntheticsCollection parent;
private Map<DexType, List<SyntheticProgramClassReference>> classes = null;
private Map<DexType, List<SyntheticMethodReference>> methods = null;
private ImmutableSet.Builder<DexType> newSyntheticInputs = null;
private Map<DexType, Set<DexType>> globalContexts = null;
public Builder(CommittedSyntheticsCollection parent) {
this.parent = parent;
}
public Builder addItem(SyntheticDefinition<?, ?, ?> definition) {
if (definition.isProgramDefinition()) {
definition.asProgramDefinition().apply(this::addMethod, this::addClass);
}
return this;
}
public Builder addClass(SyntheticProgramClassDefinition definition) {
return addClass(definition.toReference());
}
public Builder addClass(SyntheticProgramClassReference reference) {
if (classes == null) {
classes = new IdentityHashMap<>();
}
classes.computeIfAbsent(reference.getHolder(), ignore -> new ArrayList<>()).add(reference);
return this;
}
public Builder addMethod(SyntheticMethodDefinition definition) {
return addMethod(definition.toReference());
}
public Builder addMethod(SyntheticMethodReference reference) {
if (methods == null) {
methods = new IdentityHashMap<>();
}
methods.computeIfAbsent(reference.getHolder(), ignore -> new ArrayList<>()).add(reference);
return this;
}
public Builder addSyntheticInput(DexType syntheticInput) {
ensureNewSyntheticInputs().add(syntheticInput);
return this;
}
Builder collectSyntheticInputs() {
if (classes != null) {
ensureNewSyntheticInputs().addAll(classes.keySet());
}
if (methods != null) {
ensureNewSyntheticInputs().addAll(methods.keySet());
}
return this;
}
private ImmutableSet.Builder<DexType> ensureNewSyntheticInputs() {
if (newSyntheticInputs == null) {
newSyntheticInputs = ImmutableSet.builder();
}
return newSyntheticInputs;
}
public Builder addGlobalContexts(ContextsForGlobalSynthetics additionalGlobalContexts) {
if (!additionalGlobalContexts.isEmpty()) {
if (globalContexts == null) {
globalContexts = new IdentityHashMap<>();
}
additionalGlobalContexts.forEach(
(globalType, contexts) ->
globalContexts
.computeIfAbsent(globalType, k -> SetUtils.newIdentityHashSet())
.addAll(contexts));
}
return this;
}
public CommittedSyntheticsCollection build() {
if (classes == null && methods == null && globalContexts == null) {
// Adding synthetic inputs implies that an actual synthetic is added too.
assert newSyntheticInputs == null;
return parent;
}
ImmutableMap<DexType, List<SyntheticProgramClassReference>> allClasses =
mergeMapOfLists(classes, parent.classes);
ImmutableMap<DexType, List<SyntheticMethodReference>> allMethods =
mergeMapOfLists(methods, parent.methods);
ImmutableSet<DexType> allSyntheticInputs =
newSyntheticInputs == null ? parent.syntheticInputs : newSyntheticInputs.build();
ImmutableMap<DexType, Set<DexType>> allGlobalContexts =
globalContexts == null
? parent.globalContexts
: mergeMapOfSets(globalContexts, parent.globalContexts);
return new CommittedSyntheticsCollection(
parent.naming, allMethods, allClasses, allGlobalContexts, allSyntheticInputs);
}
}
private static <T> ImmutableMap<DexType, List<T>> mergeMapOfLists(
Map<DexType, List<T>> newSynthetics, ImmutableMap<DexType, List<T>> oldSynthetics) {
if (newSynthetics == null) {
return oldSynthetics;
}
oldSynthetics.forEach(
(type, elements) ->
newSynthetics.computeIfAbsent(type, ignore -> new ArrayList<>()).addAll(elements));
return ImmutableMap.copyOf(newSynthetics);
}
private static <T> ImmutableMap<DexType, Set<T>> mergeMapOfSets(
Map<DexType, Set<T>> newSynthetics, ImmutableMap<DexType, Set<T>> oldSynthetics) {
if (newSynthetics == null) {
return oldSynthetics;
}
oldSynthetics.forEach(
(type, elements) ->
newSynthetics.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(elements));
return ImmutableMap.copyOf(newSynthetics);
}
private final SyntheticNaming naming;
/** Mapping from synthetic type to its synthetic method item description. */
private final ImmutableMap<DexType, List<SyntheticMethodReference>> methods;
/** Mapping from synthetic type to its synthetic class item description. */
private final ImmutableMap<DexType, List<SyntheticProgramClassReference>> classes;
/** Mapping from global synthetic type to its synthesizing contexts. */
private final ImmutableMap<DexType, Set<DexType>> globalContexts;
/** Set of synthetic types that were present in the input. */
public final ImmutableSet<DexType> syntheticInputs;
public CommittedSyntheticsCollection(
SyntheticNaming naming,
ImmutableMap<DexType, List<SyntheticMethodReference>> methods,
ImmutableMap<DexType, List<SyntheticProgramClassReference>> classes,
ImmutableMap<DexType, Set<DexType>> globalContexts,
ImmutableSet<DexType> syntheticInputs) {
this.naming = naming;
this.methods = methods;
this.classes = classes;
this.globalContexts = globalContexts;
this.syntheticInputs = syntheticInputs;
assert verifySyntheticInputsSubsetOfSynthetics();
}
SyntheticNaming getNaming() {
return naming;
}
private boolean verifySyntheticInputsSubsetOfSynthetics() {
Set<DexType> synthetics =
ImmutableSet.<DexType>builder().addAll(methods.keySet()).addAll(classes.keySet()).build();
syntheticInputs.forEach(
syntheticInput -> {
assert synthetics.contains(syntheticInput)
: "Expected " + syntheticInput.toSourceString() + " to be a synthetic";
});
return true;
}
public static CommittedSyntheticsCollection empty(SyntheticNaming naming) {
return new CommittedSyntheticsCollection(
naming, ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of());
}
Builder builder() {
return new Builder(this);
}
boolean isEmpty() {
boolean empty = methods.isEmpty() && classes.isEmpty();
assert !empty || syntheticInputs.isEmpty();
return empty;
}
public boolean containsType(DexType type) {
return methods.containsKey(type) || classes.containsKey(type);
}
boolean containsTypeOfKind(DexType type, SyntheticKind kind) {
List<SyntheticProgramClassReference> synthetics = classes.get(type);
if (synthetics == null) {
List<SyntheticMethodReference> syntheticMethodReferences = methods.get(type);
if (syntheticMethodReferences == null) {
return false;
}
for (SyntheticMethodReference syntheticMethodReference : syntheticMethodReferences) {
if (syntheticMethodReference.getKind() == kind) {
return true;
}
}
return false;
}
for (SyntheticProgramClassReference synthetic : synthetics) {
if (synthetic.getKind() == kind) {
return true;
}
}
return false;
}
public boolean containsSyntheticInput(DexType type) {
return syntheticInputs.contains(type);
}
public Set<DexType> getContextsForGlobal(DexType globalSynthetic) {
return globalContexts.get(globalSynthetic);
}
public ImmutableMap<DexType, Set<DexType>> getGlobalContexts() {
return globalContexts;
}
public ImmutableMap<DexType, List<SyntheticMethodReference>> getMethods() {
return methods;
}
public ImmutableMap<DexType, List<SyntheticProgramClassReference>> getClasses() {
return classes;
}
public Iterable<SyntheticReference<?, ?, ?>> getItems(DexType type) {
return Iterables.concat(
classes.getOrDefault(type, emptyList()), methods.getOrDefault(type, emptyList()));
}
public void forEachSyntheticInput(Consumer<DexType> fn) {
syntheticInputs.forEach(fn);
}
public void forEachItem(Consumer<SyntheticReference<?, ?, ?>> fn) {
methods.values().forEach(r -> r.forEach(fn));
classes.values().forEach(r -> r.forEach(fn));
}
CommittedSyntheticsCollection pruneItems(PrunedItems prunedItems) {
Set<DexType> removed = prunedItems.getNoLongerSyntheticItems();
if (removed.isEmpty()) {
return this;
}
Builder builder = CommittedSyntheticsCollection.empty(naming).builder();
boolean changed = false;
for (SyntheticMethodReference reference : IterableUtils.flatten(methods.values())) {
if (removed.contains(reference.getHolder())) {
changed = true;
} else {
builder.addMethod(reference);
}
}
for (SyntheticProgramClassReference reference : IterableUtils.flatten(classes.values())) {
if (removed.contains(reference.getHolder())) {
changed = true;
} else {
builder.addClass(reference);
}
}
for (DexType syntheticInput : syntheticInputs) {
if (removed.contains(syntheticInput)) {
changed = true;
} else {
builder.addSyntheticInput(syntheticInput);
}
}
// Global synthetic contexts are only collected for per-file modes which only prune synthetic
// items, not inputs.
assert globalContexts.isEmpty()
|| prunedItems.getNoLongerSyntheticItems().size() == prunedItems.getRemovedClasses().size();
return changed ? builder.build() : this;
}
CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens) {
ImmutableSet.Builder<DexType> syntheticInputsBuilder = ImmutableSet.builder();
return new CommittedSyntheticsCollection(
naming,
rewriteItems(methods, lens, syntheticInputsBuilder),
rewriteItems(classes, lens, syntheticInputsBuilder),
globalContexts,
syntheticInputsBuilder.build());
}
private <R extends Rewritable<R>> ImmutableMap<DexType, List<R>> rewriteItems(
Map<DexType, List<R>> items,
NonIdentityGraphLens lens,
ImmutableSet.Builder<DexType> syntheticInputsBuilder) {
Map<DexType, List<R>> rewrittenItems = new IdentityHashMap<>();
for (R reference : IterableUtils.flatten(items.values())) {
R rewritten = reference.rewrite(lens);
if (rewritten != null) {
rewrittenItems
.computeIfAbsent(rewritten.getHolder(), ignore -> new ArrayList<>())
.add(rewritten);
if (syntheticInputs.contains(reference.getHolder())) {
syntheticInputsBuilder.add(rewritten.getHolder());
}
}
}
return ImmutableMap.copyOf(rewrittenItems);
}
boolean verifyTypesAreInApp(DexApplication application) {
assert verifyTypesAreInApp(application, methods.keySet());
assert verifyTypesAreInApp(application, classes.keySet());
assert verifyTypesAreInApp(application, syntheticInputs);
return true;
}
private static boolean verifyTypesAreInApp(DexApplication app, Collection<DexType> types) {
for (DexType type : types) {
assert app.programDefinitionFor(type) != null : "Missing synthetic: " + type;
}
return true;
}
}