Make Outlining deterministic.
This change ensures that we alway process methods in callgraph order.
Bug:
Change-Id: Id338a58c08df2433ff3068b38842b4d42cffc79c
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 70e156b..48c8eb0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -17,15 +17,23 @@
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -45,6 +53,10 @@
*/
public class CallGraph {
+ private CallGraph(InternalOptions options) {
+ this.shuffle = options.testing.irOrdering;
+ }
+
private static class Node {
public final DexEncodedMethod method;
@@ -121,6 +133,7 @@
private final Map<DexEncodedMethod, Node> nodes = new LinkedHashMap<>();
private final Map<DexEncodedMethod, Set<DexEncodedMethod>> breakers = new HashMap<>();
+ private final Function<List<DexEncodedMethod>, List<DexEncodedMethod>> shuffle;
// Returns whether the method->callee edge has been removed from the call graph
// to break a cycle in the call graph.
@@ -133,8 +146,8 @@
private Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
public static CallGraph build(DexApplication application, AppInfoWithSubtyping appInfo,
- GraphLense graphLense) {
- CallGraph graph = new CallGraph();
+ GraphLense graphLense, InternalOptions options) {
+ CallGraph graph = new CallGraph(options);
DexClass[] classes = application.classes().toArray(new DexClass[application.classes().size()]);
Arrays.sort(classes, (DexClass a, DexClass b) -> a.type.slowCompareTo(b.type));
for (DexClass clazz : classes) {
@@ -185,7 +198,9 @@
private static boolean allMethodsExists(DexApplication application, CallGraph graph) {
for (DexProgramClass clazz : application.classes()) {
- clazz.forEachMethod(method -> { assert graph.nodes.get(method) != null; });
+ clazz.forEachMethod(method -> {
+ assert graph.nodes.get(method) != null;
+ });
}
return true;
}
@@ -197,19 +212,19 @@
* Please note that there are no cycles in this graph (see {@link #breakCycles}).
* <p>
*
- * @return List of {@link DexEncodedMethod}.
+ * @return List of {@link DexEncodedMethod}.
*/
- List<DexEncodedMethod> extractLeaves() {
+ private List<DexEncodedMethod> extractLeaves() {
if (isEmpty()) {
- return null;
+ return Collections.emptyList();
}
// First identify all leaves before removing them from the graph.
List<Node> leaves = nodes.values().stream().filter(Node::isLeaf).collect(Collectors.toList());
- leaves.forEach( leaf -> {
- leaf.callers.forEach( caller -> caller.callees.remove(leaf));
+ leaves.forEach(leaf -> {
+ leaf.callers.forEach(caller -> caller.callees.remove(leaf));
nodes.remove(leaf.method);
});
- return leaves.stream().map( leaf -> leaf.method).collect(Collectors.toList());
+ return shuffle.apply(leaves.stream().map(leaf -> leaf.method).collect(Collectors.toList()));
}
private int traverse(Node node, Set<Node> stack, Set<Node> marked) {
@@ -253,7 +268,7 @@
int numberOfCycles = 0;
Set<Node> stack = Sets.newIdentityHashSet();
Set<Node> marked = Sets.newIdentityHashSet();
- for(Node node : nodes.values()) {
+ for (Node node : nodes.values()) {
numberOfCycles += traverse(node, stack, marked);
}
return numberOfCycles;
@@ -279,6 +294,21 @@
return nodes.size() == 0;
}
+ public void forEachMethod(Consumer<DexEncodedMethod> consumer, ExecutorService executorService)
+ throws ExecutionException {
+ while (!isEmpty()) {
+ List<DexEncodedMethod> methods = extractLeaves();
+ assert methods.size() > 0;
+ List<Future<?>> futures = new ArrayList<>();
+ for (DexEncodedMethod method : methods) {
+ futures.add(executorService.submit(() -> {
+ consumer.accept(method);
+ }));
+ }
+ ThreadUtils.awaitFutures(futures);
+ }
+ }
+
public void dump() {
nodes.forEach((m, n) -> System.out.println(n + "\n"));
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 8e613c0..a4bb015 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -266,7 +266,7 @@
removeLambdaDeserializationMethods();
timing.begin("Build call graph");
- callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense);
+ callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense, options);
timing.end();
// The process is in two phases.
@@ -278,22 +278,10 @@
// Process the application identifying outlining candidates.
timing.begin("IR conversion phase 1");
OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
- while (!callGraph.isEmpty()) {
- List<DexEncodedMethod> methods = callGraph.extractLeaves();
- assert methods.size() > 0;
- // For testing we have the option to determine the processing order of the methods.
- if (options.testing.irOrdering != null) {
- methods = options.testing.irOrdering.apply(methods);
- }
- List<Future<?>> futures = new ArrayList<>();
- for (DexEncodedMethod method : methods) {
- futures.add(executorService.submit(() -> {
+ callGraph.forEachMethod(method -> {
processMethod(method, directFeedback,
outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
- }));
- }
- ThreadUtils.awaitFutures(futures);
- }
+ }, executorService);
timing.end();
// Build a new application with jumbo string info.
@@ -314,13 +302,20 @@
// add the outline support class IF needed.
DexProgramClass outlineClass = prepareOutlining();
if (outlineClass != null) {
- // Process the selected methods for outlining.
- for (DexEncodedMethod method : outliner.getMethodsSelectedForOutlining()) {
+ // We need a new call graph to ensure deterministic order and also processing inside out
+ // to get maximal inlining. Use a identity lense, as the code has been rewritten.
+ callGraph = CallGraph
+ .build(application, appInfo.withSubtyping(), GraphLense.getIdentityLense(), options);
+ Set<DexEncodedMethod> outlineMethods = outliner.getMethodsSelectedForOutlining();
+ callGraph.forEachMethod(method -> {
+ if (!outlineMethods.contains(method)) {
+ return;
+ }
// This is the second time we compile this method first mark it not processed.
assert !method.getCode().isOutlineCode();
processMethod(method, ignoreOptimizationFeedback, outliner::applyOutliningCandidate);
assert method.isProcessed();
- }
+ }, executorService);
builder.addSynthesizedClass(outlineClass, true);
clearDexMethodCompilationState(outlineClass);
}
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 9b5e007..fa5177b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -142,7 +142,9 @@
}
public static class TestingOptions {
- public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering;
+
+ public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering
+ = Function.identity();
}
public static class AttributeRemovalOptions {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 714792d..3d59ab9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -232,7 +232,7 @@
@Test
public void checkDeterminism() throws Exception {
// Synthesize a dex containing a few empty classes including some in the default package.
- // Everything can fit easaly in a single dex file.
+ // Everything can fit easily in a single dex file.
String[] classes = {
"A",
"B",