Parallelize call graph construction

Change-Id: Id6ad17de6ebea2dd0402282b776e85de4e5e7442
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 945b58b..f8d5d07 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -238,10 +238,16 @@
     this.kotlinInfo = kotlinInfo;
   }
 
+  public boolean hasFields() {
+    return instanceFields.length + staticFields.length > 0;
+  }
+
+  public boolean hasMethods() {
+    return directMethods.length + virtualMethods.length > 0;
+  }
+
   public boolean hasMethodsOrFields() {
-    int numberOfFields = staticFields().size() + instanceFields().size();
-    int numberOfMethods = directMethods().size() + virtualMethods().size();
-    return numberOfFields + numberOfMethods > 0;
+    return hasMethods() || hasFields();
   }
 
   public boolean hasAnnotations() {
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 767f0b2..e03f33c 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
@@ -15,12 +15,12 @@
 import com.android.tools.r8.utils.ThrowingBiConsumer;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
@@ -52,23 +52,29 @@
     private int numberOfCallSites = 0;
 
     // Outgoing calls from this method.
-    private final Set<Node> callees = new LinkedHashSet<>();
+    private final Set<Node> callees = new TreeSet<>();
 
     // Incoming calls to this method.
-    private final Set<Node> callers = new LinkedHashSet<>();
+    private final Set<Node> callers = new TreeSet<>();
 
     public Node(DexEncodedMethod method) {
       this.method = method;
     }
 
-    public boolean isBridge() {
-      return method.accessFlags.isBridge();
-    }
-
-    public void addCaller(Node caller) {
-      callers.add(caller);
-      caller.callees.add(this);
-      numberOfCallSites++;
+    public void addCallerConcurrently(Node caller) {
+      if (caller != this) {
+        synchronized (callers) {
+          callers.add(caller);
+          numberOfCallSites++;
+        }
+        synchronized (caller.callees) {
+          caller.callees.add(this);
+        }
+      } else {
+        synchronized (callers) {
+          numberOfCallSites++;
+        }
+      }
     }
 
     public void removeCaller(Node caller) {
@@ -77,9 +83,7 @@
     }
 
     public Node[] getCalleesWithDeterministicOrder() {
-      Node[] sorted = callees.toArray(Node.EMPTY_ARRAY);
-      Arrays.sort(sorted);
-      return sorted;
+      return callees.toArray(Node.EMPTY_ARRAY);
     }
 
     public boolean hasCallee(Node method) {
@@ -109,9 +113,6 @@
       builder.append(" callees, ");
       builder.append(callers.size());
       builder.append(" callers");
-      if (isBridge()) {
-        builder.append(", bridge");
-      }
       builder.append(", invoke count ").append(numberOfCallSites);
       builder.append(").\n");
       if (callees.size() > 0) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
index a19bf3c..15aefa7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
@@ -16,10 +16,12 @@
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
@@ -34,6 +36,9 @@
 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;
 
 public class CallGraphBuilder {
 
@@ -44,26 +49,36 @@
     this.appView = appView;
   }
 
-  public CallGraph build(Timing timing) {
-    for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
-      processClass(clazz);
+  public CallGraph build(ExecutorService executorService, Timing timing) throws ExecutionException {
+    List<Future<?>> futures = new ArrayList<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.hasMethods()) {
+        futures.add(
+            executorService.submit(
+                () -> {
+                  processClass(clazz);
+                  return null; // we want a Callable not a Runnable to be able to throw
+                }));
+      }
     }
+    ThreadUtils.awaitFutures(futures);
 
     assert verifyAllMethodsWithCodeExists();
 
     timing.begin("Cycle elimination");
-    CycleEliminator cycleEliminator = new CycleEliminator(nodes.values(), appView.options());
+    // Sort the nodes for deterministic cycle elimination.
+    Set<Node> nodesWithDeterministicOrder = Sets.newTreeSet(nodes.values());
+    CycleEliminator cycleEliminator =
+        new CycleEliminator(nodesWithDeterministicOrder, appView.options());
     cycleEliminator.breakCycles();
     timing.end();
     assert cycleEliminator.breakCycles() == 0; // This time the cycles should be gone.
 
-    return new CallGraph(appView, Sets.newHashSet(nodes.values()));
+    return new CallGraph(appView, nodesWithDeterministicOrder);
   }
 
   private void processClass(DexProgramClass clazz) {
-    for (DexEncodedMethod method : clazz.allMethodsSorted()) {
-      processMethod(method);
-    }
+    clazz.forEachMethod(this::processMethod);
   }
 
   private void processMethod(DexEncodedMethod method) {
@@ -73,7 +88,9 @@
   }
 
   private Node getOrCreateNode(DexEncodedMethod method) {
-    return nodes.computeIfAbsent(method.method, ignore -> new Node(method));
+    synchronized (nodes) {
+      return nodes.computeIfAbsent(method.method, ignore -> new Node(method));
+    }
   }
 
   private boolean verifyAllMethodsWithCodeExists() {
@@ -111,7 +128,7 @@
 
     private void addTarget(DexEncodedMethod callee) {
       if (!callee.accessFlags.isAbstract()) {
-        getOrCreateNode(callee).addCaller(caller);
+        getOrCreateNode(callee).addCallerConcurrently(caller);
       }
     }
 
@@ -131,7 +148,7 @@
       }
     }
 
-    private void processInvoke(Invoke.Type type, DexMethod method) {
+    private void processInvoke(Type type, DexMethod method) {
       DexEncodedMethod source = caller.method;
       GraphLenseLookupResult result =
           appView.graphLense().lookupMethod(method, source.method, type);
@@ -176,31 +193,31 @@
 
     @Override
     public boolean registerInvokeVirtual(DexMethod method) {
-      processInvoke(Invoke.Type.VIRTUAL, method);
+      processInvoke(Type.VIRTUAL, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeDirect(DexMethod method) {
-      processInvoke(Invoke.Type.DIRECT, method);
+      processInvoke(Type.DIRECT, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeStatic(DexMethod method) {
-      processInvoke(Invoke.Type.STATIC, method);
+      processInvoke(Type.STATIC, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeInterface(DexMethod method) {
-      processInvoke(Invoke.Type.INTERFACE, method);
+      processInvoke(Type.INTERFACE, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeSuper(DexMethod method) {
-      processInvoke(Invoke.Type.SUPER, method);
+      processInvoke(Type.SUPER, method);
       return false;
     }
 
@@ -311,8 +328,8 @@
 
       push(node);
 
-      // Sort the callees before calling traverse recursively. This will ensure cycles are broken
-      // the same way across multiple invocations of the R8 compiler.
+      // The callees must be sorted before calling traverse recursively. This ensures that cycles
+      // are broken the same way across multiple compilations.
       Node[] callees = node.getCalleesWithDeterministicOrder();
 
       if (options.testing.nondeterministicCycleElimination) {
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 e617152..9775546 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
@@ -510,7 +510,8 @@
     OptimizationFeedbackDelayed feedback = delayedOptimizationFeedback;
     {
       timing.begin("Build call graph");
-      CallGraph callGraph = CallGraph.builder(appView.withLiveness()).build(timing);
+      CallGraph callGraph =
+          CallGraph.builder(appView.withLiveness()).build(executorService, timing);
       timing.end();
       timing.begin("IR conversion phase 1");
       BiConsumer<IRCode, DexEncodedMethod> outlineHandler =
diff --git a/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java b/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
index accc3e5..5e15f69 100644
--- a/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
@@ -57,8 +57,8 @@
 
     for (Collection<Node> nodes : orderings) {
       // Create a cycle between the two nodes.
-      forceInlinedMethod.addCaller(method);
-      method.addCaller(forceInlinedMethod);
+      forceInlinedMethod.addCallerConcurrently(method);
+      method.addCallerConcurrently(forceInlinedMethod);
 
       // Check that the cycle eliminator finds the cycle.
       CycleEliminator cycleEliminator = new CycleEliminator(nodes, new InternalOptions());
@@ -79,8 +79,8 @@
     Node forceInlinedMethod = createForceInlinedNode("n2");
 
     // Create a cycle between the two nodes.
-    forceInlinedMethod.addCaller(method);
-    method.addCaller(forceInlinedMethod);
+    forceInlinedMethod.addCallerConcurrently(method);
+    method.addCallerConcurrently(forceInlinedMethod);
 
     CycleEliminator cycleEliminator =
         new CycleEliminator(ImmutableList.of(method, forceInlinedMethod), new InternalOptions());
@@ -160,12 +160,12 @@
 
     for (Configuration configuration : configurations) {
       // Create a cycle between the three nodes.
-      n2.addCaller(n1);
-      n3.addCaller(n2);
-      n1.addCaller(n3);
+      n2.addCallerConcurrently(n1);
+      n3.addCallerConcurrently(n2);
+      n1.addCallerConcurrently(n3);
 
       // Create a cycle in the graph between node n1 and n2.
-      n1.addCaller(n2);
+      n1.addCallerConcurrently(n2);
 
       for (Node node : configuration.nodes) {
         if (configuration.forceInline.contains(node)) {