Only minify names based on interfaces that are derived by program classes.

The reduces hello world compilation time with R8 by about 15% and pathological
cases where the library path contains large collections by more than 80%.

A preliminary benchmark script is included.

Change-Id: I7571f981117b3711f54eda6c54a59854a5a0a3f2
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 4a85f38..35df2ef 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -584,16 +584,6 @@
     return ImmutableList.of();
   }
 
-  public Iterable<DexType> allInterfaces(DexItemFactory dexItemFactory) {
-    assert getTypeInfo(dexItemFactory.objectType).hierarchyLevel == ROOT_LEVEL;
-    return Iterables.filter(
-        getTypeInfo(dexItemFactory.objectType).directSubtypes, t -> getTypeInfo(t).isInterface());
-  }
-
-  public void forAllInterfaces(DexItemFactory factory, Consumer<DexType> f) {
-    allInterfaces(factory).forEach(f);
-  }
-
   public boolean isMissingOrHasMissingSuperType(DexType type) {
     DexClass clazz = definitionFor(type);
     return clazz == null || clazz.hasMissingSuperType(this);
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index b3ac259..1f22804 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -14,6 +14,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.io.PrintStream;
+import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
@@ -35,7 +36,7 @@
     }
   }
 
-  FieldRenaming computeRenaming(Timing timing) {
+  FieldRenaming computeRenaming(Collection<DexClass> interfaces, Timing timing) {
     // Reserve names in all classes first. We do this in subtyping order so we do not
     // shadow a reserved field in subclasses. While there is no concept of virtual field
     // dispatch in Java, field resolution still traverses the super type chain and external
@@ -45,15 +46,14 @@
     timing.end();
     // Next, reserve field names in interfaces. These should only be static.
     timing.begin("reserve-interfaces");
-    appView
-        .appInfo()
-        .forAllInterfaces(
-            appView.dexItemFactory(), iface -> reserveNamesInSubtypes(iface, globalState));
+    for (DexClass iface : interfaces) {
+      reserveNamesInSubtypes(iface.type, globalState);
+    }
     timing.end();
     // Rename the definitions.
     timing.begin("rename-definitions");
     renameFieldsInClasses();
-    renameFieldsInInterfaces();
+    renameFieldsInInterfaces(interfaces);
     timing.end();
     // Rename the references that are not rebound to definitions for some reasons.
     timing.begin("rename-references");
@@ -120,17 +120,13 @@
     }
   }
 
