Merge "Utility to visit classes top-down"
diff --git a/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
new file mode 100644
index 0000000..e8d6a4f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2018, 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.graph;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class TopDownClassHierarchyTraversal {
+
+ public static void visit(
+ AppView<? extends AppInfo> appView,
+ Iterable<DexProgramClass> classes,
+ Consumer<DexProgramClass> visitor) {
+ Deque<DexProgramClass> worklist = new ArrayDeque<>();
+ Set<DexProgramClass> visited = 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, visited, appView);
+ if (worklist.isEmpty()) {
+ continue;
+ }
+ }
+
+ DexProgramClass clazz = worklist.removeFirst();
+ if (visited.add(clazz)) {
+ visitor.accept(clazz);
+ }
+ }
+ }
+
+ private static void addAncestorsToWorklist(
+ DexProgramClass clazz,
+ Deque<DexProgramClass> worklist,
+ Set<DexProgramClass> visited,
+ AppView<? extends AppInfo> appView) {
+ if (visited.contains(clazz)) {
+ return;
+ }
+
+ worklist.addFirst(clazz);
+
+ // Add super classes to worklist.
+ if (clazz.superType != null) {
+ DexClass definition = appView.appInfo().definitionFor(clazz.superType);
+ if (definition != null && definition.isProgramClass()) {
+ addAncestorsToWorklist(definition.asProgramClass(), worklist, visited, appView);
+ }
+ }
+
+ // Add super interfaces to worklist.
+ for (DexType interfaceType : clazz.interfaces.values) {
+ DexClass definition = appView.appInfo().definitionFor(interfaceType);
+ if (definition != null && definition.isProgramClass()) {
+ addAncestorsToWorklist(definition.asProgramClass(), worklist, visited, appView);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 71a249f..906451e 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -5,16 +5,15 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector;
@@ -25,12 +24,8 @@
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
-import java.util.ArrayDeque;
import java.util.Arrays;
-import java.util.Deque;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
@@ -64,74 +59,34 @@
this.appView = appView;
}
- // TODO(christofferqa): Share top-down traversal with vertical class merger.
public GraphLense run() {
- // Visit classes in top-down order.
- Deque<DexProgramClass> worklist = new ArrayDeque<>();
- Set<DexProgramClass> seenBefore = new HashSet<>();
-
- Iterator<DexProgramClass> classIterator =
- appView.appInfo().app.classesWithDeterministicOrder().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;
- }
- }
-
- DexProgramClass clazz = worklist.removeFirst();
- if (!seenBefore.add(clazz)) {
- continue;
- }
-
+ Iterable<DexProgramClass> classes = appView.appInfo().app.classesWithDeterministicOrder();
+ TopDownClassHierarchyTraversal.visit(appView, classes, clazz -> {
if (satisfiesMergeCriteria(clazz)) {
merge(clazz);
}
- }
-
- BiMap<DexField, DexField> originalFieldSignatures = fieldMapping.inverse();
- BiMap<DexMethod, DexMethod> originalMethodSignatures = methodMapping.inverse();
- return new NestedGraphLense(
- ImmutableMap.of(),
- methodMapping,
- fieldMapping,
- originalFieldSignatures,
- originalMethodSignatures,
- appView.graphLense(),
- appView.dexItemFactory());
+ });
+ return buildGraphLense();
}
- private void addAncestorsToWorklist(
- DexProgramClass clazz, Deque<DexProgramClass> worklist, Set<DexProgramClass> seenBefore) {
- if (seenBefore.contains(clazz)) {
- return;
+ private GraphLense buildGraphLense() {
+ if (!fieldMapping.isEmpty() || !methodMapping.isEmpty()) {
+ BiMap<DexField, DexField> originalFieldSignatures = fieldMapping.inverse();
+ BiMap<DexMethod, DexMethod> originalMethodSignatures = methodMapping.inverse();
+ return new NestedGraphLense(
+ ImmutableMap.of(),
+ methodMapping,
+ fieldMapping,
+ originalFieldSignatures,
+ originalMethodSignatures,
+ appView.graphLense(),
+ appView.dexItemFactory());
}
-
- worklist.addFirst(clazz);
-
- // Add super classes to worklist.
- if (clazz.superType != null) {
- DexClass definition = appView.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 = appView.appInfo().definitionFor(interfaceType);
- if (definition != null && definition.isProgramClass()) {
- addAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
- }
- }
+ return appView.graphLense();
}
- public boolean satisfiesMergeCriteria(DexProgramClass clazz) {
+ private boolean satisfiesMergeCriteria(DexProgramClass clazz) {
if (appView.appInfo().neverMerge.contains(clazz.type)) {
return false;
}
@@ -169,7 +124,7 @@
return true;
}
- public boolean merge(DexProgramClass clazz) {
+ private boolean merge(DexProgramClass clazz) {
assert satisfiesMergeCriteria(clazz);
String pkg = clazz.type.getPackageDescriptor();
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index df4c786..33ffc44 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -34,6 +34,7 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -57,15 +58,12 @@
import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
-import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -198,6 +196,7 @@
private final DexApplication application;
private final AppInfoWithLiveness appInfo;
+ private final AppView<? extends AppInfoWithLiveness> appView;
private final ExecutorService executorService;
private final GraphLense graphLense;
private final MethodPoolCollection methodPoolCollection;
@@ -231,6 +230,7 @@
Timing timing) {
this.application = application;
this.appInfo = appView.appInfo();
+ this.appView = appView;
this.executorService = executorService;
this.graphLense = appView.graphLense();
this.methodPoolCollection = new MethodPoolCollection(application);
@@ -584,7 +584,7 @@
}
}
- public GraphLense run() throws ExecutionException {
+ public GraphLense run() {
timing.begin("merge");
GraphLense mergingGraphLense = mergeClasses(graphLense);
timing.end();
@@ -604,108 +604,11 @@
return result;
}
- private void addCandidateAncestorsToWorklist(
- DexProgramClass clazz, Deque<DexProgramClass> worklist, Set<DexProgramClass> seenBefore) {
- if (seenBefore.contains(clazz)) {
- return;
- }
-
- if (mergeCandidates.contains(clazz)) {
- worklist.addFirst(clazz);
- }
-
- // Add super classes to worklist.
- if (clazz.superType != null) {
- DexClass definition = appInfo.definitionFor(clazz.superType);
- if (definition != null && definition.isProgramClass()) {
- addCandidateAncestorsToWorklist(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()) {
- addCandidateAncestorsToWorklist(definition.asProgramClass(), worklist, seenBefore);
- }
- }
- }
-
- private GraphLense mergeClasses(GraphLense graphLense) throws ExecutionException {
- Deque<DexProgramClass> worklist = new ArrayDeque<>();
- Set<DexProgramClass> seenBefore = new HashSet<>();
-
- int numberOfMerges = 0;
-
- Iterator<DexProgramClass> candidatesIterator = mergeCandidates.iterator();
-
+ private GraphLense mergeClasses(GraphLense graphLense) {
// Visit the program classes in a top-down order according to the class hierarchy.
- while (candidatesIterator.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.
- addCandidateAncestorsToWorklist(candidatesIterator.next(), worklist, seenBefore);
- if (worklist.isEmpty()) {
- continue;
- }
- }
-
- DexProgramClass clazz = worklist.removeFirst();
- assert isMergeCandidate(clazz, pinnedTypes);
- if (!seenBefore.add(clazz)) {
- continue;
- }
-
- DexProgramClass targetClass =
- appInfo.definitionFor(clazz.type.getSingleSubtype()).asProgramClass();
- assert !mergedClasses.containsKey(targetClass.type);
-
- boolean clazzOrTargetClassHasBeenMerged =
- mergedClassesInverse.containsKey(clazz.type)
- || mergedClassesInverse.containsKey(targetClass.type);
- if (clazzOrTargetClassHasBeenMerged) {
- if (!isStillMergeCandidate(clazz)) {
- continue;
- }
- } else {
- assert isStillMergeCandidate(clazz);
- }
-
- // 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).mayCollide()) {
- if (Log.ENABLED) {
- AbortReason.CONFLICT.printLogMessageForClass(clazz);
- }
- continue;
- }
-
- ClassMerger merger = new ClassMerger(clazz, targetClass);
- boolean merged = merger.merge();
- if (merged) {
- // Commit the changes to the graph lense.
- renamedMembersLense.merge(merger.getRenamings());
- synthesizedBridges.addAll(merger.getSynthesizedBridges());
- }
- 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());
- }
- }
- }
+ TopDownClassHierarchyTraversal.visit(appView, mergeCandidates, this::mergeClassIfPossible);
if (Log.ENABLED) {
- Log.debug(getClass(), "Merged %d classes.", numberOfMerges);
+ Log.debug(getClass(), "Merged %d classes.", mergedClasses.size());
}
return renamedMembersLense.build(graphLense, mergedClasses, synthesizedBridges, appInfo);
}
@@ -764,6 +667,66 @@
return false;
}
+ private void mergeClassIfPossible(DexProgramClass clazz) {
+ if (!mergeCandidates.contains(clazz)) {
+ return;
+ }
+
+ assert isMergeCandidate(clazz, pinnedTypes);
+
+ DexProgramClass targetClass =
+ appInfo.definitionFor(clazz.type.getSingleSubtype()).asProgramClass();
+ assert !mergedClasses.containsKey(targetClass.type);
+
+ boolean clazzOrTargetClassHasBeenMerged =
+ mergedClassesInverse.containsKey(clazz.type)
+ || mergedClassesInverse.containsKey(targetClass.type);
+ if (clazzOrTargetClassHasBeenMerged) {
+ if (!isStillMergeCandidate(clazz)) {
+ return;
+ }
+ } else {
+ assert isStillMergeCandidate(clazz);
+ }
+
+ // 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).mayCollide()) {
+ if (Log.ENABLED) {
+ AbortReason.CONFLICT.printLogMessageForClass(clazz);
+ }
+ return;
+ }
+
+ ClassMerger merger = new ClassMerger(clazz, targetClass);
+ boolean merged;
+ try {
+ merged = merger.merge();
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ if (merged) {
+ // Commit the changes to the graph lense.
+ renamedMembersLense.merge(merger.getRenamings());
+ synthesizedBridges.addAll(merger.getSynthesizedBridges());
+ }
+ if (Log.ENABLED) {
+ if (merged) {
+ 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());
+ }
+ }
+ }
+
private boolean fieldResolutionMayChange(DexClass source, DexClass target) {
if (source.type == target.superType) {
// If there is a "iget Target.f" or "iput Target.f" instruction in target, and the class