Reland "Group synthetics based on their ordering."
Bug: b/237413146
This reverts commit 12ab345c790393ae107b893f25aee28968f1e5f0.
Change-Id: I20855ddc11d580d0d43b05be1d44a3e6ecec7dfd
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 527cec3..6065527 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -35,6 +35,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
@@ -53,6 +54,7 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -592,14 +594,12 @@
List<EquivalenceGroup<T>> groups =
groupEquivalent(appView, members, intermediate, classToFeatureSplitMap);
for (EquivalenceGroup<T> group : groups) {
- // If the group already has a representative, then this representative is pinned.
- // Otherwise, we select a deterministic representative.
- if (group.hasRepresentative()) {
+ // If the group has a pinned representative don't construct an external type.
+ if (group.isPinned(appView)) {
EquivalenceGroup<T> previous =
equivalences.put(group.getRepresentative().getHolder().getType(), group);
assert previous == null;
} else {
- group.selectDeterministicRepresentative();
groupsPerPrefix
.computeIfAbsent(
group.getRepresentative().getPrefixForExternalSyntheticType(),
@@ -666,52 +666,94 @@
List<T> potentialEquivalence,
boolean intermediate,
ClassToFeatureSplitMap classToFeatureSplitMap) {
- List<EquivalenceGroup<T>> groups = new ArrayList<>();
- // Each other member is in a shared group if it is actually equivalent to the first member.
- for (T synthetic : potentialEquivalence) {
- if (groups.size() > GROUP_COUNT_THRESHOLD) {
- return ListUtils.map(
- potentialEquivalence, m -> new EquivalenceGroup<>(m, isPinned(appView, m)));
+ // Fast path the singleton groups.
+ if (potentialEquivalence.size() == 1) {
+ return ImmutableList.of(EquivalenceGroup.singleton(potentialEquivalence.get(0)));
+ }
+ assert !potentialEquivalence.isEmpty();
+
+ // Sort the potential members and split them up into potential groups of members that are
+ // actually equal.
+ GraphLens graphLens = appView.graphLens();
+ boolean includeContext =
+ intermediate || appView.options().getStartupOptions().isStartupInstrumentationEnabled();
+ List<T> sortedPotentialMembers =
+ ListUtils.sort(
+ potentialEquivalence,
+ (a, b) -> a.compareTo(b, includeContext, graphLens, classToFeatureSplitMap));
+ List<List<T>> potentialGroups = new ArrayList<>();
+ {
+ List<T> currentGroup = new ArrayList<>();
+ T currentRepresentative = sortedPotentialMembers.get(0);
+ currentGroup.add(currentRepresentative);
+ for (int i = 1; i < sortedPotentialMembers.size(); i++) {
+ T member = sortedPotentialMembers.get(i);
+ if (!currentRepresentative.isEquivalentTo(
+ member, includeContext, graphLens, classToFeatureSplitMap)) {
+ potentialGroups.add(currentGroup);
+ currentGroup = new ArrayList<>();
+ currentRepresentative = member;
+ }
+ currentGroup.add(member);
}
- boolean mustBeRepresentative = isPinned(appView, synthetic);
- EquivalenceGroup<T> equivalenceGroup = null;
- for (EquivalenceGroup<T> group : groups) {
- boolean includeContext =
- intermediate || appView.options().getStartupOptions().isStartupInstrumentationEnabled();
- if (synthetic.isEquivalentTo(
- group.hasRepresentative()
- ? group.getRepresentative()
- : group.getFirstNonRepresentativeMember(),
- includeContext,
- appView.graphLens(),
- classToFeatureSplitMap)) {
- if (mustBeRepresentative && group.hasRepresentative()) {
- // Check if the current synthetic is smaller than the group's representative. If so,
- // then replace the representative, to ensure deterministic groups, and create a new
- // singleton group containing the old representative. Otherwise, just add a singleton
- // group containing the new synthetic.
- T representative = group.getRepresentative();
- if (representative
- .toReference()
- .getReference()
- .compareTo(synthetic.toReference().getReference())
- > 0) {
- group.replaceAndRemoveRepresentative(synthetic);
- synthetic = representative;
+ potentialGroups.add(currentGroup);
+ }
+
+ // Compute the actual groups by picking the group representatives. In cases of pinned members
+ // this may need to split out representatives into their own singleton groups.
+ List<EquivalenceGroup<T>> actualGroups = new ArrayList<>();
+ for (List<T> potentialGroup : potentialGroups) {
+ assert !potentialGroup.isEmpty();
+ if (potentialGroup.size() == 1) {
+ actualGroups.add(EquivalenceGroup.singleton(potentialGroup.get(0)));
+ continue;
+ }
+ List<T> forcedRepresentatives = null;
+ if (appView.enableWholeProgramOptimizations()) {
+ Iterator<T> it = potentialGroup.iterator();
+ while (it.hasNext()) {
+ T member = it.next();
+ boolean mustBeRepresentative = isPinned(appView, member);
+ if (mustBeRepresentative) {
+ if (forcedRepresentatives == null) {
+ forcedRepresentatives = new ArrayList<>();
}
- } else {
- equivalenceGroup = group;
+ forcedRepresentatives.add(member);
+ it.remove();
}
- break;
}
}
- if (equivalenceGroup != null) {
- equivalenceGroup.add(synthetic, mustBeRepresentative);
+ if (forcedRepresentatives != null) {
+ assert appView.enableWholeProgramOptimizations();
+ T representative =
+ findSmallestMember(
+ forcedRepresentatives,
+ other -> actualGroups.add(EquivalenceGroup.pinnedSingleton(other)));
+ actualGroups.add(EquivalenceGroup.pinnedGroup(representative, potentialGroup));
} else {
- groups.add(new EquivalenceGroup<>(synthetic, mustBeRepresentative));
+ List<T> members = new ArrayList<>(potentialGroup.size() - 1);
+ T representative = findSmallestMember(potentialGroup, members::add);
+ actualGroups.add(EquivalenceGroup.unpinnedGroup(representative, members));
}
}
- return groups;
+ return actualGroups;
+ }
+
+ private static <T extends SyntheticDefinition<?, T, ?>> T findSmallestMember(
+ List<T> members, Consumer<T> notSmallestCallback) {
+ assert !members.isEmpty();
+ T smallest = members.get(0);
+ for (int i = 1; i < members.size(); i++) {
+ T member = members.get(i);
+ if (member.toReference().getReference().compareTo(smallest.toReference().getReference())
+ < 0) {
+ notSmallestCallback.accept(smallest);
+ smallest = member;
+ } else {
+ notSmallestCallback.accept(member);
+ }
+ }
+ return smallest;
}
/**
@@ -846,28 +888,47 @@
private static class EquivalenceGroup<T extends SyntheticDefinition<?, T, ?>> {
// The members of the equivalence group, *excluding* the representative.
- private List<T> members = new ArrayList<>();
- private T representative;
+ private final List<T> members;
+ private final T representative;
+ private final OptionalBool pinned;
- EquivalenceGroup(T member, boolean isRepresentative) {
- add(member, isRepresentative);
+ static <T extends SyntheticDefinition<?, T, ?>> EquivalenceGroup<T> singleton(T member) {
+ return new EquivalenceGroup<>(member, ImmutableList.of(), OptionalBool.UNKNOWN);
}
- void add(T member, boolean isRepresentative) {
- if (isRepresentative) {
- assert !hasRepresentative();
- representative = member;
- } else {
- members.add(member);
+ static <T extends SyntheticDefinition<?, T, ?>> EquivalenceGroup<T> unpinnedGroup(
+ T representative, List<T> members) {
+ return new EquivalenceGroup<>(
+ representative, Collections.unmodifiableList(members), OptionalBool.FALSE);
+ }
+
+ static <T extends SyntheticDefinition<?, T, ?>> EquivalenceGroup<T> pinnedSingleton(T member) {
+ return new EquivalenceGroup<>(member, ImmutableList.of(), OptionalBool.TRUE);
+ }
+
+ static <T extends SyntheticDefinition<?, T, ?>> EquivalenceGroup<T> pinnedGroup(
+ T representative, List<T> members) {
+ return new EquivalenceGroup<>(
+ representative, Collections.unmodifiableList(members), OptionalBool.TRUE);
+ }
+
+ private EquivalenceGroup(T representative, List<T> members, OptionalBool pinned) {
+ assert representative != null;
+ assert members != null;
+ assert pinned != null;
+ this.members = members;
+ this.representative = representative;
+ this.pinned = pinned;
+ }
+
+ public boolean isPinned(AppView<?> appView) {
+ if (pinned.isTrue()) {
+ return true;
}
- }
-
- int compareToIncludingContext(
- EquivalenceGroup<T> other,
- GraphLens graphLens,
- ClassToFeatureSplitMap classToFeatureSplitMap) {
- return getRepresentative()
- .compareTo(other.getRepresentative(), true, graphLens, classToFeatureSplitMap);
+ if (pinned.isFalse()) {
+ return false;
+ }
+ return SyntheticFinalization.isPinned(appView, representative);
}
public void forEach(Consumer<T> consumer) {
@@ -879,64 +940,23 @@
members.forEach(consumer);
}
- T getFirstNonRepresentativeMember() {
- assert !members.isEmpty();
- return members.get(0);
- }
-
T getRepresentative() {
- assert hasRepresentative();
return representative;
}
- boolean hasRepresentative() {
- return representative != null;
- }
-
boolean isDerivedFromMainDexList(MainDexInfo mainDexInfo) {
return getRepresentative().getContext().isDerivedFromMainDexList(mainDexInfo)
|| Iterables.any(
members, member -> member.getContext().isDerivedFromMainDexList(mainDexInfo));
}
- void replaceAndRemoveRepresentative(T representative) {
- assert hasRepresentative();
- this.representative = representative;
- }
-
- void selectDeterministicRepresentative() {
- // Pick a deterministic member as representative.
- assert !hasRepresentative();
- int representativeIndex = 0;
- for (int i = 1; i < members.size(); i++) {
- T next = members.get(i);
- T representative = members.get(representativeIndex);
- if (next.toReference().getReference().compareTo(representative.toReference().getReference())
- < 0) {
- representativeIndex = i;
- }
- }
- T representative = members.get(representativeIndex);
- members.set(representativeIndex, ListUtils.last(members));
- ListUtils.removeLast(members);
- setRepresentative(representative);
- }
-
- void setRepresentative(T representative) {
- assert !hasRepresentative();
- this.representative = representative;
- }
-
@Override
public String toString() {
- if (hasRepresentative()) {
- return "EquivalenceGroup{ size = "
- + (members.size() + 1)
- + ", repr = "
- + getRepresentative()
- + " }";
- }
- return "EquivalenceGroup{ size = " + members.size() + " }";
+ return "EquivalenceGroup{ size = "
+ + (members.size() + 1)
+ + ", repr = "
+ + getRepresentative()
+ + " }";
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 9c6e2a7..406d482 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -268,12 +268,26 @@
void accept(T item, int index);
}
+ public static <T> List<T> sort(List<T> items, Comparator<T> comparator) {
+ List<T> sorted = new ArrayList<>(items);
+ sorted.sort(comparator);
+ return sorted;
+ }
+
public static <T> void destructiveSort(List<T> items, Comparator<T> comparator) {
items.sort(comparator);
}
// Utility to add a slow verification of a comparator as part of sorting. Note that this
// should not generally be used in asserts unless the quadratic behavior can be tolerated.
+ public static <T> List<T> sortAndVerify(List<T> items, Comparator<T> comparator) {
+ List<T> sorted = sort(items, comparator);
+ assert verifyComparatorOnSortedList(sorted, comparator);
+ return sorted;
+ }
+
+ // Utility to add a slow verification of a comparator as part of sorting. Note that this
+ // should not generally be used in asserts unless the quadratic behavior can be tolerated.
public static <T> void destructiveSortAndVerify(List<T> items, Comparator<T> comparator) {
destructiveSort(items, comparator);
assert verifyComparatorOnSortedList(items, comparator);