Allow sharing within a shared context in intermediate mode.
This also relands "Assert that equivalences groups are ordered."
Bug: 172410912
Bug: 172194101
Change-Id: I1875b93b7000e0b0aca4793f2edf90e37cc84fbf
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 1d037cc..30d232e 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -26,7 +26,7 @@
abstract DexProgramClass getHolder();
- abstract HashCode computeHash();
+ abstract HashCode computeHash(boolean intermediate);
- abstract boolean isEquivalentTo(SyntheticDefinition other);
+ abstract boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate);
}
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 bdb76ed..00af9d5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -98,13 +97,10 @@
lookupSyntheticMethodDefinitions(application);
Collection<List<SyntheticMethodDefinition>> potentialEquivalences =
- // Don't share synthetics in intermediate mode builds.
- options.intermediate
- ? ListUtils.map(methodDefinitions, Collections::singletonList)
- : computePotentialEquivalences(methodDefinitions);
+ computePotentialEquivalences(methodDefinitions, options.intermediate);
Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> equivalences =
- computeActualEquivalences(potentialEquivalences, options.itemFactory);
+ computeActualEquivalences(potentialEquivalences, options.intermediate, options.itemFactory);
Builder lensBuilder = NestedGraphLens.builder();
List<DexProgramClass> newProgramClasses = new ArrayList<>();
@@ -332,11 +328,11 @@
private static <T extends SyntheticDefinition & Comparable<T>>
Map<DexType, EquivalenceGroup<T>> computeActualEquivalences(
- Collection<List<T>> potentialEquivalences, DexItemFactory factory) {
+ Collection<List<T>> potentialEquivalences, boolean intermediate, DexItemFactory factory) {
Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
potentialEquivalences.forEach(
members -> {
- List<List<T>> groups = groupEquivalent(members);
+ List<List<T>> groups = groupEquivalent(members, intermediate);
for (List<T> group : groups) {
T representative = findDeterministicRepresentative(group);
// The representative is required to be the first element of the group.
@@ -356,6 +352,9 @@
groups.sort(EquivalenceGroup::compareTo);
for (int i = 0; i < groups.size(); i++) {
EquivalenceGroup<T> group = groups.get(i);
+ // Two equivalence groups in same context type must be distinct otherwise the assignment
+ // of the synthetic name will be non-deterministic between the two.
+ assert i == 0 || checkGroupsAreDistict(groups.get(i - 1), group);
DexType representativeType = createExternalType(context, i, factory);
equivalences.put(representativeType, group);
}
@@ -364,13 +363,13 @@
}
private static <T extends SyntheticDefinition & Comparable<T>> List<List<T>> groupEquivalent(
- List<T> potentialEquivalence) {
+ List<T> potentialEquivalence, boolean intermediate) {
List<List<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) {
boolean requireNewGroup = true;
for (List<T> group : groups) {
- if (synthetic.isEquivalentTo(group.get(0))) {
+ if (synthetic.isEquivalentTo(group.get(0), intermediate)) {
requireNewGroup = false;
group.add(synthetic);
break;
@@ -385,6 +384,12 @@
return groups;
}
+ private static <T extends SyntheticDefinition & Comparable<T>> boolean checkGroupsAreDistict(
+ EquivalenceGroup<T> g1, EquivalenceGroup<T> g2) {
+ assert g1.compareTo(g2) != 0;
+ return true;
+ }
+
private static <T extends SyntheticDefinition & Comparable<T>> T findDeterministicRepresentative(
List<T> members) {
// Pick a deterministic member as representative.
@@ -408,10 +413,10 @@
}
private static <T extends SyntheticDefinition> Collection<List<T>> computePotentialEquivalences(
- List<T> definitions) {
+ List<T> definitions, boolean intermediate) {
Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
for (T definition : definitions) {
- HashCode hash = definition.computeHash();
+ HashCode hash = definition.computeHash(intermediate);
equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
}
return equivalences.values();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
index ec7794d..8cddcfc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -41,17 +41,29 @@
}
@Override
- HashCode computeHash() {
+ HashCode computeHash(boolean intermediate) {
Hasher hasher = Hashing.sha256().newHasher();
+ if (intermediate) {
+ // If in intermediate mode, include the context type as sharing is restricted to within a
+ // single context.
+ hasher.putInt(getContext().getSynthesizingContextType().hashCode());
+ }
method.getDefinition().hashSyntheticContent(hasher);
return hasher.hash();
}
@Override
- boolean isEquivalentTo(SyntheticDefinition other) {
+ boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate) {
if (!(other instanceof SyntheticMethodDefinition)) {
return false;
}
+ if (intermediate
+ && getContext().getSynthesizingContextType()
+ != other.getContext().getSynthesizingContextType()) {
+ // If in intermediate mode, only synthetics within the same context should be considered
+ // equal.
+ return false;
+ }
SyntheticMethodDefinition o = (SyntheticMethodDefinition) other;
return method.getDefinition().isSyntheticContentEqual(o.method.getDefinition());
}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
index 4a2e645..45f8957 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -147,8 +147,9 @@
.writeToZip();
GenerateMainDexListRunResult mainDexListFromDex =
traceMainDex(Collections.emptyList(), Collections.singleton(out));
- // Compiling in intermediate will not share the synthetics so there is one per call site.
- assertEquals(MAIN_DEX_LIST_CLASSES.size() + 6, mainDexListFromDex.getMainDexList().size());
+ // Compiling in intermediate will share the synthetics within the context types so there is one
+ // synthetic class per backport in User2: Character.compare and Integer.compare.
+ assertEquals(MAIN_DEX_LIST_CLASSES.size() + 2, mainDexListFromDex.getMainDexList().size());
}
@Test