Enable interface merging in second round of class merging
Bug: 173990042
Change-Id: Iaa9901fb499e795cb10eeb0e8c832fd2899649af
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index bf21997..4ef90be 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -28,7 +28,6 @@
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
import com.android.tools.r8.ir.optimize.library.LibraryMemberOptimizer;
import com.android.tools.r8.ir.optimize.library.LibraryMethodSideEffectModelCollection;
-import com.android.tools.r8.optimize.MemberRebindingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.LibraryModeledPredicate;
@@ -688,13 +687,26 @@
}
// Insert a member rebinding lens above the first unapplied lens.
- MemberRebindingLens appliedMemberRebindingLens =
- firstUnappliedLens.findPrevious(GraphLens::isMemberRebindingLens);
- GraphLens newMemberRebindingLens =
- appliedMemberRebindingLens != null
- ? appliedMemberRebindingLens.toRewrittenFieldRebindingLens(
- appView.dexItemFactory(), appliedLens)
- : GraphLens.getIdentityLens();
+ // TODO(b/182129249): Once the member rebinding phase has been removed, the MemberRebindingLens
+ // should be removed and all uses of FieldRebindingIdentityLens should be replaced by
+ // MemberRebindingIdentityLens.
+ NonIdentityGraphLens appliedMemberRebindingLens =
+ firstUnappliedLens.findPrevious(
+ previous ->
+ previous.isMemberRebindingLens() || previous.isMemberRebindingIdentityLens());
+ GraphLens newMemberRebindingLens;
+ if (appliedMemberRebindingLens != null) {
+ newMemberRebindingLens =
+ appliedMemberRebindingLens.isMemberRebindingLens()
+ ? appliedMemberRebindingLens
+ .asMemberRebindingLens()
+ .toRewrittenFieldRebindingLens(appView, appliedLens)
+ : appliedMemberRebindingLens
+ .asMemberRebindingIdentityLens()
+ .toRewrittenMemberRebindingIdentityLens(appView, appliedLens);
+ } else {
+ newMemberRebindingLens = GraphLens.getIdentityLens();
+ }
firstUnappliedLens.withAlternativeParentLens(
newMemberRebindingLens,
diff --git a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
index 1c7bf88..685188e 100644
--- a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
@@ -4,17 +4,19 @@
package com.android.tools.r8.graph;
+import java.util.function.Function;
+
public class BottomUpClassHierarchyTraversal<T extends DexClass>
extends ClassHierarchyTraversal<T, BottomUpClassHierarchyTraversal<T>> {
- private final SubtypingInfo subtypingInfo;
+ private final Function<DexType, Iterable<DexType>> immediateSubtypesProvider;
private BottomUpClassHierarchyTraversal(
AppView<? extends AppInfoWithClassHierarchy> appView,
- SubtypingInfo subtypingInfo,
+ Function<DexType, Iterable<DexType>> immediateSubtypesProvider,
Scope scope) {
super(appView, scope);
- this.subtypingInfo = subtypingInfo;
+ this.immediateSubtypesProvider = immediateSubtypesProvider;
}
/**
@@ -23,7 +25,8 @@
*/
public static BottomUpClassHierarchyTraversal<DexClass> forAllClasses(
AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
- return new BottomUpClassHierarchyTraversal<>(appView, subtypingInfo, Scope.ALL_CLASSES);
+ return new BottomUpClassHierarchyTraversal<>(
+ appView, subtypingInfo::allImmediateSubtypes, Scope.ALL_CLASSES);
}
/**
@@ -32,8 +35,18 @@
*/
public static BottomUpClassHierarchyTraversal<DexProgramClass> forProgramClasses(
AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+ return forProgramClasses(appView, subtypingInfo::allImmediateSubtypes);
+ }
+
+ /**
+ * Returns a visitor that can be used to visit all the program classes that are reachable from a
+ * given set of sources.
+ */
+ public static BottomUpClassHierarchyTraversal<DexProgramClass> forProgramClasses(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Function<DexType, Iterable<DexType>> immediateSubtypesProvider) {
return new BottomUpClassHierarchyTraversal<>(
- appView, subtypingInfo, Scope.ONLY_PROGRAM_CLASSES);
+ appView, immediateSubtypesProvider, Scope.ONLY_PROGRAM_CLASSES);
}
@Override
@@ -57,7 +70,7 @@
worklist.addFirst(clazzWithTypeT);
// Add subtypes to worklist.
- for (DexType subtype : subtypingInfo.allImmediateSubtypes(clazz.type)) {
+ for (DexType subtype : immediateSubtypesProvider.apply(clazz.getType())) {
DexClass definition = appView.definitionFor(subtype);
if (definition != null) {
if (scope != Scope.ONLY_PROGRAM_CLASSES || definition.isProgramClass()) {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index ff2fd57..8abd2ec 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
+import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
+import com.android.tools.r8.optimize.MemberRebindingLens;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.IterableUtils;
@@ -457,6 +459,18 @@
return false;
}
+ public MemberRebindingLens asMemberRebindingLens() {
+ return null;
+ }
+
+ public boolean isMemberRebindingIdentityLens() {
+ return false;
+ }
+
+ public MemberRebindingIdentityLens asMemberRebindingIdentityLens() {
+ return null;
+ }
+
public abstract boolean isNonIdentityLens();
public NonIdentityGraphLens asNonIdentityLens() {
@@ -671,7 +685,8 @@
}
@SuppressWarnings("unchecked")
- public final <T extends GraphLens> T findPrevious(Predicate<NonIdentityGraphLens> predicate) {
+ public final <T extends NonIdentityGraphLens> T findPrevious(
+ Predicate<NonIdentityGraphLens> predicate) {
GraphLens current = getPrevious();
while (current.isNonIdentityLens()) {
NonIdentityGraphLens nonIdentityGraphLens = current.asNonIdentityLens();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 2b65950..4b02558 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -270,15 +270,14 @@
}
private void mergeInterfaces() {
- DexTypeList previousInterfaces = group.getTarget().getInterfaces();
- Set<DexType> interfaces = Sets.newLinkedHashSet(previousInterfaces);
+ Set<DexType> interfaces = Sets.newLinkedHashSet();
if (group.isInterfaceGroup()) {
// Add all implemented interfaces from the merge group to the target class, ignoring
// implemented interfaces that are part of the merge group.
Set<DexType> groupTypes =
SetUtils.newImmutableSet(
builder -> group.forEach(clazz -> builder.accept(clazz.getType())));
- group.forEachSource(
+ group.forEach(
clazz -> {
for (DexType itf : clazz.getInterfaces()) {
if (!groupTypes.contains(itf)) {
@@ -288,7 +287,7 @@
});
} else {
// Add all implemented interfaces from the merge group to the target class.
- group.forEachSource(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
+ group.forEach(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
}
group.getTarget().setInterfaces(DexTypeList.create(interfaces));
}
@@ -355,7 +354,8 @@
target = current;
}
}
- group.setTarget(appView.testing().horizontalClassMergingTarget.apply(candidates, target));
+ group.setTarget(
+ appView.testing().horizontalClassMergingTarget.apply(appView, candidates, target));
}
private ClassInitializerSynthesizedCode createClassInitializerMerger() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index ebedb0b..c073a34 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -134,6 +134,10 @@
return classes.isEmpty();
}
+ public boolean isClassGroup() {
+ return !isInterfaceGroup();
+ }
+
public boolean isInterfaceGroup() {
assert !isEmpty();
assert IterableUtils.allIdentical(getClasses(), DexClass::isInterface);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
index b091242..a2362bd 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
@@ -22,11 +22,13 @@
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodCollisions.InterfaceInfo;
+import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
@@ -34,6 +36,7 @@
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
/**
* This policy prevents that interface merging changes semantics of invoke-interface/invoke-virtual
@@ -153,7 +156,7 @@
Map<DexType, Map<DexMethodSignature, Set<DexMethod>>>
defaultMethodsInheritedBySubclassesPerClass =
computeDefaultMethodsInheritedBySubclassesPerProgramClass(
- classesOfInterest, inheritedDefaultMethodsPerClass, subtypingInfo);
+ classesOfInterest, inheritedDefaultMethodsPerClass, groups, subtypingInfo);
// Store the computed information for each interface that is subject to merging.
Map<DexType, InterfaceInfo> infos = new IdentityHashMap<>();
@@ -270,7 +273,16 @@
computeDefaultMethodsInheritedBySubclassesPerProgramClass(
Collection<DexProgramClass> classesOfInterest,
Map<DexType, Map<DexMethodSignature, Set<DexMethod>>> inheritedDefaultMethodsPerClass,
+ Collection<MergeGroup> groups,
SubtypingInfo subtypingInfo) {
+ // Build a mapping from class types to their merge group.
+ Map<DexType, Iterable<DexProgramClass>> classGroupsByType =
+ MapUtils.newIdentityHashMap(
+ builder ->
+ Iterables.filter(groups, MergeGroup::isClassGroup)
+ .forEach(
+ group -> group.forEach(clazz -> builder.accept(clazz.getType(), group))));
+
// Copy the map from classes to their inherited default methods.
Map<DexType, Map<DexMethodSignature, Set<DexMethod>>>
defaultMethodsInheritedBySubclassesPerClass =
@@ -279,7 +291,28 @@
new HashMap<>(),
outerValue ->
MapUtils.clone(outerValue, new HashMap<>(), SetUtils::newIdentityHashSet));
- BottomUpClassHierarchyTraversal.forProgramClasses(appView, subtypingInfo)
+
+ // Propagate data upwards. If classes A and B are in a merge group, we need to push the state
+ // for A to all of B's supertypes, and the state for B to all of A's supertypes.
+ //
+ // Therefore, it is important that we don't process any of A's supertypes until B has been
+ // processed, since that would lead to inadequate upwards propagation. To achieve this, we
+ // simulate that both A and B are subtypes of A's and B's supertypes.
+ Function<DexType, Iterable<DexType>> immediateSubtypesProvider =
+ type -> {
+ Set<DexType> immediateSubtypesAfterClassMerging = Sets.newIdentityHashSet();
+ for (DexType immediateSubtype : subtypingInfo.allImmediateSubtypes(type)) {
+ Iterable<DexProgramClass> group = classGroupsByType.get(immediateSubtype);
+ if (group != null) {
+ group.forEach(member -> immediateSubtypesAfterClassMerging.add(member.getType()));
+ } else {
+ immediateSubtypesAfterClassMerging.add(immediateSubtype);
+ }
+ }
+ return immediateSubtypesAfterClassMerging;
+ };
+
+ BottomUpClassHierarchyTraversal.forProgramClasses(appView, immediateSubtypesProvider)
.visit(
classesOfInterest,
clazz -> {
@@ -287,16 +320,20 @@
Map<DexMethodSignature, Set<DexMethod>> defaultMethodsToPropagate =
defaultMethodsInheritedBySubclassesPerClass.getOrDefault(
clazz.getType(), emptyMap());
- for (DexType supertype : clazz.allImmediateSupertypes()) {
- Map<DexMethodSignature, Set<DexMethod>>
- defaultMethodsInheritedBySubclassesForSupertype =
- defaultMethodsInheritedBySubclassesPerClass.computeIfAbsent(
- supertype, ignore -> new HashMap<>());
- defaultMethodsToPropagate.forEach(
- (signature, methods) ->
- defaultMethodsInheritedBySubclassesForSupertype
- .computeIfAbsent(signature, ignore -> Sets.newIdentityHashSet())
- .addAll(methods));
+ Iterable<DexProgramClass> group =
+ classGroupsByType.getOrDefault(clazz.getType(), IterableUtils.singleton(clazz));
+ for (DexProgramClass member : group) {
+ for (DexType supertype : member.allImmediateSupertypes()) {
+ Map<DexMethodSignature, Set<DexMethod>>
+ defaultMethodsInheritedBySubclassesForSupertype =
+ defaultMethodsInheritedBySubclassesPerClass.computeIfAbsent(
+ supertype, ignore -> new HashMap<>());
+ defaultMethodsToPropagate.forEach(
+ (signature, methods) ->
+ defaultMethodsInheritedBySubclassesForSupertype
+ .computeIfAbsent(signature, ignore -> Sets.newIdentityHashSet())
+ .addAll(methods));
+ }
}
});
defaultMethodsInheritedBySubclassesPerClass
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
index d9e6cd1..fc457f7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
@@ -8,25 +8,26 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
-import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterators;
-import com.google.common.collect.Sets;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.IdentityHashMap;
-import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Consumer;
+import java.util.function.Function;
/**
* This policy ensures that we do not create cycles in the class hierarchy as a result of interface
@@ -53,11 +54,15 @@
* interface J extends IK, ... {}
* </pre>
*/
-public class OnlyDirectlyConnectedOrUnrelatedInterfaces extends MultiClassPolicy {
+public class OnlyDirectlyConnectedOrUnrelatedInterfaces
+ extends MultiClassPolicyWithPreprocessing<SubtypingInfo> {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final Mode mode;
+ // The interface merge groups that this policy has committed to so far.
+ private final Map<DexProgramClass, MergeGroup> committed = new IdentityHashMap<>();
+
public OnlyDirectlyConnectedOrUnrelatedInterfaces(
AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
this.appView = appView;
@@ -65,116 +70,84 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<MergeGroup> apply(MergeGroup group, SubtypingInfo subtypingInfo) {
if (!group.isInterfaceGroup()) {
return ImmutableList.of(group);
}
- Set<DexProgramClass> classes = new LinkedHashSet<>(group.getClasses());
- Map<DexProgramClass, Set<DexProgramClass>> ineligibleForMerging =
- computeIneligibleForMergingGraph(classes);
- if (ineligibleForMerging.isEmpty()) {
- return ImmutableList.of(group);
+ List<MergeGroupWithInfo> newGroupsWithInfo = new ArrayList<>();
+ for (DexProgramClass clazz : group) {
+ Set<DexProgramClass> superInterfaces = computeSuperInterfaces(clazz);
+ Set<DexProgramClass> subInterfaces = computeSubInterfaces(clazz, subtypingInfo);
+
+ MergeGroupWithInfo newGroup = null;
+ for (MergeGroupWithInfo candidateGroup : newGroupsWithInfo) {
+ // Check if adding `clazz` to `candidateGroup` would introduce a super interface that is
+ // also a sub interface. In that case we must abort since merging would lead to a cycle in
+ // the class hierarchy.
+ if (candidateGroup.isSafeToAddSubAndSuperInterfaces(
+ clazz, subInterfaces, superInterfaces)) {
+ newGroup = candidateGroup;
+ break;
+ }
+ }
+
+ if (newGroup != null) {
+ newGroup.add(clazz, superInterfaces, subInterfaces);
+ } else {
+ newGroupsWithInfo.add(new MergeGroupWithInfo(clazz, superInterfaces, subInterfaces));
+ }
}
- // Extract sub-merge groups from the graph in such a way that all pairs of interfaces in each
- // merge group are not connected by an edge in the graph.
List<MergeGroup> newGroups = new LinkedList<>();
- while (!classes.isEmpty()) {
- Iterator<DexProgramClass> iterator = classes.iterator();
- MergeGroup newGroup = new MergeGroup(iterator.next());
- Iterators.addAll(
- newGroup,
- Iterators.filter(
- iterator,
- candidate -> !isConnectedToGroup(candidate, newGroup, ineligibleForMerging)));
+ for (MergeGroupWithInfo newGroupWithInfo : newGroupsWithInfo) {
+ MergeGroup newGroup = newGroupWithInfo.getGroup();
if (!newGroup.isTrivial()) {
newGroups.add(newGroup);
+ newGroup.forEach(clazz -> committed.put(clazz, newGroup));
}
- classes.removeAll(newGroup.getClasses());
}
return newGroups;
}
- /**
- * Computes an undirected graph, where the nodes are the interfaces from the merge group, and an
- * edge I <-> J represents that I and J are not eligible for merging.
- *
- * <p>We will insert an edge I <-> J, if interface I inherits from interface J, and the path from
- * I to J in the class hierarchy includes an interface K that is outside the merge group. Note
- * that if I extends J directly we will not insert an edge I <-> J (unless there are multiple
- * paths in the class hierarchy from I to J, and one of the paths goes through an interface
- * outside the merge group).
- */
- private Map<DexProgramClass, Set<DexProgramClass>> computeIneligibleForMergingGraph(
- Set<DexProgramClass> classes) {
- Map<DexProgramClass, Set<DexProgramClass>> ineligibleForMerging = new IdentityHashMap<>();
- for (DexProgramClass clazz : classes) {
- forEachIndirectlyReachableInterfaceInMergeGroup(
- clazz,
- classes,
- other ->
- ineligibleForMerging
- .computeIfAbsent(clazz, ignore -> Sets.newIdentityHashSet())
- .add(other));
- }
- return ineligibleForMerging;
+ private Set<DexProgramClass> computeSuperInterfaces(DexProgramClass clazz) {
+ return computeTransitiveSubOrSuperInterfaces(clazz, DexClass::getInterfaces);
}
- private void forEachIndirectlyReachableInterfaceInMergeGroup(
- DexProgramClass clazz, Set<DexProgramClass> classes, Consumer<DexProgramClass> consumer) {
- // First find the set of interfaces that can be reached via paths in the class hierarchy from
- // the given interface, without visiting any interfaces outside the merge group.
- WorkList<DexType> workList = WorkList.newIdentityWorkList(clazz.getInterfaces());
- while (workList.hasNext()) {
- DexProgramClass directlyReachableInterface =
- asProgramClassOrNull(appView.definitionFor(workList.next()));
- if (directlyReachableInterface == null) {
- continue;
- }
- // If the implemented interface is a member of the merge group, then include it's interfaces.
- if (classes.contains(directlyReachableInterface)) {
- workList.addIfNotSeen(directlyReachableInterface.getInterfaces());
- }
- }
-
- // Initialize a new worklist with the first layer of indirectly reachable interface types.
- Set<DexType> directlyReachableInterfaceTypes = workList.getSeenSet();
- workList = WorkList.newIdentityWorkList();
- for (DexType directlyReachableInterfaceType : directlyReachableInterfaceTypes) {
- DexProgramClass directlyReachableInterface =
- asProgramClassOrNull(appView.definitionFor(directlyReachableInterfaceType));
- if (directlyReachableInterface != null) {
- workList.addIfNotSeen(directlyReachableInterface.getInterfaces());
- }
- }
-
- // Report all interfaces from the merge group that are reachable in the class hierarchy from the
- // worklist.
- while (workList.hasNext()) {
- DexProgramClass indirectlyReachableInterface =
- asProgramClassOrNull(appView.definitionFor(workList.next()));
- if (indirectlyReachableInterface == null) {
- continue;
- }
- if (classes.contains(indirectlyReachableInterface)) {
- consumer.accept(indirectlyReachableInterface);
- }
- workList.addIfNotSeen(indirectlyReachableInterface.getInterfaces());
- }
+ private Set<DexProgramClass> computeSubInterfaces(
+ DexProgramClass clazz, SubtypingInfo subtypingInfo) {
+ return computeTransitiveSubOrSuperInterfaces(
+ clazz, definition -> subtypingInfo.allImmediateExtendsSubtypes(definition.getType()));
}
- private boolean isConnectedToGroup(
+ private Set<DexProgramClass> computeTransitiveSubOrSuperInterfaces(
DexProgramClass clazz,
- MergeGroup group,
- Map<DexProgramClass, Set<DexProgramClass>> ineligibleForMerging) {
- for (DexProgramClass member : group) {
- if (ineligibleForMerging.getOrDefault(clazz, Collections.emptySet()).contains(member)
- || ineligibleForMerging.getOrDefault(member, Collections.emptySet()).contains(clazz)) {
- return true;
+ Function<DexProgramClass, Iterable<DexType>> immediateSubOrSuperInterfacesProvider) {
+ WorkList<DexProgramClass> workList = WorkList.newWorkList(new LinkedHashSet<>());
+ // Intentionally not marking `clazz` as seen, since we only want the strict sub/super types.
+ workList.addIgnoringSeenSet(clazz);
+ while (workList.hasNext()) {
+ DexProgramClass interfaceDefinition = workList.next();
+ MergeGroup group = committed.get(interfaceDefinition);
+ if (group != null) {
+ workList.addIfNotSeen(group);
+ }
+ for (DexType immediateSubOrSuperInterfaceType :
+ immediateSubOrSuperInterfacesProvider.apply(interfaceDefinition)) {
+ DexProgramClass immediateSubOrSuperInterface =
+ asProgramClassOrNull(appView.definitionFor(immediateSubOrSuperInterfaceType));
+ if (immediateSubOrSuperInterface != null) {
+ workList.addIfNotSeen(immediateSubOrSuperInterface);
+ }
}
}
- return false;
+ assert !workList.isSeen(clazz);
+ return workList.getMutableSeenSet();
+ }
+
+ @Override
+ public void clear() {
+ committed.clear();
}
@Override
@@ -183,7 +156,80 @@
}
@Override
+ public SubtypingInfo preprocess(Collection<MergeGroup> groups) {
+ return new SubtypingInfo(appView);
+ }
+
+ @Override
public boolean shouldSkipPolicy() {
return !appView.options().horizontalClassMergerOptions().isInterfaceMergingEnabled(mode);
}
+
+ static class MergeGroupWithInfo {
+
+ private final MergeGroup group;
+ private final Set<DexProgramClass> members;
+ private final Set<DexProgramClass> superInterfaces;
+ private final Set<DexProgramClass> subInterfaces;
+
+ MergeGroupWithInfo(
+ DexProgramClass clazz,
+ Set<DexProgramClass> superInterfaces,
+ Set<DexProgramClass> subInterfaces) {
+ this.group = new MergeGroup(clazz);
+ this.members = SetUtils.newIdentityHashSet(clazz);
+ this.superInterfaces = superInterfaces;
+ this.subInterfaces = subInterfaces;
+ }
+
+ void add(
+ DexProgramClass clazz,
+ Set<DexProgramClass> newSuperInterfaces,
+ Set<DexProgramClass> newSubInterfaces) {
+ group.add(clazz);
+ members.add(clazz);
+ Iterables.addAll(
+ superInterfaces,
+ Iterables.filter(
+ newSuperInterfaces, superInterface -> !members.contains(superInterface)));
+ superInterfaces.remove(clazz);
+ Iterables.addAll(
+ subInterfaces,
+ Iterables.filter(newSubInterfaces, subInterface -> !members.contains(subInterface)));
+ subInterfaces.remove(clazz);
+ }
+
+ MergeGroup getGroup() {
+ return group;
+ }
+
+ boolean isSafeToAddSubAndSuperInterfaces(
+ DexProgramClass clazz,
+ Set<DexProgramClass> newSubInterfaces,
+ Set<DexProgramClass> newSuperInterfaces) {
+ // Check that adding the new sub and super interfaces to the group is safe.
+ for (DexProgramClass newSubInterface : newSubInterfaces) {
+ if (!group.contains(newSubInterface) && superInterfaces.contains(newSubInterface)) {
+ return false;
+ }
+ }
+ for (DexProgramClass newSuperInterface : newSuperInterfaces) {
+ if (!group.contains(newSuperInterface) && subInterfaces.contains(newSuperInterface)) {
+ return false;
+ }
+ }
+ // Check that adding the sub and super interfaces of the group to the current class is safe.
+ for (DexProgramClass subInterface : subInterfaces) {
+ if (subInterface != clazz && newSuperInterfaces.contains(subInterface)) {
+ return false;
+ }
+ }
+ for (DexProgramClass superInterface : superInterfaces) {
+ if (superInterface != clazz && newSubInterfaces.contains(superInterface)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index 3f77dbb..0fdc499 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -41,7 +41,12 @@
}
public static Builder builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
- return new Builder(appView);
+ return builder(appView, appView.graphLens());
+ }
+
+ public static Builder builder(
+ AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens previousLens) {
+ return new Builder(appView, previousLens);
}
@Override
@@ -129,16 +134,55 @@
return getPrevious().isContextFreeForMethods();
}
+ @Override
+ public boolean isMemberRebindingIdentityLens() {
+ return true;
+ }
+
+ @Override
+ public MemberRebindingIdentityLens asMemberRebindingIdentityLens() {
+ return this;
+ }
+
+ public MemberRebindingIdentityLens toRewrittenMemberRebindingIdentityLens(
+ AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ Builder builder = builder(appView, getIdentityLens());
+ nonReboundFieldReferenceToDefinitionMap.forEach(
+ (nonReboundFieldReference, reboundFieldReference) -> {
+ DexField rewrittenReboundFieldReference = lens.lookupField(reboundFieldReference);
+ DexField rewrittenNonReboundFieldReference =
+ rewrittenReboundFieldReference.withHolder(
+ lens.lookupType(nonReboundFieldReference.getHolderType()), dexItemFactory);
+ builder.recordNonReboundFieldAccess(
+ rewrittenNonReboundFieldReference, rewrittenReboundFieldReference);
+ });
+ nonReboundMethodReferenceToDefinitionMap.forEach(
+ (nonReboundMethodReference, reboundMethodReference) -> {
+ DexMethod rewrittenReboundMethodReference =
+ lens.getRenamedMethodSignature(reboundMethodReference);
+ DexMethod rewrittenNonReboundMethodReference =
+ rewrittenReboundMethodReference.withHolder(
+ lens.lookupType(nonReboundMethodReference.getHolderType()), dexItemFactory);
+ builder.recordNonReboundMethodAccess(
+ rewrittenNonReboundMethodReference, rewrittenReboundMethodReference);
+ });
+ return builder.build();
+ }
+
public static class Builder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final GraphLens previousLens;
+
private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap =
new IdentityHashMap<>();
private final Map<DexMethod, DexMethod> nonReboundMethodReferenceToDefinitionMap =
new IdentityHashMap<>();
- private Builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ private Builder(AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens previousLens) {
this.appView = appView;
+ this.previousLens = previousLens;
}
void recordNonReboundFieldAccesses(FieldAccessInfo fieldAccessInfo) {
@@ -152,6 +196,12 @@
nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
}
+ private void recordNonReboundMethodAccess(
+ DexMethod nonReboundMethodReference, DexMethod reboundMethodReference) {
+ nonReboundMethodReferenceToDefinitionMap.put(
+ nonReboundMethodReference, reboundMethodReference);
+ }
+
void recordMethodAccess(DexMethod reference) {
if (reference.getHolderType().isArrayType()) {
return;
@@ -161,7 +211,7 @@
SingleResolutionResult resolutionResult =
appView.appInfo().resolveMethodOn(holder, reference).asSingleResolution();
if (resolutionResult != null && resolutionResult.getResolvedHolder() != holder) {
- nonReboundMethodReferenceToDefinitionMap.put(
+ recordNonReboundMethodAccess(
reference, resolutionResult.getResolvedMethod().getReference());
}
}
@@ -175,7 +225,7 @@
nonReboundFieldReferenceToDefinitionMap,
nonReboundMethodReferenceToDefinitionMap,
appView.dexItemFactory(),
- appView.graphLens());
+ previousLens);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
index 4310236..d202af8 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.graph.NestedGraphLens.mapVirtualInterfaceInvocationTypes;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -47,6 +48,11 @@
}
@Override
+ public MemberRebindingLens asMemberRebindingLens() {
+ return this;
+ }
+
+ @Override
public DexType getOriginalType(DexType type) {
return getPrevious().getOriginalType(type);
}
@@ -131,7 +137,8 @@
}
public FieldRebindingIdentityLens toRewrittenFieldRebindingLens(
- DexItemFactory dexItemFactory, GraphLens lens) {
+ AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
FieldRebindingIdentityLens.Builder builder = FieldRebindingIdentityLens.builder();
nonReboundFieldReferenceToDefinitionMap.forEach(
(nonReboundFieldReference, reboundFieldReference) -> {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 12126a4..73badd3 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -85,7 +85,6 @@
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -1215,7 +1214,7 @@
private boolean enable =
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
- private boolean enableInterfaceMerging = false;
+ private boolean enableInterfaceMergingInInitial = false;
private boolean enableSyntheticMerging = true;
private boolean ignoreRuntimeTypeChecksForTesting = false;
private boolean restrictToSynthetics = false;
@@ -1238,10 +1237,6 @@
this.enable = enable;
}
- public void enableInterfaceMerging() {
- enableInterfaceMerging = true;
- }
-
public int getMaxGroupSize() {
return maxGroupSize;
}
@@ -1265,23 +1260,26 @@
return ignoreRuntimeTypeChecksForTesting;
}
- public boolean isInterfaceMergingEnabled() {
- assert !isInterfaceMergingEnabled(HorizontalClassMerger.Mode.INITIAL);
- return isInterfaceMergingEnabled(HorizontalClassMerger.Mode.FINAL);
- }
-
public boolean isSyntheticMergingEnabled() {
return enableSyntheticMerging;
}
public boolean isInterfaceMergingEnabled(HorizontalClassMerger.Mode mode) {
- return enableInterfaceMerging && mode.isFinal();
+ if (mode.isInitial()) {
+ return enableInterfaceMergingInInitial;
+ }
+ assert mode.isFinal();
+ return true;
}
public boolean isRestrictedToSynthetics() {
return restrictToSynthetics || !isOptimizing() || !isShrinking();
}
+ public void setEnableInterfaceMergingInInitial() {
+ enableInterfaceMergingInInitial = true;
+ }
+
public void setIgnoreRuntimeTypeChecksForTesting() {
ignoreRuntimeTypeChecksForTesting = true;
}
@@ -1351,8 +1349,8 @@
public BiConsumer<DexItemFactory, HorizontallyMergedClasses> horizontallyMergedClassesConsumer =
ConsumerUtils.emptyBiConsumer();
- public BiFunction<Iterable<DexProgramClass>, DexProgramClass, DexProgramClass>
- horizontalClassMergingTarget = (candidates, target) -> target;
+ public TriFunction<AppView<?>, Iterable<DexProgramClass>, DexProgramClass, DexProgramClass>
+ horizontalClassMergingTarget = (appView, candidates, target) -> target;
public BiConsumer<DexItemFactory, EnumDataMap> unboxedEnumsConsumer =
ConsumerUtils.emptyBiConsumer();
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index 63e881e..fc39146 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.utils.StringUtils.BraceType;
+import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -50,6 +51,12 @@
return result;
}
+ public static <K, V> IdentityHashMap<K, V> newIdentityHashMap(BiForEachable<K, V> forEachable) {
+ IdentityHashMap<K, V> map = new IdentityHashMap<>();
+ forEachable.forEach(map::put);
+ return map;
+ }
+
public static <T> void removeIdentityMappings(Map<T, T> map) {
map.entrySet().removeIf(entry -> entry.getKey() == entry.getValue());
}
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index 4279cb7..67e4148 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -48,6 +48,10 @@
return workList;
}
+ public static <T> WorkList<T> newWorkList(Set<T> seen) {
+ return new WorkList<>(seen);
+ }
+
private WorkList(EqualityTest equalityTest) {
this(equalityTest == EqualityTest.HASH ? new HashSet<>() : Sets.newIdentityHashSet());
}
@@ -56,6 +60,10 @@
this.seen = seen;
}
+ public void addIgnoringSeenSet(T item) {
+ workingList.addLast(item);
+ }
+
public void addAllIgnoringSeenSet(Iterable<T> items) {
items.forEach(workingList::addLast);
}
@@ -86,6 +94,10 @@
return !hasNext();
}
+ public boolean isSeen(T item) {
+ return seen.contains(item);
+ }
+
public void markAsSeen(T item) {
seen.add(item);
}
@@ -103,6 +115,10 @@
return Collections.unmodifiableSet(seen);
}
+ public Set<T> getMutableSeenSet() {
+ return seen;
+ }
+
public enum EqualityTest {
HASH,
IDENTITY
diff --git a/src/test/examples/classmerging/NoHorizontalClassMerging.java b/src/test/examples/classmerging/NoHorizontalClassMerging.java
new file mode 100644
index 0000000..3b7a831
--- /dev/null
+++ b/src/test/examples/classmerging/NoHorizontalClassMerging.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, 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 classmerging;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface NoHorizontalClassMerging {}
diff --git a/src/test/examples/classmerging/SimpleInterfaceAccessTest.java b/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
index 6fde90e..804b4ed 100644
--- a/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
+++ b/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
@@ -32,12 +32,14 @@
}
// Should only be merged into OtherSimpleInterfaceImpl if access modifications are allowed.
+ @NoHorizontalClassMerging
public interface SimpleInterface {
void foo();
}
// Should only be merged into OtherSimpleInterfaceImpl if access modifications are allowed.
+ @NoHorizontalClassMerging
public interface OtherSimpleInterface {
void bar();
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 64ce874..46fcb91 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -72,3 +72,4 @@
-neverinline class * {
@classmerging.NeverInline <methods>;
}
+-nohorizontalclassmerging @classmerging.NoHorizontalClassMerging class *
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
index 8a80d6e..6fad862 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
@@ -41,7 +41,7 @@
.addOptionsModification(
options ->
options.testing.horizontalClassMergingTarget =
- (candidates, target) -> candidates.iterator().next())
+ (appView, candidates, target) -> candidates.iterator().next())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("a", "b", "c", "d")
.inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
index bf50ab1..0844722 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
@@ -111,7 +111,7 @@
.addOptionsModification(
options -> {
options.testing.horizontalClassMergingTarget =
- (canditates, target) -> {
+ (appView, canditates, target) -> {
Set<ClassReference> candidateClassReferences =
Streams.stream(canditates)
.map(DexClass::getClassReference)
@@ -164,7 +164,7 @@
.addOptionsModification(
options -> {
options.testing.horizontalClassMergingTarget =
- (canditates, target) -> {
+ (appView, canditates, target) -> {
Set<ClassReference> candidateClassReferences =
Streams.stream(canditates)
.map(DexClass::getClassReference)
@@ -217,7 +217,7 @@
.addOptionsModification(
options -> {
options.testing.horizontalClassMergingTarget =
- (canditates, target) -> {
+ (appView, canditates, target) -> {
Set<ClassReference> candidateClassReferences =
Streams.stream(canditates)
.map(DexClass::getClassReference)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
index 3787201..955ff04 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.classmerging.horizontal.dispatch;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -12,7 +13,6 @@
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
public class OverrideAbstractMethodWithDefaultTest extends HorizontalClassMergingTestBase {
@@ -31,13 +31,14 @@
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("J", "B2")
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(I.class), isPresent());
- assertThat(codeInspector.clazz(J.class), isPresent());
+ assertThat(codeInspector.clazz(J.class), isAbsent());
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B1.class), isPresent());
assertThat(codeInspector.clazz(B2.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
index 580598b..0af1e85 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.classmerging.horizontal.dispatch;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -36,9 +37,11 @@
} else {
inspector
.assertClassesNotMerged(A.class, B.class)
+ .assertIsCompleteMergeGroup(I.class, J.class)
.assertIsCompleteMergeGroup(
SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
- SyntheticItemsTestUtils.syntheticCompanionClass(J.class));
+ SyntheticItemsTestUtils.syntheticCompanionClass(J.class))
+ .assertNoOtherClassesMerged();
}
})
.run(parameters.getRuntime(), Main.class)
@@ -46,7 +49,9 @@
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(I.class), isPresent());
- assertThat(codeInspector.clazz(J.class), isPresent());
+ assertThat(
+ codeInspector.clazz(J.class),
+ onlyIf(parameters.canUseDefaultAndStaticInterfaceMethods(), isPresent()));
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B.class), isPresent());
assertThat(codeInspector.clazz(C.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
index 65ca772..7b5437e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.classmerging.horizontal.dispatch;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -42,9 +43,11 @@
} else {
inspector
.assertClassesNotMerged(A.class, B.class)
+ .assertIsCompleteMergeGroup(I.class, J.class)
.assertIsCompleteMergeGroup(
SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
- SyntheticItemsTestUtils.syntheticCompanionClass(J.class));
+ SyntheticItemsTestUtils.syntheticCompanionClass(J.class))
+ .assertNoOtherClassesMerged();
}
})
.run(parameters.getRuntime(), Main.class)
@@ -52,7 +55,9 @@
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(I.class), isPresent());
- assertThat(codeInspector.clazz(J.class), isPresent());
+ assertThat(
+ codeInspector.clazz(J.class),
+ onlyIf(parameters.canUseDefaultAndStaticInterfaceMethods(), isPresent()));
assertThat(codeInspector.clazz(Parent.class), isPresent());
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java
index 650d804..4d81f1a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java
@@ -44,11 +44,6 @@
// hierarchy.
.addHorizontallyMergedClassesInspector(
HorizontallyMergedClassesInspector::assertNoClassesMerged)
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableNoHorizontalClassMergingAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
index 1c5d915..cc19dd2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -16,8 +15,9 @@
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -25,19 +25,21 @@
@RunWith(Parameterized.class)
public class CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest extends TestBase {
+ private final boolean enableInterfaceMergingInInitial;
private final TestParameters parameters;
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameterized.Parameters(name = "{1}, enableInterfaceMergingInInitial: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
public CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest(
- TestParameters parameters) {
+ boolean enableInterfaceMergingInInitial, TestParameters parameters) {
+ this.enableInterfaceMergingInInitial = enableInterfaceMergingInInitial;
this.parameters = parameters;
}
- // TODO(b/173990042): Disallow merging of A and B in the first round of class merging.
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
@@ -48,23 +50,22 @@
// the default method J.m() to A.
.addHorizontallyMergedClassesInspector(
inspector -> {
+ inspector
+ .assertIsCompleteMergeGroup(A.class, B.class)
+ .assertMergedInto(B.class, A.class);
if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
- inspector
- .assertIsCompleteMergeGroup(A.class, B.class)
- .assertMergedInto(B.class, A.class)
- .assertClassesNotMerged(I.class, J.class, K.class);
+ inspector.assertClassesNotMerged(I.class, J.class, K.class);
} else {
inspector
- .assertIsCompleteMergeGroup(A.class, B.class)
- .assertMergedInto(B.class, A.class)
.assertIsCompleteMergeGroup(I.class, J.class)
.assertClassesNotMerged(K.class);
}
})
.addOptionsModification(
options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
+ if (enableInterfaceMergingInInitial) {
+ options.horizontalClassMergerOptions().setEnableInterfaceMergingInInitial();
+ }
})
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
@@ -80,10 +81,10 @@
assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
assertThat(aClassSubject, isImplementing(inspector.clazz(K.class)));
- ClassSubject bClassSubject = inspector.clazz(C.class);
- assertThat(bClassSubject, isPresent());
+ ClassSubject cClassSubject = inspector.clazz(C.class);
+ assertThat(cClassSubject, isPresent());
assertThat(
- bClassSubject,
+ cClassSubject,
isImplementing(
inspector.clazz(
parameters.canUseDefaultAndStaticInterfaceMethods()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
index d4f14bd..dca511f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
@@ -51,11 +51,6 @@
inspector.assertIsCompleteMergeGroup(I.class, J.class);
}
})
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
index f9e85ba..62cf256 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -52,12 +52,6 @@
inspector.assertNoClassesMerged();
}
})
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- options.horizontalClassMergerOptions().setIgnoreRuntimeTypeChecksForTesting();
- })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
index 7ca9cc5..e8bd34f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
@@ -36,11 +36,6 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
index dad75a4..a88f6c1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
@@ -36,11 +36,6 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
index e58ca16..dae6f36 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
@@ -38,11 +38,6 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
index a877121..e002263 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
@@ -38,11 +38,6 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
index 0807547..ef6ac67 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
@@ -42,11 +42,6 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
index 5f30eff..f33ec1c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
@@ -42,11 +42,6 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfaceChainMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfaceChainMergingTest.java
new file mode 100644
index 0000000..80160a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfaceChainMergingTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2021, 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.classmerging.horizontal.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EmptyInterfaceChainMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EmptyInterfaceChainMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertIsCompleteMergeGroup(I.class, J.class, K.class)
+ .assertNoOtherClassesMerged())
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccess();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(A.class);
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {}
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J extends I {}
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface K extends J {}
+
+ static class A implements K {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
index 24e5de8..7cbdad2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
@@ -40,11 +40,6 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
index 2d264db..f13b7e0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
@@ -36,11 +36,6 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
index 23ed1ea..d529fc9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
@@ -36,11 +36,6 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
index 6ad6ef1..e8630bb 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
@@ -50,11 +50,6 @@
inspector.assertIsCompleteMergeGroup(I.class, J.class);
}
})
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
index 1c33206..6905202 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
@@ -40,17 +40,17 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
- // TODO(b/173990042): Extend horizontal class merging to interfaces.
.addHorizontallyMergedClassesInspector(
inspector -> {
if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
- inspector.assertNoClassesMerged();
+ inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged();
} else {
inspector
+ .assertClassesNotMerged(I.class, J.class)
.assertClassReferencesMerged(
SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
SyntheticItemsTestUtils.syntheticCompanionClass(J.class))
- .assertClassesNotMerged(I.class, J.class);
+ .assertNoOtherClassesMerged();
}
})
.enableInliningAnnotations()
@@ -62,11 +62,9 @@
// We do not allow horizontal class merging of interfaces and classes. Therefore, A
// should remain in the output.
assertThat(inspector.clazz(A.class), isPresent());
-
- // TODO(b/173990042): I and J should be merged.
if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
assertThat(inspector.clazz(I.class), isPresent());
- assertThat(inspector.clazz(J.class), isPresent());
+ assertThat(inspector.clazz(J.class), isAbsent());
} else {
assertThat(inspector.clazz(syntheticCompanionClass(I.class)), isPresent());
assertThat(inspector.clazz(syntheticCompanionClass(J.class)), isAbsent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 5468cae..2f157d0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -1052,7 +1052,8 @@
CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever.class"),
CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class"),
CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$1.class"),
- CF_DIR.resolve("NeverInline.class")
+ CF_DIR.resolve("NeverInline.class"),
+ CF_DIR.resolve("NoHorizontalClassMerging.class")
};
// SimpleInterface cannot be merged into SimpleInterfaceImpl because SimpleInterfaceImpl
// is in a different package and is not public.
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 1bfbaac..71ddab8 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -63,7 +63,7 @@
.addOptionsModification(
options ->
options.testing.horizontalClassMergingTarget =
- (candidates, target) -> candidates.iterator().next())
+ (appView, candidates, target) -> candidates.iterator().next())
.addHorizontallyMergedClassesInspector(
inspector ->
inspector.assertMergedInto(BaseWithStatic.class, AFeatureWithStatic.class))
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
index 9d11655..12bc414 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
@@ -85,6 +85,7 @@
void foo();
}
+ @NoHorizontalClassMerging
@NoVerticalClassMerging
interface J {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
index 768fbd6..6bc0862 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -42,6 +43,7 @@
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
@@ -86,6 +88,7 @@
void m();
}
+ @NoHorizontalClassMerging
@NoVerticalClassMerging
interface J extends I {
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 550356e..de5d7f0 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -12,21 +12,26 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.R8;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
@@ -204,6 +209,7 @@
.addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
+ .apply(this::configureHorizontalClassMerging)
.compile()
.graphInspector();
@@ -222,6 +228,7 @@
+ " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
+ .apply(this::configureHorizontalClassMerging)
.compile()
.graphInspector();
assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector);
@@ -241,6 +248,7 @@
+ " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
+ .apply(this::configureHorizontalClassMerging)
.compile()
.graphInspector();
assertRetainedClassesEqual(referenceInspector, ifThenKeepClassesWithMembersInspector);
@@ -262,11 +270,27 @@
+ " *** <2>(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
+ .apply(this::configureHorizontalClassMerging)
.compile()
.graphInspector();
assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
}
+ private void configureHorizontalClassMerging(R8FullTestBuilder testBuilder) {
+ // Attempt to ensure similar class merging across different builds by choosing the merge target
+ // as the class with the lexicographically smallest original name.
+ testBuilder.addOptionsModification(
+ options ->
+ options.testing.horizontalClassMergingTarget =
+ (appView, candidates, target) -> {
+ List<DexProgramClass> classes = Lists.newArrayList(candidates);
+ classes.sort(
+ Comparator.comparing(
+ clazz -> appView.graphLens().getOriginalType(clazz.getType())));
+ return ListUtils.first(classes);
+ });
+ }
+
private void assertRetainedClassesEqual(
GraphInspector referenceResult, GraphInspector conditionalResult) {
assertRetainedClassesEqual(referenceResult, conditionalResult, false, false);
diff --git a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
index 59c649b..7f6323a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -11,10 +11,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.jasmin.JasminBuilder;
@@ -33,16 +34,18 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
+@NoHorizontalClassMerging
interface B112452064SuperInterface1 {
void foo();
}
+@NoHorizontalClassMerging
interface B112452064SuperInterface2 {
void bar();
}
-interface B112452064SubInterface extends B112452064SuperInterface1, B112452064SuperInterface2 {
-}
+@NoHorizontalClassMerging
+interface B112452064SubInterface extends B112452064SuperInterface1, B112452064SuperInterface2 {}
class B112452064TestMain {
@@ -82,7 +85,8 @@
@Parameters(name = "{1}, argument removal: {0}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
public ParameterTypeTest(boolean enableArgumentRemoval, TestParameters parameters) {
@@ -128,7 +132,8 @@
options.enableUnusedInterfaceRemoval = enableUnusedInterfaceRemoval;
options.enableVerticalClassMerging = enableVerticalClassMerging;
})
- .setMinApi(parameters.getRuntime())
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), B112452064TestMain.class)
.assertSuccessWithOutput(javaResult.stdout)
@@ -286,7 +291,6 @@
"return");
final String mainClassName = mainClass.name;
- String proguardConfig = keepMainProguardConfiguration(mainClassName, false, false);
// Run input program on java.
Path outputDirectory = temp.newFolder().toPath();
@@ -296,36 +300,34 @@
assertThat(javaResult.stdout, containsString(bar.name));
assertEquals(-1, javaResult.stderr.indexOf("ClassNotFoundException"));
- AndroidApp processedApp =
- compileWithR8(
- jasminBuilder.build(),
- proguardConfig,
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(jasminBuilder.buildClasses())
+ .addKeepMainRule(mainClassName)
+ .addOptionsModification(
options -> {
// Disable inlining to avoid the (short) tested method from being inlined and removed.
options.enableInlining = false;
options.enableArgumentRemoval = enableArgumentRemoval;
- });
-
- // Run processed (output) program on ART
- ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
- if (enableArgumentRemoval) {
- assertEquals(0, artResult.exitCode);
- } else {
- assertNotEquals(0, artResult.exitCode);
-
- DexVm.Version currentVersion = ToolHelper.getDexVm().getVersion();
- String errorMessage =
- currentVersion.isNewerThan(Version.V4_4_4)
- ? "type Precise Reference: Foo[] but expected Reference: SubInterface[]"
- : "[LFoo; is not instance of [LSubInterface;";
- assertThat(artResult.stderr, containsString(errorMessage));
- }
-
- assertEquals(-1, artResult.stderr.indexOf("ClassNotFoundException"));
-
- CodeInspector inspector = new CodeInspector(processedApp);
- ClassSubject subSubject = inspector.clazz(sub.name);
- assertNotEquals(enableArgumentRemoval, subSubject.isPresent());
+ })
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject subSubject = inspector.clazz(sub.name);
+ assertNotEquals(enableArgumentRemoval, subSubject.isPresent());
+ })
+ .run(parameters.getRuntime(), mainClassName)
+ .applyIf(
+ enableArgumentRemoval || parameters.isCfRuntime(),
+ SingleTestRunResult::assertSuccess,
+ result ->
+ result.assertFailureWithErrorThatMatches(
+ containsString(
+ parameters.getDexRuntimeVersion().isNewerThan(Version.V4_4_4)
+ ? "type Precise Reference: Foo[] but expected Reference: SubInterface[]"
+ : "[LFoo; is not instance of [LSubInterface;")))
+ .assertStderrMatches(not(containsString("ClassNotFoundException")));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
index c6e5eff..b427736 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -43,6 +44,7 @@
.addKeepMainRule(Main.class)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -79,6 +81,7 @@
}
}
+ @NoHorizontalClassMerging
@NoUnusedInterfaceRemoval
@NoVerticalClassMerging
interface I {
@@ -94,6 +97,7 @@
}
}
+ @NoHorizontalClassMerging
@NoUnusedInterfaceRemoval
@NoVerticalClassMerging
interface J extends I {}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index 0410bc0..b39fd97 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -32,6 +33,8 @@
private final DexItemFactory dexItemFactory;
private final HorizontallyMergedClasses horizontallyMergedClasses;
+ private final Set<ClassReference> seen = new HashSet<>();
+
public HorizontallyMergedClassesInspector(
DexItemFactory dexItemFactory, HorizontallyMergedClasses horizontallyMergedClasses) {
this.dexItemFactory = dexItemFactory;
@@ -102,6 +105,7 @@
+ StringUtils.join(", ", unmerged, DexType::getTypeName),
0,
unmerged.size());
+ seen.addAll(types.stream().map(DexType::asClassReference).collect(Collectors.toList()));
return this;
}
@@ -117,6 +121,17 @@
return this;
}
+ public HorizontallyMergedClassesInspector assertNoOtherClassesMerged() {
+ horizontallyMergedClasses.forEachMergeGroup(
+ (sources, target) -> {
+ for (DexType source : sources) {
+ assertTrue(source.getTypeName(), seen.contains(source.asClassReference()));
+ }
+ assertTrue(target.getTypeName(), seen.contains(target.asClassReference()));
+ });
+ return this;
+ }
+
public HorizontallyMergedClassesInspector assertClassesNotMerged(Class<?>... classes) {
return assertClassesNotMerged(Arrays.asList(classes));
}
@@ -145,6 +160,7 @@
assertTrue(type.isClassType());
assertFalse(horizontallyMergedClasses.hasBeenMergedOrIsMergeTarget(type));
}
+ seen.addAll(types.stream().map(DexType::asClassReference).collect(Collectors.toList()));
return this;
}
@@ -204,6 +220,7 @@
classReferences.size() - 1,
sources.size());
assertTrue(types.containsAll(sources));
+ seen.addAll(classReferences);
return this;
}