Introduce a shared super type traversal.

Change-Id: Ic9b7db58e00ee294cc0f1ad1d5d6445281d130c7
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 6630f9c..cefb55a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -4,12 +4,18 @@
 
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.utils.TraversalContinuation.BREAK;
+import static com.android.tools.r8.utils.TraversalContinuation.CONTINUE;
+
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
 
 /* Specific subclass of AppInfo designed to support desugaring in D8. Desugaring requires a
  * minimal amount of knowledge in the overall program, provided through classpath. Basic
@@ -37,6 +43,92 @@
     return this;
   }
 
+  /**
+   * Primitive traversal over all supertypes of a given type.
+   *
+   * <p>No order is guaranteed for the traversal, but a given type will be visited at most once. The
+   * given type is *not* visited. The function indicates if traversal should continue or break. The
+   * result of the traversal is BREAK iff the function returned BREAK.
+   */
+  public TraversalContinuation traverseSuperTypes(
+      final DexClass clazz, BiFunction<DexType, Boolean, TraversalContinuation> fn) {
+    // We do an initial zero-allocation pass over the class super chain as it does not require a
+    // worklist/seen-set. Only if the traversal is not aborted and there actually are interfaces,
+    // do we continue traversal over the interface types. This is assuming that the second pass
+    // over the super chain is less expensive than the eager allocation of the worklist.
+    int interfaceCount = 0;
+    {
+      DexClass currentClass = clazz;
+      while (currentClass != null) {
+        interfaceCount += currentClass.interfaces.values.length;
+        if (currentClass.superType == null) {
+          break;
+        }
+        TraversalContinuation stepResult = fn.apply(currentClass.superType, false);
+        if (stepResult.shouldBreak()) {
+          return stepResult;
+        }
+        currentClass = definitionFor(currentClass.superType);
+      }
+    }
+    if (interfaceCount == 0) {
+      return CONTINUE;
+    }
+    // Interfaces exist, create a worklist and seen set to ensure single visits.
+    Set<DexType> seen = Sets.newIdentityHashSet();
+    Deque<DexType> worklist = new ArrayDeque<>();
+    // Populate the worklist with the direct interfaces of the super chain.
+    {
+      DexClass currentClass = clazz;
+      while (currentClass != null) {
+        for (DexType iface : currentClass.interfaces.values) {
+          if (seen.add(iface)) {
+            TraversalContinuation stepResult = fn.apply(iface, true);
+            if (stepResult.shouldBreak()) {
+              return stepResult;
+            }
+            worklist.addLast(iface);
+          }
+        }
+        if (currentClass.superType == null) {
+          break;
+        }
+        currentClass = definitionFor(currentClass.superType);
+      }
+    }
+    // Iterate all interfaces.
+    while (!worklist.isEmpty()) {
+      DexType type = worklist.removeFirst();
+      DexClass definition = definitionFor(type);
+      if (definition != null) {
+        for (DexType iface : definition.interfaces.values) {
+          if (seen.add(iface)) {
+            TraversalContinuation stepResult = fn.apply(iface, true);
+            if (stepResult.shouldBreak()) {
+              return stepResult;
+            }
+            worklist.addLast(iface);
+          }
+        }
+      }
+    }
+    return CONTINUE;
+  }
+
+  /**
+   * Iterate each super type of class.
+   *
+   * <p>Same as traverseSuperTypes, but unconditionally visits all.
+   */
+  public void forEachSuperType(final DexClass clazz, BiConsumer<DexType, Boolean> fn) {
+    traverseSuperTypes(
+        clazz,
+        (type, isInterface) -> {
+          fn.accept(type, isInterface);
+          return CONTINUE;
+        });
+  }
+
   public boolean isSubtype(DexType subtype, DexType supertype) {
     assert subtype != null;
     assert supertype != null;
@@ -60,30 +152,15 @@
     if (!subtype.isClassType() || !supertype.isClassType()) {
       return false;
     }
-    Deque<DexType> workList = new ArrayDeque<>();
-    workList.addFirst(subtype);
-    while (!workList.isEmpty()) {
-      DexType type = workList.pollFirst();
-      DexClass subtypeClass = definitionFor(type);
-      if (subtypeClass == null) {
-        // Collect missing types for future reporting?
-        continue;
-      }
-      if (subtypeClass.superType == supertype) {
-        return true;
-      }
-      if (subtypeClass.superType != null) {
-        workList.add(subtypeClass.superType);
-      }
-      for (DexType itf : subtypeClass.interfaces.values) {
-        if (itf == supertype) {
-          return true;
-        }
-        workList.add(itf);
-      }
+    DexClass clazz = definitionFor(subtype);
+    if (clazz == null) {
+      return false;
     }
     // TODO(b/123506120): Report missing types when the predicate is inconclusive.
-    return false;
+    return traverseSuperTypes(
+            clazz,
+            (type, isInterface) -> type == supertype ? BREAK : CONTINUE)
+        .shouldBreak();
   }
 
   public boolean isRelatedBySubtyping(DexType type, DexType other) {
@@ -105,33 +182,16 @@
       return clazz.isInterface() ? Collections.singleton(type) : Collections.emptySet();
     }
 
-    // Slow path traverses the full hierarchy.
+    // Slow path traverses the full super type hierarchy.
     Set<DexType> interfaces = Sets.newIdentityHashSet();
     if (clazz.isInterface()) {
       interfaces.add(type);
     }
-    Deque<DexType> workList = new ArrayDeque<>();
-    if (clazz.superType != null && clazz.superType != dexItemFactory().objectType) {
-      workList.add(clazz.superType);
-    }
-    Collections.addAll(interfaces, clazz.interfaces.values);
-    Collections.addAll(workList, clazz.interfaces.values);
-    while (!workList.isEmpty()) {
-      DexType item = workList.pollFirst();
-      DexClass definition = definitionFor(item);
-      if (definition == null) {
-        // Collect missing types for future reporting?
-        continue;
+    forEachSuperType(clazz, (dexType, isInterface) -> {
+      if (isInterface) {
+        interfaces.add(dexType);
       }
-      if (definition.superType != null && definition.superType != dexItemFactory().objectType) {
-        workList.add(definition.superType);
-      }
-      for (DexType iface : definition.interfaces.values) {
-        if (interfaces.add(iface)) {
-          workList.add(iface);
-        }
-      }
-    }
+    });
     return interfaces;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
new file mode 100644
index 0000000..294a210
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, 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.utils;
+
+/** Two value continuation value to indicate the continuation of a loop/traversal. */
+public enum TraversalContinuation {
+  CONTINUE,
+  BREAK;
+
+  public final boolean shouldBreak() {
+    return this == BREAK;
+  }
+
+  public final boolean shouldContinue() {
+    return this == CONTINUE;
+  }
+}