Visit classes top-down during vertical class merging
Change-Id: I77d582a4dd939fb7c5b818deb21c166bd2455154
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3239dbe..d587efe 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -42,8 +42,8 @@
import com.android.tools.r8.shaking.ReasonPrinter;
import com.android.tools.r8.shaking.RootSetBuilder;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.shaking.SimpleClassMerger;
import com.android.tools.r8.shaking.TreePruner;
+import com.android.tools.r8.shaking.VerticalClassMerger;
import com.android.tools.r8.shaking.protolite.ProtoLiteExtension;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
@@ -335,8 +335,8 @@
// Class merging requires inlining.
if (options.enableClassMerging && options.enableInlining) {
timing.begin("ClassMerger");
- SimpleClassMerger classMerger = new SimpleClassMerger(application,
- appInfo.withLiveness(), graphLense, timing);
+ VerticalClassMerger classMerger =
+ new VerticalClassMerger(application, appInfo.withLiveness(), graphLense, timing);
graphLense = classMerger.run();
timing.end();
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
similarity index 86%
rename from src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
rename to src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index c9d34ee..2377e3a 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -32,8 +32,10 @@
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
+import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -46,13 +48,13 @@
/**
* Merges Supertypes with a single implementation into their single subtype.
- * <p>
- * A common use-case for this is to merge an interface into its single implementation.
- * <p>
- * The class merger only fixes the structure of the graph but leaves the actual instructions
+ *
+ * <p>A common use-case for this is to merge an interface into its single implementation.
+ *
+ * <p>The class merger only fixes the structure of the graph but leaves the actual instructions
* untouched. Fixup of instructions is deferred via a {@link GraphLense} to the Ir building phase.
*/
-public class SimpleClassMerger {
+public class VerticalClassMerger {
private final DexApplication application;
private final AppInfoWithLiveness appInfo;
@@ -63,8 +65,11 @@
private Collection<DexMethod> invokes;
private int numberOfMerges = 0;
- public SimpleClassMerger(DexApplication application, AppInfoWithLiveness appInfo,
- GraphLense graphLense, Timing timing) {
+ public VerticalClassMerger(
+ DexApplication application,
+ AppInfoWithLiveness appInfo,
+ GraphLense graphLense,
+ Timing timing) {
this.application = application;
this.appInfo = appInfo;
this.graphLense = graphLense;
@@ -135,46 +140,99 @@
return result;
}
+ private void addAncestorsToWorklist(
+ DexProgramClass clazz, Deque<DexProgramClass> worklist, Set<DexProgramClass> seenBefore) {
+ if (seenBefore.contains(clazz)) {
+ return;
+ }
+
+ worklist.addFirst(clazz);
+
+ // Add super classes to worklist.
+ if (clazz.superType != null) {
+ DexClass definition = appInfo.definitionFor(clazz.superType);
+ if (definition != null && definition.isProgramClass()) {
+ addAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
+ }
+ }
+
+ // Add super interfaces to worklist.
+ for (DexType interfaceType : clazz.interfaces.values) {
+ DexClass definition = appInfo.definitionFor(interfaceType);
+ if (definition != null && definition.isProgramClass()) {
+ addAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
+ }
+ }
+ }
+
private GraphLense mergeClasses(GraphLense graphLense) {
- for (DexProgramClass clazz : application.classes()) {
- if (isMergeCandidate(clazz)) {
- DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
- if (appInfo.isPinned(targetClass.type)) {
- // We have to keep the target class intact, so we cannot merge it.
+ Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
+ Deque<DexProgramClass> worklist = new ArrayDeque<>();
+ Set<DexProgramClass> seenBefore = new HashSet<>();
+
+ Iterator<DexProgramClass> classIterator = classes.iterator();
+
+ // Visit the program classes in a top-down order according to the class hierarchy.
+ while (classIterator.hasNext() || !worklist.isEmpty()) {
+ if (worklist.isEmpty()) {
+ // Add the ancestors of this class (including the class itself) to the worklist in such a
+ // way that all super types of the class come before the class itself.
+ addAncestorsToWorklist(classIterator.next(), worklist, seenBefore);
+ if (worklist.isEmpty()) {
continue;
}
- if (mergedClasses.containsKey(targetClass.type)) {
- // TODO(herhut): Traverse top-down.
- continue;
- }
- if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
- // TODO(herhut): Handle class initializers.
- if (Log.ENABLED) {
- Log.info(getClass(), "Cannot merge %s into %s due to static initializers.",
- clazz.toSourceString(), targetClass.toSourceString());
- }
- continue;
- }
- // Guard against the case where we have two methods that may get the same signature
- // if we replace types. This is rare, so we approximate and err on the safe side here.
- if (new CollisionDetector(clazz.type, targetClass.type, getInvokes(), mergedClasses)
- .mayCollide()) {
- if (Log.ENABLED) {
- Log.info(getClass(), "Cannot merge %s into %s due to conflict.", clazz.toSourceString(),
- targetClass.toSourceString());
- }
- continue;
- }
- boolean merged = new ClassMerger(clazz, targetClass).merge();
+ }
+
+ DexProgramClass clazz = worklist.removeFirst();
+ if (!seenBefore.add(clazz) || !isMergeCandidate(clazz)) {
+ continue;
+ }
+
+ DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
+ assert !mergedClasses.containsKey(targetClass.type);
+ if (appInfo.isPinned(targetClass.type)) {
+ // We have to keep the target class intact, so we cannot merge it.
+ continue;
+ }
+ if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
+ // TODO(herhut): Handle class initializers.
if (Log.ENABLED) {
- if (merged) {
- numberOfMerges++;
- Log.info(getClass(), "Merged class %s into %s.", clazz.toSourceString(),
- targetClass.toSourceString());
- } else {
- Log.info(getClass(), "Aborted merge for class %s into %s.",
- clazz.toSourceString(), targetClass.toSourceString());
- }
+ Log.info(
+ getClass(),
+ "Cannot merge %s into %s due to static initializers.",
+ clazz.toSourceString(),
+ targetClass.toSourceString());
+ }
+ continue;
+ }
+ // Guard against the case where we have two methods that may get the same signature
+ // if we replace types. This is rare, so we approximate and err on the safe side here.
+ if (new CollisionDetector(clazz.type, targetClass.type, getInvokes(), mergedClasses)
+ .mayCollide()) {
+ if (Log.ENABLED) {
+ Log.info(
+ getClass(),
+ "Cannot merge %s into %s due to conflict.",
+ clazz.toSourceString(),
+ targetClass.toSourceString());
+ }
+ continue;
+ }
+ boolean merged = new ClassMerger(clazz, targetClass).merge();
+ if (Log.ENABLED) {
+ if (merged) {
+ numberOfMerges++;
+ Log.info(
+ getClass(),
+ "Merged class %s into %s.",
+ clazz.toSourceString(),
+ targetClass.toSourceString());
+ } else {
+ Log.info(
+ getClass(),
+ "Aborted merge for class %s into %s.",
+ clazz.toSourceString(),
+ targetClass.toSourceString());
}
}
}