-  private void renameFieldsInInterfaces() {
-    for (DexType interfaceType : appView.appInfo().allInterfaces(appView.dexItemFactory())) {
-      renameFieldsInInterface(interfaceType);
+  private void renameFieldsInInterfaces(Collection<DexClass> interfaces) {
+    for (DexClass iface : interfaces) {
+      renameFieldsInInterface(iface);
     }
   }
 
-  private void renameFieldsInInterface(DexType type) {
-    DexClass clazz = appView.definitionFor(type);
-    if (clazz == null) {
-      return;
-    }
+  private void renameFieldsInInterface(DexClass clazz) {
     assert clazz.isInterface();
     NamingState<DexType, ?> state = minifierState.getState(clazz.type);
     assert state != null;
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 661711f..4ec7337 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -22,6 +22,7 @@
 import com.google.common.collect.Sets;
 import java.io.PrintStream;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -30,6 +31,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -76,30 +78,30 @@
     return callSiteRenamings;
   }
 
-  private void reserveNamesInInterfaces() {
-    for (DexType type : appView.appInfo().allInterfaces(appView.dexItemFactory())) {
-      assert appView.appInfo().isInterface(type);
-      frontierState.allocateNamingStateAndReserve(type, type, null);
+  private void reserveNamesInInterfaces(Collection<DexClass> interfaces) {
+    for (DexClass iface : interfaces) {
+      assert iface.isInterface();
+      frontierState.allocateNamingStateAndReserve(iface.type, iface.type, null);
     }
   }
 
-  void assignNamesToInterfaceMethods(Timing timing) {
+  void assignNamesToInterfaceMethods(Timing timing, Collection<DexClass> interfaces) {
     // Reserve all the names that are required for interfaces.
-    reserveNamesInInterfaces();
+    reserveNamesInInterfaces(interfaces);
 
     // First compute a map from method signatures to a set of naming states for interfaces and
     // frontier states of classes that implement them. We add the frontier states so that we can
     // reserve the names for later method naming.
     timing.begin("Compute map");
-    for (DexType type : appView.appInfo().allInterfaces(appView.dexItemFactory())) {
-      assert appView.appInfo().isInterface(type);
-      DexClass clazz = appView.definitionFor(type);
-      if (clazz != null) {
-        assert clazz.isInterface();
-        Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(type);
-        for (DexEncodedMethod method : shuffleMethods(clazz.methods(), appView.options())) {
-          addStatesToGlobalMapForMethod(method.method, collectedStates, type);
-        }
+    Set<DexType> allInterfaceTypes = new HashSet<>(interfaces.size());
+    for (DexClass iface : interfaces) {
+      allInterfaceTypes.add(iface.type);
+    }
+    for (DexClass iface : interfaces) {
+      Set<NamingState<DexProto, ?>> collectedStates =
+          getReachableStates(iface.type, allInterfaceTypes);
+      for (DexEncodedMethod method : shuffleMethods(iface.methods(), appView.options())) {
+        addStatesToGlobalMapForMethod(method.method, collectedStates, iface.type);
       }
     }
 
@@ -131,8 +133,8 @@
           callSites.put(callSite, implementedMethods.iterator().next().method);
           for (DexEncodedMethod method : implementedMethods) {
             DexType iface = method.method.holder;
-            assert appView.appInfo().isInterface(iface);
-            Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface);
+            Set<NamingState<DexProto, ?>> collectedStates =
+                getReachableStates(iface, allInterfaceTypes);
             addStatesToGlobalMapForMethod(method.method, collectedStates, iface);
             callSiteMethods.add(equivalence.wrap(method.method));
           }
@@ -160,6 +162,7 @@
     // Go over every method and assign a name.
     timing.begin("Allocate names");
 
+    timing.begin("sort");
     // Sort the methods by the number of dependent states, so that we use short names for methods
     // that are referenced in many places.
     List<Wrapper<DexMethod>> interfaceMethods =
@@ -167,7 +170,9 @@
             .filter(wrapper -> unificationParent.getOrDefault(wrapper, wrapper).equals(wrapper))
             .sorted(appView.options().testing.minifier.createInterfaceMethodOrdering(this))
             .collect(Collectors.toList());
+    timing.end();
 
+    timing.begin("propogate");
     // Propagate reserved names to all states.
     List<Wrapper<DexMethod>> reservedInterfaceMethods =
         interfaceMethods.stream()
@@ -176,18 +181,24 @@
     for (Wrapper<DexMethod> key : reservedInterfaceMethods) {
       propagateReservedNames(key, unification);
     }
+    timing.end();
 
+    timing.begin("assert");
     // Verify that there is no more to propagate.
     assert reservedInterfaceMethods.stream()
         .noneMatch(key -> propagateReservedNames(key, unification));
+    timing.end();
 
+    timing.begin("assing interface");
     // Assign names to unreserved interface methods.
     for (Wrapper<DexMethod> key : interfaceMethods) {
       if (!reservedInterfaceMethods.contains(key)) {
         assignNameToInterfaceMethod(key, unification);
       }
     }
+    timing.end();
 
+    timing.begin("callsites");
     for (Entry<DexCallSite, DexMethod> entry : callSites.entrySet()) {
       DexMethod method = entry.getValue();
       DexString renamed = minifierState.getRenaming(method);
@@ -200,6 +211,7 @@
       }
     }
     timing.end();
+    timing.end(); // end compute map timing
   }
 
   private boolean propagateReservedNames(
@@ -280,6 +292,7 @@
 
   private void addStatesToGlobalMapForMethod(
       DexMethod method, Set<NamingState<DexProto, ?>> collectedStates, DexType originInterface) {
+    assert collectedStates.stream().noneMatch(Objects::isNull);
     Wrapper<DexMethod> key = equivalence.wrap(method);
     globalStateMap.computeIfAbsent(key, k -> new HashSet<>()).addAll(collectedStates);
     sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method);
@@ -319,16 +332,14 @@
     return false;
   }
 
-  private Set<NamingState<DexProto, ?>> getReachableStates(DexType type) {
-    Set<DexType> reachableInterfaces = Sets.newIdentityHashSet();
-    reachableInterfaces.add(type);
-    collectSuperInterfaces(type, reachableInterfaces);
-    collectSubInterfaces(type, reachableInterfaces);
-
+  private Set<NamingState<DexProto, ?>> getReachableStates(DexType type, Set<DexType> allInterfaces) {
+    Set<DexType> reachableInterfaces = getReachableInterfaces(type, allInterfaces);
     Set<NamingState<DexProto, ?>> reachableStates = new HashSet<>();
     for (DexType reachableInterface : reachableInterfaces) {
       // Add the interface itself.
-      reachableStates.add(minifierState.getState(reachableInterface));
+      NamingState<DexProto, ?> ifaceState = minifierState.getState(reachableInterface);
+      assert ifaceState != null;
+      reachableStates.add(ifaceState);
 
       // And the frontiers that correspond to the classes that implement the interface.
       for (DexType frontier : appView.appInfo().allImplementsSubtypes(reachableInterface)) {
@@ -340,24 +351,36 @@
     return reachableStates;
   }
 
-  private void collectSuperInterfaces(DexType iface, Set<DexType> interfaces) {
+  private Set<DexType> getReachableInterfaces(DexType type, Set<DexType> allInterfaces) {
+    if (!allInterfaces.contains(type)) {
+      return Collections.emptySet();
+    }
+    Set<DexType> reachableInterfaces = Sets.newIdentityHashSet();
+    reachableInterfaces.add(type);
+    collectSuperInterfaces(type, reachableInterfaces, allInterfaces);
+    collectSubInterfaces(type, reachableInterfaces, allInterfaces);
+    return reachableInterfaces;
+  }
+
+  private void collectSuperInterfaces(
+      DexType iface, Set<DexType> reachable, Set<DexType> allInterfaces) {
     DexClass clazz = appView.definitionFor(iface);
-    // In cases where we lack the interface's definition, we can at least look at subtypes and
-    // tie those up to get proper naming.
     if (clazz != null) {
       for (DexType type : clazz.interfaces.values) {
-        if (interfaces.add(type)) {
-          collectSuperInterfaces(type, interfaces);
+        if (allInterfaces.contains(type) && reachable.add(type)) {
+          collectSuperInterfaces(type, reachable, allInterfaces);
         }
       }
     }
   }
 
-  private void collectSubInterfaces(DexType iface, Set<DexType> interfaces) {
+  private void collectSubInterfaces(
+      DexType iface, Set<DexType> reachableInterfaces, Set<DexType> allInterfaces) {
+    // In cases where we lack the interface's definition, we can at least look at subtypes and
+    // tie those up to get proper naming.
     for (DexType subtype : appView.appInfo().allExtendsSubtypes(iface)) {
-      assert appView.appInfo().isInterface(subtype);
-      if (interfaces.add(subtype)) {
-        collectSubInterfaces(subtype, interfaces);
+      if (allInterfaces.contains(subtype) && reachableInterfaces.add(subtype)) {
+        collectSubInterfaces(subtype, reachableInterfaces, allInterfaces);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 15d550c..bb100cc 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -20,6 +20,7 @@
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableMap;
 import java.io.PrintStream;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -128,7 +129,8 @@
     }
   }
 
-  MethodRenaming computeRenaming(Set<DexCallSite> desugaredCallSites, Timing timing) {
+  MethodRenaming computeRenaming(
+      Collection<DexClass> interfaces, Set<DexCallSite> desugaredCallSites, Timing timing) {
     // Phase 1: Reserve all the names that need to be kept and allocate linked state in the
     //          library part.
     timing.begin("Phase 1");
@@ -141,9 +143,8 @@
     InterfaceMethodNameMinifier interfaceMethodNameMinifier =
         new InterfaceMethodNameMinifier(
             appView, desugaredCallSites, equivalence, frontierState, minifierState);
-    interfaceMethodNameMinifier.assignNamesToInterfaceMethods(timing);
+    interfaceMethodNameMinifier.assignNamesToInterfaceMethods(timing, interfaces);
     timing.end();
-    // TODO(zerny): The traversals below should only traverse the reachable graph!
     // Phase 3: Assign names top-down by traversing the subtype hierarchy.
     timing.begin("Phase 3");
     assignNamesToClassesMethods(appView.dexItemFactory().objectType, false);
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 6db6f61..411219b 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
@@ -23,6 +24,7 @@
 import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import java.util.Set;
+import java.util.TreeSet;
 
 public class Minifier {
 
@@ -36,6 +38,10 @@
 
   public NamingLens run(Timing timing) {
     assert appView.options().isMinifying();
+    timing.begin("ComputeInterfaces");
+    Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
+    interfaces.addAll(appView.appInfo().computeReachableInterfaces(desugaredCallSites));
+    timing.end();
     timing.begin("MinifyClasses");
     ClassNameMinifier classNameMinifier =
         new ClassNameMinifier(
@@ -54,7 +60,8 @@
     MemberNamingStrategy minifyMembers = new MinifierMemberNamingStrategy(appView.dexItemFactory());
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
-        new MethodNameMinifier(appView, minifyMembers).computeRenaming(desugaredCallSites, timing);
+        new MethodNameMinifier(appView, minifyMembers)
+            .computeRenaming(interfaces, desugaredCallSites, timing);
     timing.end();
 
     assert new MinifiedRenaming(appView, classRenaming, methodRenaming, FieldRenaming.empty())
@@ -62,7 +69,7 @@
 
     timing.begin("MinifyFields");
     FieldRenaming fieldRenaming =
-        new FieldNameMinifier(appView, minifyMembers).computeRenaming(timing);
+        new FieldNameMinifier(appView, minifyMembers).computeRenaming(interfaces, timing);
     timing.end();
 
     NamingLens lens = new MinifiedRenaming(appView, classRenaming, methodRenaming, fieldRenaming);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index caed777..1566997 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -34,6 +34,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 public class ProguardMapMinifier {
 
@@ -133,19 +134,23 @@
         classNameMinifier.computeRenaming(timing, syntheticCompanionClasses);
     timing.end();
 
+    Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.slowCompareTo(b.type));
+    interfaces.addAll(appView.appInfo().computeReachableInterfaces(desugaredCallSites));
+
     ApplyMappingMemberNamingStrategy nameStrategy =
         new ApplyMappingMemberNamingStrategy(
             memberNames, appView.dexItemFactory(), appView.options().reporter);
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
-        new MethodNameMinifier(appView, nameStrategy).computeRenaming(desugaredCallSites, timing);
+        new MethodNameMinifier(appView, nameStrategy)
+            .computeRenaming(interfaces, desugaredCallSites, timing);
     // Amend the method renamings with the default interface methods.
     methodRenaming.renaming.putAll(defaultInterfaceMethodImplementationNames);
     timing.end();
 
     timing.begin("MinifyFields");
     FieldRenaming fieldRenaming =
-        new FieldNameMinifier(appView, nameStrategy).computeRenaming(timing);
+        new FieldNameMinifier(appView, nameStrategy).computeRenaming(interfaces, timing);
     timing.end();
 
     appView.options().reporter.failIfPendingErrors();
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index b08fad9..7ff727e 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.optimize.PublicizerLense.PublicizedLenseBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
+import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -56,7 +57,9 @@
 
     // Phase 2: Visit classes and promote class/member to public if possible.
     timing.begin("Phase 2: promoteToPublic");
-    appView.appInfo().forAllInterfaces(appView.dexItemFactory(), this::publicizeType);
+    for (DexClass iface : appView.appInfo().computeReachableInterfaces(Collections.emptySet())) {
+      publicizeType(iface.type);
+    }
     publicizeType(appView.dexItemFactory().objectType);
     timing.end();
 
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 660de51..1a2b0e2 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -12,6 +12,7 @@
 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.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
@@ -28,8 +29,10 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -540,6 +543,44 @@
     previous.markObsolete();
   }
 
+  public Collection<DexClass> computeReachableInterfaces(Set<DexCallSite> desugaredCallSites) {
+    Set<DexClass> interfaces = Sets.newIdentityHashSet();
+    Set<DexType> seen = Sets.newIdentityHashSet();
+    Deque<DexType> worklist = new ArrayDeque<>();
+    for (DexProgramClass clazz : classes()) {
+      worklist.add(clazz.type);
+    }
+    // TODO(b/129458850): Remove this once desugared classes are made part of the program classes.
+    for (DexCallSite callSite : desugaredCallSites) {
+      for (DexEncodedMethod method : lookupLambdaImplementedMethods(callSite)) {
+        worklist.add(method.method.holder);
+      }
+    }
+    for (DexCallSite callSite : callSites) {
+      for (DexEncodedMethod method : lookupLambdaImplementedMethods(callSite)) {
+        worklist.add(method.method.holder);
+      }
+    }
+    while (!worklist.isEmpty()) {
+      DexType type = worklist.pop();
+      if (!seen.add(type)) {
+        continue;
+      }
+      DexClass definition = definitionFor(type);
+      if (definition == null) {
+        continue;
+      }
+      if (definition.isInterface()) {
+        interfaces.add(definition);
+      }
+      if (definition.superType != null) {
+        worklist.add(definition.superType);
+      }
+      Collections.addAll(worklist, definition.interfaces.values);
+    }
+    return interfaces;
+  }
+
   public AppInfoWithLiveness withoutStaticFieldsWrites(Set<DexField> noLongerWrittenFields) {
     assert checkIfObsolete();
     if (noLongerWrittenFields.isEmpty()) {