A small framework for extending the Enqueuer

Change-Id: I40a59b6da5ea34339241d482373ba24119450756
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index 1a54866..7ad4c5b 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -4,12 +4,33 @@
 
 package com.android.tools.r8.graph.analysis;
 
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.shaking.KeepReason;
 
-public interface EnqueuerAnalysis {
+public abstract class EnqueuerAnalysis {
 
-  void processNewlyInstantiatedClass(DexProgramClass clazz, KeepReason reason);
+  /** Called when a class is found to be instantiated. */
+  public void processNewlyInstantiatedClass(DexProgramClass clazz, KeepReason reason) {}
 
-  void done();
+  /** Called when a field is found to be live. */
+  public void processNewlyLiveField(DexEncodedField field) {}
+
+  /** Called when a method is found to be live. */
+  public void processNewlyLiveMethod(DexEncodedMethod method) {}
+
+  /**
+   * Called when the Enqueuer reaches a fixpoint. This may happen multiple times, since each
+   * analysis may enqueue items into the worklist upon the fixpoint using {@param worklist}.
+   */
+  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist) {}
+
+  /**
+   * Called when the Enqueuer has reached the final fixpoint. Each analysis may use this callback to
+   * perform some post-processing.
+   */
+  public void done() {}
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index eb7f627..54b03a6 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -13,7 +13,7 @@
 import java.util.IdentityHashMap;
 import java.util.Map;
 
-public class InitializedClassesInInstanceMethodsAnalysis implements EnqueuerAnalysis {
+public class InitializedClassesInInstanceMethodsAnalysis extends EnqueuerAnalysis {
 
   // A simple structure that stores the result of the analysis.
   public static class InitializedClassesInInstanceMethods {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 4bf7fb3..217d655 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -248,10 +248,8 @@
   private final SetWithReason<DexType> instantiatedLambdas =
       new SetWithReason<>(this::registerType);
 
-  /**
-   * A queue of items that need processing. Different items trigger different actions:
-   */
-  private final Queue<Action> workList = Queues.newArrayDeque();
+  /** A queue of items that need processing. Different items trigger different actions. */
+  private final EnqueuerWorklist workList = new EnqueuerWorklist();
 
   /**
    * A queue of items that have been added to try to keep Proguard compatibility.
@@ -611,7 +609,7 @@
         Log.verbose(getClass(), "Register Iput `%s`.", field);
       }
       // TODO(herhut): We have to add this, but DCR should eliminate dead writes.
-      workList.add(Action.markReachableField(field, KeepReason.fieldReferencedIn(currentMethod)));
+      workList.enqueueMarkReachableFieldAction(field, KeepReason.fieldReferencedIn(currentMethod));
       return true;
     }
 
@@ -623,7 +621,7 @@
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Register Iget `%s`.", field);
       }
-      workList.add(Action.markReachableField(field, KeepReason.fieldReferencedIn(currentMethod)));
+      workList.enqueueMarkReachableFieldAction(field, KeepReason.fieldReferencedIn(currentMethod));
       return true;
     }
 
@@ -1391,6 +1389,9 @@
 
     // Add all dependent members to the workqueue.
     enqueueRootItems(rootSet.getDependentItems(encodedField));
+
+    // Notify analyses.
+    analyses.forEach(analysis -> analysis.processNewlyLiveField(encodedField));
   }
 
   private void markInstanceFieldAsLive(DexEncodedField field, KeepReason reason) {
@@ -1404,8 +1405,12 @@
     processAnnotations(field, field.annotations.annotations);
     liveFields.add(field, reason);
     collectProguardCompatibilityRule(reason);
+
     // Add all dependent members to the workqueue.
     enqueueRootItems(rootSet.getDependentItems(field));
+
+    // Notify analyses.
+    analyses.forEach(analysis -> analysis.processNewlyLiveField(field));
   }
 
   private void markInstantiated(DexType type, KeepReason reason) {
@@ -1894,17 +1899,31 @@
           }
         }
 
-        // Continue fix-point processing while there are additional work items to ensure
-        // items that are passed to Java reflections are traced.
-        if (proguardCompatibilityWorkList.isEmpty()
-            && pendingReflectiveUses.isEmpty()) {
-          break;
+        // Continue fix-point processing while there are additional work items to ensure items that
+        // are passed to Java reflections are traced.
+        if (!pendingReflectiveUses.isEmpty()) {
+          pendingReflectiveUses.forEach(this::handleReflectiveBehavior);
+          pendingReflectiveUses.clear();
         }
-        pendingReflectiveUses.forEach(this::handleReflectiveBehavior);
-        workList.addAll(proguardCompatibilityWorkList);
-        proguardCompatibilityWorkList.clear();
-        pendingReflectiveUses.clear();
+        if (!proguardCompatibilityWorkList.isEmpty()) {
+          workList.addAll(proguardCompatibilityWorkList);
+          proguardCompatibilityWorkList.clear();
+        }
+        if (!workList.isEmpty()) {
+          continue;
+        }
+
+        // Notify each analysis that a fixpoint has been reached, and give each analysis an
+        // opportunity to add items to the worklist.
+        analyses.forEach(analysis -> analysis.notifyFixpoint(this, workList));
+        if (!workList.isEmpty()) {
+          continue;
+        }
+
+        // Reached the fixpoint.
+        break;
       }
+
       if (Log.ENABLED) {
         Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
         for (Entry<DexType, SetWithReason<DexEncodedMethod>> entry : reachableVirtualMethods
@@ -2068,8 +2087,12 @@
             annotation -> processAnnotation(method, annotation));
       }
       method.registerCodeReferences(new UseRegistry(options.itemFactory, method));
+
       // Add all dependent members to the workqueue.
       enqueueRootItems(rootSet.getDependentItems(method));
+
+      // Notify analyses.
+      analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method));
     }
   }
 
@@ -2430,7 +2453,7 @@
     }
   }
 
-  private static class Action {
+  static class Action {
 
     final Kind kind;
     final DexItem target;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
new file mode 100644
index 0000000..7493979
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2019, 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.shaking;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.shaking.Enqueuer.Action;
+import com.google.common.collect.Queues;
+import java.util.Collection;
+import java.util.Queue;
+
+public class EnqueuerWorklist {
+
+  /** A queue of items that need processing. Different items trigger different actions. */
+  private final Queue<Action> worklist = Queues.newArrayDeque();
+
+  public void add(Action action) {
+    worklist.add(action);
+  }
+
+  public void addAll(Collection<Action> action) {
+    worklist.addAll(action);
+  }
+
+  public boolean isEmpty() {
+    return worklist.isEmpty();
+  }
+
+  public Action poll() {
+    return worklist.poll();
+  }
+
+  public void enqueueMarkReachableFieldAction(DexField field, KeepReason reason) {
+    add(Action.markReachableField(field, reason));
+  }
+}