Rewrite traverse in CallGraphBuilder to be an iterative algorithm

Bug: 143685705
Change-Id: I4cc07af9f3a678af57c81c3f5ddd87653aa2b09f
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 1e0a60d..6af3446 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -330,7 +330,6 @@
     // Mapping from callee to the set of callers that were removed from the callee.
     private Map<Node, Set<Node>> removedEdges = new IdentityHashMap<>();
 
-    private int currentDepth = 0;
     private int maxDepth = 0;
 
     CycleEliminator(Collection<Node> nodes, InternalOptions options) {
@@ -345,10 +344,8 @@
 
     CycleEliminationResult breakCycles() {
       // Break cycles in this call graph by removing edges causing cycles.
-      for (Node node : nodes) {
-        assert currentDepth == 0;
-        traverse(node);
-      }
+      traverse();
+
       CycleEliminationResult result = new CycleEliminationResult(removedEdges);
       if (Log.ENABLED) {
         Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedEdges());
@@ -359,7 +356,6 @@
     }
 
     private void reset() {
-      assert currentDepth == 0;
       assert stack.isEmpty();
       assert stackSet.isEmpty();
       marked.clear();
@@ -367,41 +363,114 @@
       removedEdges = new IdentityHashMap<>();
     }
 
-    private void traverse(Node node) {
-      if (Log.ENABLED) {
-        if (currentDepth > maxDepth) {
-          maxDepth = currentDepth;
+    private static class WorkItem {
+      boolean isNode() {
+        return false;
+      }
+
+      NodeWorkItem asNode() {
+        return null;
+      }
+
+      boolean isIterator() {
+        return false;
+      }
+
+      IteratorWorkItem asIterator() {
+        return null;
+      }
+    }
+
+    private static class NodeWorkItem extends WorkItem {
+      private final Node node;
+
+      NodeWorkItem(Node node) {
+        this.node = node;
+      }
+
+      @Override
+      boolean isNode() {
+        return true;
+      }
+
+      @Override
+      NodeWorkItem asNode() {
+        return this;
+      }
+    }
+
+    private static class IteratorWorkItem extends WorkItem {
+      private final Node caller;
+      private final Iterator<Node> callees;
+
+      IteratorWorkItem(Node caller, Iterator<Node> callees) {
+        this.caller = caller;
+        this.callees = callees;
+      }
+
+      @Override
+      boolean isIterator() {
+        return true;
+      }
+
+      @Override
+      IteratorWorkItem asIterator() {
+        return this;
+      }
+    }
+
+    private void traverse() {
+      Deque<WorkItem> workItems = new ArrayDeque<>(nodes.size());
+      for (Node node : nodes) {
+        workItems.addLast(new NodeWorkItem(node));
+      }
+      while (!workItems.isEmpty()) {
+        WorkItem workItem = workItems.removeFirst();
+        if (workItem.isNode()) {
+          Node node = workItem.asNode().node;
+          if (Log.ENABLED) {
+            if (stack.size() > maxDepth) {
+              maxDepth = stack.size();
+            }
+          }
+
+          if (marked.contains(node)) {
+            // Already visited all nodes that can be reached from this node.
+            continue;
+          }
+
+          push(node);
+
+          // The callees must be sorted before calling traverse recursively. This ensures that
+          // cycles are broken the same way across multiple compilations.
+          Collection<Node> callees = node.getCalleesWithDeterministicOrder();
+
+          if (options.testing.nondeterministicCycleElimination) {
+            callees = reorderNodes(new ArrayList<>(callees));
+          }
+          workItems.addFirst(new IteratorWorkItem(node, callees.iterator()));
+        } else {
+          assert workItem.isIterator();
+          IteratorWorkItem iteratorWorkItem = workItem.asIterator();
+          Node newCaller = iterateCallees(iteratorWorkItem.callees, iteratorWorkItem.caller);
+          if (newCaller != null) {
+            // We did not finish the work on this iterator, so add it again.
+            workItems.addFirst(iteratorWorkItem);
+            workItems.addFirst(new NodeWorkItem(newCaller));
+          } else {
+            assert !iteratorWorkItem.callees.hasNext();
+            pop(iteratorWorkItem.caller);
+            marked.add(iteratorWorkItem.caller);
+          }
         }
       }
+    }
 
-      if (marked.contains(node)) {
-        // Already visited all nodes that can be reached from this node.
-        return;
-      }
-
-      push(node);
-
-      // The callees must be sorted before calling traverse recursively. This ensures that cycles
-      // are broken the same way across multiple compilations.
-      Collection<Node> callees = node.getCalleesWithDeterministicOrder();
-
-      if (options.testing.nondeterministicCycleElimination) {
-        callees = reorderNodes(new ArrayList<>(callees));
-      }
-
-      Iterator<Node> calleeIterator = callees.iterator();
+    private Node iterateCallees(Iterator<Node> calleeIterator, Node node) {
       while (calleeIterator.hasNext()) {
         Node callee = calleeIterator.next();
-
-        // If we've exceeded the depth threshold, then treat it as if we have found a cycle. This
-        // ensures that we won't run into stack overflows when the call graph contains large call
-        // chains. This should have a negligible impact on code size as long as the threshold is
-        // large enough.
         boolean foundCycle = stackSet.contains(callee);
-        boolean thresholdExceeded =
-            currentDepth >= options.callGraphCycleEliminatorMaxDepthThreshold
-                && edgeRemovalIsSafe(node, callee);
-        if (foundCycle || thresholdExceeded) {
+        if (foundCycle) {
           // Found a cycle that needs to be eliminated.
           if (edgeRemovalIsSafe(node, callee)) {
             // Break the cycle by removing the edge node->callee.
@@ -461,13 +530,10 @@
             recoverStack(cycle);
           }
         } else {
-          currentDepth++;
-          traverse(callee);
-          currentDepth--;
+          return callee;
         }
       }
-      pop(node);
-      marked.add(node);
+      return null;
     }
 
     private void push(Node node) {