Refactor all graph reporting to the GraphReporter.

Change-Id: I7faab9a084f114b3b789d4248b10951613acf8a3
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a7d57b1..c56ea8a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -821,7 +821,7 @@
     if (whyAreYouKeepingConsumer != null) {
       for (DexReference reference : rootSet.reasonAsked) {
         whyAreYouKeepingConsumer.printWhyAreYouKeeping(
-            enqueuer.getGraphNode(reference), System.out);
+            enqueuer.getGraphReporter().getGraphNode(reference), System.out);
       }
     }
     if (rootSet.checkDiscarded.isEmpty()
@@ -851,7 +851,8 @@
       if (!failed.isEmpty()) {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         whyAreYouKeepingConsumer.printWhyAreYouKeeping(
-            enqueuer.getGraphNode(definition.toReference()), new PrintStream(baos));
+            enqueuer.getGraphReporter().getGraphNode(definition.toReference()),
+            new PrintStream(baos));
         options.reporter.info(
             new StringDiagnostic(
                 "Item " + definition.toSourceString() + " was not discarded.\n" + baos.toString()));
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 57ed2d3..fd9a75f 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -12,17 +12,8 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
-import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
-import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
-import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.experimental.graphinfo.GraphNode;
-import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
-import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Descriptor;
@@ -58,24 +49,19 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.shaking.EnqueuerWorklist.Action;
+import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
-import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -84,7 +70,6 @@
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.lang.reflect.InvocationHandler;
 import java.util.ArrayDeque;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
@@ -164,14 +149,6 @@
 
   private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
 
-  // Canonicalization of external graph-nodes and edge info.
-  private final Map<DexItem, AnnotationGraphNode> annotationNodes = new IdentityHashMap<>();
-  private final Map<DexType, ClassGraphNode> classNodes = new IdentityHashMap<>();
-  private final Map<DexMethod, MethodGraphNode> methodNodes = new IdentityHashMap<>();
-  private final Map<DexField, FieldGraphNode> fieldNodes = new IdentityHashMap<>();
-  private final Map<ProguardKeepRuleBase, KeepRuleGraphNode> ruleNodes = new IdentityHashMap<>();
-  private final Map<EdgeKind, GraphEdgeInfo> reasonInfo = new IdentityHashMap<>();
-
   /**
    * Set of method signatures used in invoke-super instructions that either cannot be resolved or
    * resolve to a private method (leading to an IllegalAccessError).
@@ -199,7 +176,7 @@
    * Set of types that are mentioned in the program. We at least need an empty abstract class item
    * for these.
    */
-  private final SetWithReason<DexProgramClass> liveTypes = new SetWithReason<>(this::registerClass);
+  private final SetWithReason<DexProgramClass> liveTypes;
 
   /** Set of live types defined in the library and classpath. Used to avoid duplicate tracing. */
   private final Set<DexClass> liveNonProgramTypes = Sets.newIdentityHashSet();
@@ -213,12 +190,10 @@
   private final Set<DexType> skippedProtoExtensionTypes = Sets.newIdentityHashSet();
 
   /** Set of annotation types that are instantiated. */
-  private final SetWithReason<DexAnnotation> liveAnnotations =
-      new SetWithReason<>(this::registerAnnotation);
+  private final SetWithReason<DexAnnotation> liveAnnotations;
 
   /** Set of types that are actually instantiated. These cannot be abstract. */
-  private final SetWithReason<DexProgramClass> instantiatedTypes =
-      new SetWithReason<>(this::registerClass);
+  private final SetWithReason<DexProgramClass> instantiatedTypes;
 
   /** Set of all types that are instantiated, directly or indirectly, thus may be abstract. */
   private final Set<DexProgramClass> directAndIndirectlyInstantiatedTypes =
@@ -229,8 +204,7 @@
    * are required so that invokes can find the method. If a method is only a target but not live,
    * its implementation may be removed and it may be marked abstract.
    */
-  private final SetWithReason<DexEncodedMethod> targetedMethods =
-      new SetWithReason<>(this::registerMethod);
+  private final SetWithReason<DexEncodedMethod> targetedMethods;
   /**
    * Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
    */
@@ -251,14 +225,12 @@
    * Set of methods that belong to live classes and can be reached by invokes. These need to be
    * kept.
    */
-  private final SetWithReason<DexEncodedMethod> liveMethods =
-      new SetWithReason<>(this::registerMethod);
+  private final SetWithReason<DexEncodedMethod> liveMethods;
 
   /**
    * Set of fields that belong to live classes and can be reached by invokes. These need to be kept.
    */
-  private final SetWithReason<DexEncodedField> liveFields =
-      new SetWithReason<>(this::registerField);
+  private final SetWithReason<DexEncodedField> liveFields;
 
   /**
    * Set of service types (from META-INF/services/) that may have been instantiated reflectively via
@@ -270,8 +242,7 @@
    * Set of interface types for which there may be instantiations, such as lambda expressions or
    * explicit keep rules.
    */
-  private final SetWithReason<DexProgramClass> instantiatedInterfaceTypes =
-      new SetWithReason<>(this::registerInterface);
+  private final SetWithReason<DexProgramClass> instantiatedInterfaceTypes;
 
   /** A queue of items that need processing. Different items trigger different actions. */
   private final EnqueuerWorklist workList;
@@ -314,8 +285,6 @@
       new IdentityHashMap<>();
 
   private final GraphReporter graphReporter;
-  private final GraphConsumer keptGraphConsumer;
-  private CollectingGraphConsumer verificationGraphConsumer = null;
 
   Enqueuer(
       AppView<? extends AppInfoWithSubtyping> appView,
@@ -326,8 +295,7 @@
     this.appInfo = appView.appInfo();
     this.appView = appView;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
-    this.graphReporter = new GraphReporter();
-    this.keptGraphConsumer = recordKeptGraph(options, keptGraphConsumer);
+    this.graphReporter = new GraphReporter(appView, keptGraphConsumer);
     this.mode = mode;
     this.options = options;
     this.workList = EnqueuerWorklist.createWorklist(appView);
@@ -335,12 +303,24 @@
     if (options.enableGeneratedMessageLiteShrinking && mode.isInitialOrFinalTreeShaking()) {
       registerAnalysis(new ProtoEnqueuerExtension(appView));
     }
+
+    liveTypes = new SetWithReason<>(graphReporter::registerClass);
+    liveAnnotations = new SetWithReason<>(graphReporter::registerAnnotation);
+    instantiatedTypes = new SetWithReason<>(graphReporter::registerClass);
+    targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
+    liveMethods = new SetWithReason<>(graphReporter::registerMethod);
+    liveFields = new SetWithReason<>(graphReporter::registerField);
+    instantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
   }
 
   public Mode getMode() {
     return mode;
   }
 
+  public GraphReporter getGraphReporter() {
+    return graphReporter;
+  }
+
   public Enqueuer registerAnalysis(EnqueuerAnalysis analysis) {
     this.analyses.add(analysis);
     return this;
@@ -1315,7 +1295,7 @@
 
   private void registerClassInitializer(DexProgramClass definition, KeepReason reason) {
     if (definition.hasClassInitializer()) {
-      registerMethod(definition.getClassInitializer(), reason);
+      graphReporter.registerMethod(definition.getClassInitializer(), reason);
     }
   }
 
@@ -1850,7 +1830,7 @@
     MarkedResolutionTarget resolution = virtualTargetsMarkedAsReachable.get(method);
     if (resolution != null) {
       if (!resolution.isUnresolved()) {
-        registerMethod(resolution.method, reason);
+        graphReporter.registerMethod(resolution.method, reason);
       }
       return;
     }
@@ -2126,47 +2106,15 @@
     return createAppInfo(appInfo);
   }
 
-  private GraphConsumer recordKeptGraph(InternalOptions options, GraphConsumer consumer) {
-    if (options.testing.verifyKeptGraphInfo) {
-      verificationGraphConsumer = new CollectingGraphConsumer(consumer);
-      return verificationGraphConsumer;
-    }
-    return consumer;
-  }
-
-  private boolean verifyKeptGraph() {
-    if (options.testing.verifyKeptGraphInfo) {
-      assert verificationGraphConsumer != null;
-      for (DexProgramClass liveType : liveTypes.items) {
-        assert verifyRootedPath(liveType, verificationGraphConsumer);
+  public boolean verifyKeptGraph() {
+    if (appView.options().testing.verifyKeptGraphInfo) {
+      for (DexProgramClass liveType : liveTypes.getItems()) {
+        assert graphReporter.verifyRootedPath(liveType);
       }
     }
     return true;
   }
 
-  private boolean verifyRootedPath(DexProgramClass liveType, CollectingGraphConsumer graph) {
-    ClassGraphNode node = getClassGraphNode(liveType.type);
-    Set<GraphNode> seen = Sets.newIdentityHashSet();
-    Deque<GraphNode> targets = DequeUtils.newArrayDeque(node);
-    while (!targets.isEmpty()) {
-      GraphNode item = targets.pop();
-      if (item instanceof KeepRuleGraphNode) {
-        KeepRuleGraphNode rule = (KeepRuleGraphNode) item;
-        if (rule.getPreconditions().isEmpty()) {
-          return true;
-        }
-      }
-      if (seen.add(item)) {
-        Map<GraphNode, Set<GraphEdgeInfo>> sources = graph.getSourcesTargeting(item);
-        assert sources != null : "No sources set for " + item;
-        assert !sources.isEmpty() : "Empty sources set for " + item;
-        targets.addAll(sources.keySet());
-      }
-    }
-    assert false : "No rooted path to " + liveType.type;
-    return true;
-  }
-
   private AppInfoWithLiveness createAppInfo(AppInfoWithSubtyping appInfo) {
     ImmutableSortedSet.Builder<DexType> builder =
         ImmutableSortedSet.orderedBy(PresortedComparable::slowCompareTo);
@@ -2921,7 +2869,7 @@
     }
   }
 
-  private static class MarkedResolutionTarget {
+  public static class MarkedResolutionTarget {
 
     private static final MarkedResolutionTarget UNRESOLVED = new MarkedResolutionTarget(null, null);
 
@@ -3120,402 +3068,4 @@
     }
   }
 
-  class GraphReporter {
-
-    private EdgeKind reportPrecondition(KeepRuleGraphNode keepRuleGraphNode) {
-      if (keepRuleGraphNode.getPreconditions().isEmpty()) {
-        return EdgeKind.KeepRule;
-      }
-      for (GraphNode precondition : keepRuleGraphNode.getPreconditions()) {
-        reportEdge(precondition, keepRuleGraphNode, EdgeKind.KeepRulePrecondition);
-      }
-      return EdgeKind.ConditionalKeepRule;
-    }
-
-    KeepReasonWitness reportKeepClass(
-        DexDefinition precondition, ProguardKeepRuleBase rule, DexProgramClass clazz) {
-      if (keptGraphConsumer == null) {
-        return KeepReasonWitness.INSTANCE;
-      }
-      KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
-      EdgeKind edgeKind = reportPrecondition(ruleNode);
-      return reportEdge(ruleNode, getClassGraphNode(clazz.type), edgeKind);
-    }
-
-    KeepReasonWitness reportKeepClass(
-        DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexProgramClass clazz) {
-      assert !rules.isEmpty();
-      if (keptGraphConsumer != null) {
-        for (ProguardKeepRuleBase rule : rules) {
-          reportKeepClass(precondition, rule, clazz);
-        }
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    KeepReasonWitness reportKeepMethod(
-        DexDefinition precondition, ProguardKeepRuleBase rule, DexEncodedMethod method) {
-      if (keptGraphConsumer == null) {
-        return KeepReasonWitness.INSTANCE;
-      }
-      KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
-      EdgeKind edgeKind = reportPrecondition(ruleNode);
-      return reportEdge(ruleNode, getMethodGraphNode(method.method), edgeKind);
-    }
-
-    KeepReasonWitness reportKeepMethod(
-        DexDefinition precondition,
-        Collection<ProguardKeepRuleBase> rules,
-        DexEncodedMethod method) {
-      assert !rules.isEmpty();
-      if (keptGraphConsumer != null) {
-        for (ProguardKeepRuleBase rule : rules) {
-          reportKeepMethod(precondition, rule, method);
-        }
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    KeepReasonWitness reportKeepField(
-        DexDefinition precondition, ProguardKeepRuleBase rule, DexEncodedField field) {
-      if (keptGraphConsumer == null) {
-        return KeepReasonWitness.INSTANCE;
-      }
-      KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
-      EdgeKind edgeKind = reportPrecondition(ruleNode);
-      return reportEdge(ruleNode, getFieldGraphNode(field.field), edgeKind);
-    }
-
-    KeepReasonWitness reportKeepField(
-        DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexEncodedField field) {
-      assert !rules.isEmpty();
-      if (keptGraphConsumer != null) {
-        for (ProguardKeepRuleBase rule : rules) {
-          reportKeepField(precondition, rule, field);
-        }
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    public KeepReasonWitness reportCompatKeepDefaultInitializer(
-        DexProgramClass holder, DexEncodedMethod defaultInitializer) {
-      assert holder.type == defaultInitializer.method.holder;
-      assert holder.getDefaultInitializer() == defaultInitializer;
-      if (keptGraphConsumer != null) {
-        reportEdge(
-            getClassGraphNode(holder.type),
-            getMethodGraphNode(defaultInitializer.method),
-            EdgeKind.CompatibilityRule);
-      }
-      return KeepReasonWitness.COMPAT_INSTANCE;
-    }
-
-    public KeepReasonWitness reportCompatKeepMethod(
-        DexProgramClass holder, DexEncodedMethod method) {
-      assert holder.type == method.method.holder;
-      // TODO(b/141729349): This compat rule is from the method to itself and has not edge. Fix it.
-      // The rule is stating that if the method is targeted it is live. Since such an edge does
-      // not contribute to additional information in the kept graph as it stands (no distinction
-      // of targeted vs live edges), there is little point in emitting it.
-      return KeepReasonWitness.COMPAT_INSTANCE;
-    }
-
-    public KeepReason reportCompatInstantiated(
-        DexProgramClass instantiated, DexEncodedMethod method) {
-      if (keptGraphConsumer != null) {
-        reportEdge(
-            getMethodGraphNode(method.method),
-            getClassGraphNode(instantiated.type),
-            EdgeKind.CompatibilityRule);
-      }
-      return KeepReasonWitness.COMPAT_INSTANCE;
-    }
-
-    public KeepReasonWitness reportClassReferencedFrom(
-        DexProgramClass clazz, DexEncodedMethod method) {
-      if (keptGraphConsumer != null) {
-        MethodGraphNode source = getMethodGraphNode(method.method);
-        ClassGraphNode target = getClassGraphNode(clazz.type);
-        return reportEdge(source, target, EdgeKind.ReferencedFrom);
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    public KeepReasonWitness reportClassReferencedFrom(
-        DexProgramClass clazz, DexEncodedField field) {
-      if (keptGraphConsumer != null) {
-        FieldGraphNode source = getFieldGraphNode(field.field);
-        ClassGraphNode target = getClassGraphNode(clazz.type);
-        return reportEdge(source, target, EdgeKind.ReferencedFrom);
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    public KeepReasonWitness reportReachableMethodAsLive(
-        DexEncodedMethod encodedMethod, MarkedResolutionTarget reason) {
-      if (keptGraphConsumer != null) {
-        return reportEdge(
-            getMethodGraphNode(reason.method.method),
-            getMethodGraphNode(encodedMethod.method),
-            EdgeKind.OverridingMethod);
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    public KeepReasonWitness reportReachableMethodAsLive(
-        DexEncodedMethod encodedMethod, Set<MarkedResolutionTarget> reasons) {
-      assert !reasons.isEmpty();
-      if (keptGraphConsumer != null) {
-        MethodGraphNode target = getMethodGraphNode(encodedMethod.method);
-        for (MarkedResolutionTarget reason : reasons) {
-          reportEdge(getMethodGraphNode(reason.method.method), target, EdgeKind.OverridingMethod);
-        }
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    private KeepReason reportCompanionClass(DexProgramClass iface, DexProgramClass companion) {
-      assert iface.isInterface();
-      assert InterfaceMethodRewriter.isCompanionClassType(companion.type);
-      if (keptGraphConsumer == null) {
-        return KeepReasonWitness.INSTANCE;
-      }
-      return reportEdge(
-          getClassGraphNode(iface.type),
-          getClassGraphNode(companion.type),
-          EdgeKind.CompanionClass);
-    }
-
-    private KeepReason reportCompanionMethod(
-        DexEncodedMethod definition, DexEncodedMethod implementation) {
-      assert InterfaceMethodRewriter.isCompanionClassType(implementation.method.holder);
-      if (keptGraphConsumer == null) {
-        return KeepReasonWitness.INSTANCE;
-      }
-      return reportEdge(
-          getMethodGraphNode(definition.method),
-          getMethodGraphNode(implementation.method),
-          EdgeKind.CompanionMethod);
-    }
-
-    private KeepReasonWitness reportEdge(
-        GraphNode source, GraphNode target, GraphEdgeInfo.EdgeKind kind) {
-      assert keptGraphConsumer != null;
-      keptGraphConsumer.acceptEdge(source, target, getEdgeInfo(kind));
-      return KeepReasonWitness.INSTANCE;
-    }
-
-  }
-
-  /**
-   * Sentinel value indicating that a keep reason has been reported.
-   *
-   * <p>Should only ever be returned by the register* function below.
-   */
-  private static class KeepReasonWitness extends KeepReason {
-
-    private static KeepReasonWitness INSTANCE = new KeepReasonWitness();
-    private static KeepReasonWitness COMPAT_INSTANCE =
-        new KeepReasonWitness() {
-          @Override
-          public boolean isDueToProguardCompatibility() {
-            return true;
-          }
-        };
-
-    @Override
-    public EdgeKind edgeKind() {
-      throw new Unreachable();
-    }
-
-    @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      throw new Unreachable();
-    }
-  }
-
-  private boolean skipReporting(KeepReason reason) {
-    assert reason != null;
-    if (reason == KeepReasonWitness.INSTANCE || reason == KeepReasonWitness.COMPAT_INSTANCE) {
-      return true;
-    }
-    assert getSourceNode(reason) != null;
-    return keptGraphConsumer == null;
-  }
-
-  private KeepReasonWitness registerType(DexType type, KeepReason reason) {
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getClassGraphNode(type), reason);
-  }
-
-  private KeepReasonWitness registerInterface(DexProgramClass iface, KeepReason reason) {
-    assert iface.isInterface();
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getClassGraphNode(iface.type), reason);
-  }
-
-  private KeepReasonWitness registerClass(DexProgramClass clazz, KeepReason reason) {
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getClassGraphNode(clazz.type), reason);
-  }
-
-  private KeepReasonWitness registerAnnotation(DexAnnotation annotation, KeepReason reason) {
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getAnnotationGraphNode(annotation.annotation.type), reason);
-  }
-
-  private KeepReasonWitness registerMethod(DexEncodedMethod method, KeepReason reason) {
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    if (reason.edgeKind() == EdgeKind.IsLibraryMethod && isNonProgramClass(method.method.holder)) {
-      // Don't report edges to actual library methods.
-      // TODO(b/120959039): This should be dead code once no library classes are ever enqueued.
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getMethodGraphNode(method.method), reason);
-  }
-
-  private KeepReasonWitness registerField(DexEncodedField field, KeepReason reason) {
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getFieldGraphNode(field.field), reason);
-  }
-
-  private KeepReasonWitness registerEdge(GraphNode target, KeepReason reason) {
-    assert !skipReporting(reason);
-    GraphNode sourceNode = getSourceNode(reason);
-    // TODO(b/120959039): Make sure we do have edges to nodes deriving library nodes!
-    if (!sourceNode.isLibraryNode()) {
-      GraphEdgeInfo edgeInfo = getEdgeInfo(reason);
-      keptGraphConsumer.acceptEdge(sourceNode, target, edgeInfo);
-    }
-    return KeepReasonWitness.INSTANCE;
-  }
-
-  private boolean isNonProgramClass(DexType type) {
-    DexClass clazz = appView.definitionFor(type);
-    return clazz == null || clazz.isNotProgramClass();
-  }
-
-  private GraphNode getSourceNode(KeepReason reason) {
-    return reason.getSourceNode(this);
-  }
-
-  public GraphNode getGraphNode(DexReference reference) {
-    if (reference.isDexType()) {
-      return getClassGraphNode(reference.asDexType());
-    }
-    if (reference.isDexMethod()) {
-      return getMethodGraphNode(reference.asDexMethod());
-    }
-    if (reference.isDexField()) {
-      return getFieldGraphNode(reference.asDexField());
-    }
-    throw new Unreachable();
-  }
-
-  GraphEdgeInfo getEdgeInfo(KeepReason reason) {
-    return getEdgeInfo(reason.edgeKind());
-  }
-
-  GraphEdgeInfo getEdgeInfo(GraphEdgeInfo.EdgeKind kind) {
-    return reasonInfo.computeIfAbsent(kind, k -> new GraphEdgeInfo(k));
-  }
-
-  AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
-    return annotationNodes.computeIfAbsent(type, t -> {
-      if (t instanceof DexType) {
-        return new AnnotationGraphNode(getClassGraphNode(((DexType) t)));
-      }
-      throw new Unimplemented("Incomplete support for annotation node on item: " + type.getClass());
-    });
-  }
-
-  ClassGraphNode getClassGraphNode(DexType type) {
-    return classNodes.computeIfAbsent(
-        type,
-        t -> {
-          DexClass definition = appView.definitionFor(t);
-          return new ClassGraphNode(
-              definition != null && definition.isNotProgramClass(),
-              Reference.classFromDescriptor(t.toDescriptorString()));
-        });
-  }
-
-  MethodGraphNode getMethodGraphNode(DexMethod context) {
-    return methodNodes.computeIfAbsent(
-        context,
-        m -> {
-          DexClass holderDefinition = appView.definitionFor(context.holder);
-          Builder<TypeReference> builder = ImmutableList.builder();
-          for (DexType param : m.proto.parameters.values) {
-            builder.add(Reference.typeFromDescriptor(param.toDescriptorString()));
-          }
-          return new MethodGraphNode(
-              holderDefinition != null && holderDefinition.isNotProgramClass(),
-              Reference.method(
-                  Reference.classFromDescriptor(m.holder.toDescriptorString()),
-                  m.name.toString(),
-                  builder.build(),
-                  m.proto.returnType.isVoidType()
-                      ? null
-                      : Reference.typeFromDescriptor(m.proto.returnType.toDescriptorString())));
-        });
-  }
-
-  FieldGraphNode getFieldGraphNode(DexField context) {
-    return fieldNodes.computeIfAbsent(
-        context,
-        f -> {
-          DexClass holderDefinition = appView.definitionFor(context.holder);
-          return new FieldGraphNode(
-              holderDefinition != null && holderDefinition.isNotProgramClass(),
-              Reference.field(
-                  Reference.classFromDescriptor(f.holder.toDescriptorString()),
-                  f.name.toString(),
-                  Reference.typeFromDescriptor(f.type.toDescriptorString())));
-        });
-  }
-
-  // Due to the combined encoding of dependent rules, ala keepclassmembers and conditional keep
-  // rules the conversion to a keep-rule graph node can be one of three forms:
-  // 1. A non-dependent keep rule. In this case precondtion == null and the rule is not an if-rule.
-  // 2. A dependent keep rule. In this case precondtion != null and rule is not an if-rule.
-  // 3. A conditional keep rule. In this case rule is an if-rule, but precondition may or may not be
-  //    null. In the non-null case, the precondition is the type the consequent may depend on,
-  //    say T for the consequent "-keep T { f; }". It is *not* the precondition of the conditional
-  //    rule.
-  KeepRuleGraphNode getKeepRuleGraphNode(DexDefinition precondition, ProguardKeepRuleBase rule) {
-    if (rule instanceof ProguardKeepRule) {
-      Set<GraphNode> preconditions =
-          precondition != null
-              ? Collections.singleton(getGraphNode(precondition.toReference()))
-              : Collections.emptySet();
-      return ruleNodes.computeIfAbsent(rule, key -> new KeepRuleGraphNode(rule, preconditions));
-    }
-    if (rule instanceof ProguardIfRule) {
-      ProguardIfRule ifRule = (ProguardIfRule) rule;
-      assert !ifRule.getPreconditions().isEmpty();
-      return ruleNodes.computeIfAbsent(
-          ifRule,
-          key -> {
-            Set<GraphNode> preconditions = new HashSet<>(ifRule.getPreconditions().size());
-            for (DexReference condition : ifRule.getPreconditions()) {
-              preconditions.add(getGraphNode(condition));
-            }
-            return new KeepRuleGraphNode(ifRule, preconditions);
-          });
-    }
-    throw new Unreachable("Unexpected type of keep rule: " + rule);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
new file mode 100644
index 0000000..e87abf0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -0,0 +1,482 @@
+// 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.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
+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.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.shaking.Enqueuer.MarkedResolutionTarget;
+import com.android.tools.r8.utils.DequeUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class GraphReporter {
+
+  private final AppView<?> appView;
+  private final GraphConsumer keptGraphConsumer;
+  private final CollectingGraphConsumer verificationGraphConsumer;
+
+  // Canonicalization of external graph-nodes and edge info.
+  private final Map<DexItem, AnnotationGraphNode> annotationNodes = new IdentityHashMap<>();
+  private final Map<DexType, ClassGraphNode> classNodes = new IdentityHashMap<>();
+  private final Map<DexMethod, MethodGraphNode> methodNodes = new IdentityHashMap<>();
+  private final Map<DexField, FieldGraphNode> fieldNodes = new IdentityHashMap<>();
+  private final Map<ProguardKeepRuleBase, KeepRuleGraphNode> ruleNodes = new IdentityHashMap<>();
+  private final Map<EdgeKind, GraphEdgeInfo> reasonInfo = new IdentityHashMap<>();
+
+  GraphReporter(AppView<?> appView, GraphConsumer keptGraphConsumer) {
+    this.appView = appView;
+    if (appView.options().testing.verifyKeptGraphInfo) {
+      this.verificationGraphConsumer = new CollectingGraphConsumer(keptGraphConsumer);
+      this.keptGraphConsumer = verificationGraphConsumer;
+    } else {
+      this.verificationGraphConsumer = null;
+      this.keptGraphConsumer = keptGraphConsumer;
+    }
+  }
+
+  public boolean verifyRootedPath(DexProgramClass liveType) {
+    assert verificationGraphConsumer != null;
+    ClassGraphNode node = getClassGraphNode(liveType.type);
+    Set<GraphNode> seen = Sets.newIdentityHashSet();
+    Deque<GraphNode> targets = DequeUtils.newArrayDeque(node);
+    while (!targets.isEmpty()) {
+      GraphNode item = targets.pop();
+      if (item instanceof KeepRuleGraphNode) {
+        KeepRuleGraphNode rule = (KeepRuleGraphNode) item;
+        if (rule.getPreconditions().isEmpty()) {
+          return true;
+        }
+      }
+      if (seen.add(item)) {
+        Map<GraphNode, Set<GraphEdgeInfo>> sources =
+            verificationGraphConsumer.getSourcesTargeting(item);
+        assert sources != null : "No sources set for " + item;
+        assert !sources.isEmpty() : "Empty sources set for " + item;
+        targets.addAll(sources.keySet());
+      }
+    }
+    assert false : "No rooted path to " + liveType.type;
+    return false;
+  }
+
+  private EdgeKind reportPrecondition(KeepRuleGraphNode keepRuleGraphNode) {
+    if (keepRuleGraphNode.getPreconditions().isEmpty()) {
+      return EdgeKind.KeepRule;
+    }
+    for (GraphNode precondition : keepRuleGraphNode.getPreconditions()) {
+      reportEdge(precondition, keepRuleGraphNode, EdgeKind.KeepRulePrecondition);
+    }
+    return EdgeKind.ConditionalKeepRule;
+  }
+
+  KeepReasonWitness reportKeepClass(
+      DexDefinition precondition, ProguardKeepRuleBase rule, DexProgramClass clazz) {
+    if (keptGraphConsumer == null) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
+    EdgeKind edgeKind = reportPrecondition(ruleNode);
+    return reportEdge(ruleNode, getClassGraphNode(clazz.type), edgeKind);
+  }
+
+  KeepReasonWitness reportKeepClass(
+      DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexProgramClass clazz) {
+    assert !rules.isEmpty();
+    if (keptGraphConsumer != null) {
+      for (ProguardKeepRuleBase rule : rules) {
+        reportKeepClass(precondition, rule, clazz);
+      }
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  KeepReasonWitness reportKeepMethod(
+      DexDefinition precondition, ProguardKeepRuleBase rule, DexEncodedMethod method) {
+    if (keptGraphConsumer == null) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
+    EdgeKind edgeKind = reportPrecondition(ruleNode);
+    return reportEdge(ruleNode, getMethodGraphNode(method.method), edgeKind);
+  }
+
+  KeepReasonWitness reportKeepMethod(
+      DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexEncodedMethod method) {
+    assert !rules.isEmpty();
+    if (keptGraphConsumer != null) {
+      for (ProguardKeepRuleBase rule : rules) {
+        reportKeepMethod(precondition, rule, method);
+      }
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  KeepReasonWitness reportKeepField(
+      DexDefinition precondition, ProguardKeepRuleBase rule, DexEncodedField field) {
+    if (keptGraphConsumer == null) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
+    EdgeKind edgeKind = reportPrecondition(ruleNode);
+    return reportEdge(ruleNode, getFieldGraphNode(field.field), edgeKind);
+  }
+
+  KeepReasonWitness reportKeepField(
+      DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexEncodedField field) {
+    assert !rules.isEmpty();
+    if (keptGraphConsumer != null) {
+      for (ProguardKeepRuleBase rule : rules) {
+        reportKeepField(precondition, rule, field);
+      }
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportCompatKeepDefaultInitializer(
+      DexProgramClass holder, DexEncodedMethod defaultInitializer) {
+    assert holder.type == defaultInitializer.method.holder;
+    assert holder.getDefaultInitializer() == defaultInitializer;
+    if (keptGraphConsumer != null) {
+      reportEdge(
+          getClassGraphNode(holder.type),
+          getMethodGraphNode(defaultInitializer.method),
+          EdgeKind.CompatibilityRule);
+    }
+    return KeepReasonWitness.COMPAT_INSTANCE;
+  }
+
+  public KeepReasonWitness reportCompatKeepMethod(DexProgramClass holder, DexEncodedMethod method) {
+    assert holder.type == method.method.holder;
+    // TODO(b/141729349): This compat rule is from the method to itself and has not edge. Fix it.
+    // The rule is stating that if the method is targeted it is live. Since such an edge does
+    // not contribute to additional information in the kept graph as it stands (no distinction
+    // of targeted vs live edges), there is little point in emitting it.
+    return KeepReasonWitness.COMPAT_INSTANCE;
+  }
+
+  public KeepReason reportCompatInstantiated(
+      DexProgramClass instantiated, DexEncodedMethod method) {
+    if (keptGraphConsumer != null) {
+      reportEdge(
+          getMethodGraphNode(method.method),
+          getClassGraphNode(instantiated.type),
+          EdgeKind.CompatibilityRule);
+    }
+    return KeepReasonWitness.COMPAT_INSTANCE;
+  }
+
+  public KeepReasonWitness reportClassReferencedFrom(
+      DexProgramClass clazz, DexEncodedMethod method) {
+    if (keptGraphConsumer != null) {
+      MethodGraphNode source = getMethodGraphNode(method.method);
+      ClassGraphNode target = getClassGraphNode(clazz.type);
+      return reportEdge(source, target, EdgeKind.ReferencedFrom);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportClassReferencedFrom(DexProgramClass clazz, DexEncodedField field) {
+    if (keptGraphConsumer != null) {
+      FieldGraphNode source = getFieldGraphNode(field.field);
+      ClassGraphNode target = getClassGraphNode(clazz.type);
+      return reportEdge(source, target, EdgeKind.ReferencedFrom);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportReachableMethodAsLive(
+      DexEncodedMethod encodedMethod, MarkedResolutionTarget reason) {
+    if (keptGraphConsumer != null) {
+      return reportEdge(
+          getMethodGraphNode(reason.method.method),
+          getMethodGraphNode(encodedMethod.method),
+          EdgeKind.OverridingMethod);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportReachableMethodAsLive(
+      DexEncodedMethod encodedMethod, Set<MarkedResolutionTarget> reasons) {
+    assert !reasons.isEmpty();
+    if (keptGraphConsumer != null) {
+      MethodGraphNode target = getMethodGraphNode(encodedMethod.method);
+      for (MarkedResolutionTarget reason : reasons) {
+        reportEdge(getMethodGraphNode(reason.method.method), target, EdgeKind.OverridingMethod);
+      }
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReason reportCompanionClass(DexProgramClass iface, DexProgramClass companion) {
+    assert iface.isInterface();
+    assert InterfaceMethodRewriter.isCompanionClassType(companion.type);
+    if (keptGraphConsumer == null) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return reportEdge(
+        getClassGraphNode(iface.type), getClassGraphNode(companion.type), EdgeKind.CompanionClass);
+  }
+
+  public KeepReason reportCompanionMethod(
+      DexEncodedMethod definition, DexEncodedMethod implementation) {
+    assert InterfaceMethodRewriter.isCompanionClassType(implementation.method.holder);
+    if (keptGraphConsumer == null) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return reportEdge(
+        getMethodGraphNode(definition.method),
+        getMethodGraphNode(implementation.method),
+        EdgeKind.CompanionMethod);
+  }
+
+  private KeepReasonWitness reportEdge(GraphNode source, GraphNode target, EdgeKind kind) {
+    assert keptGraphConsumer != null;
+    keptGraphConsumer.acceptEdge(source, target, getEdgeInfo(kind));
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  /**
+   * Sentinel value indicating that a keep reason has been reported.
+   *
+   * <p>Should only ever be returned by the graph reporter functions.
+   */
+  public static class KeepReasonWitness extends KeepReason {
+
+    private static KeepReasonWitness INSTANCE = new KeepReasonWitness();
+    private static KeepReasonWitness COMPAT_INSTANCE =
+        new KeepReasonWitness() {
+          @Override
+          public boolean isDueToProguardCompatibility() {
+            return true;
+          }
+        };
+
+    private KeepReasonWitness() {
+      // Only the reporter may create instances.
+    }
+
+    @Override
+    public EdgeKind edgeKind() {
+      throw new Unreachable();
+    }
+
+    @Override
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
+      throw new Unreachable();
+    }
+  }
+
+  private boolean skipReporting(KeepReason reason) {
+    assert reason != null;
+    if (reason == KeepReasonWitness.INSTANCE || reason == KeepReasonWitness.COMPAT_INSTANCE) {
+      return true;
+    }
+    assert getSourceNode(reason) != null;
+    return keptGraphConsumer == null;
+  }
+
+  public KeepReasonWitness registerInterface(DexProgramClass iface, KeepReason reason) {
+    assert iface.isInterface();
+    if (skipReporting(reason)) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return registerEdge(getClassGraphNode(iface.type), reason);
+  }
+
+  public KeepReasonWitness registerClass(DexProgramClass clazz, KeepReason reason) {
+    if (skipReporting(reason)) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return registerEdge(getClassGraphNode(clazz.type), reason);
+  }
+
+  public KeepReasonWitness registerAnnotation(DexAnnotation annotation, KeepReason reason) {
+    if (skipReporting(reason)) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return registerEdge(getAnnotationGraphNode(annotation.annotation.type), reason);
+  }
+
+  public KeepReasonWitness registerMethod(DexEncodedMethod method, KeepReason reason) {
+    if (skipReporting(reason)) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    if (reason.edgeKind() == EdgeKind.IsLibraryMethod && isNonProgramClass(method.method.holder)) {
+      // Don't report edges to actual library methods.
+      // TODO(b/120959039): This should be dead code once no library classes are ever enqueued.
+      return KeepReasonWitness.INSTANCE;
+    }
+    return registerEdge(getMethodGraphNode(method.method), reason);
+  }
+
+  public KeepReasonWitness registerField(DexEncodedField field, KeepReason reason) {
+    if (skipReporting(reason)) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return registerEdge(getFieldGraphNode(field.field), reason);
+  }
+
+  private KeepReasonWitness registerEdge(GraphNode target, KeepReason reason) {
+    assert !skipReporting(reason);
+    GraphNode sourceNode = getSourceNode(reason);
+    // TODO(b/120959039): Make sure we do have edges to nodes deriving library nodes!
+    if (!sourceNode.isLibraryNode()) {
+      GraphEdgeInfo edgeInfo = getEdgeInfo(reason);
+      keptGraphConsumer.acceptEdge(sourceNode, target, edgeInfo);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  private boolean isNonProgramClass(DexType type) {
+    DexClass clazz = appView.definitionFor(type);
+    return clazz == null || clazz.isNotProgramClass();
+  }
+
+  private GraphNode getSourceNode(KeepReason reason) {
+    return reason.getSourceNode(this);
+  }
+
+  public GraphNode getGraphNode(DexReference reference) {
+    if (reference.isDexType()) {
+      return getClassGraphNode(reference.asDexType());
+    }
+    if (reference.isDexMethod()) {
+      return getMethodGraphNode(reference.asDexMethod());
+    }
+    if (reference.isDexField()) {
+      return getFieldGraphNode(reference.asDexField());
+    }
+    throw new Unreachable();
+  }
+
+  GraphEdgeInfo getEdgeInfo(KeepReason reason) {
+    return getEdgeInfo(reason.edgeKind());
+  }
+
+  GraphEdgeInfo getEdgeInfo(EdgeKind kind) {
+    return reasonInfo.computeIfAbsent(kind, k -> new GraphEdgeInfo(k));
+  }
+
+  AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
+    return annotationNodes.computeIfAbsent(
+        type,
+        t -> {
+          if (t instanceof DexType) {
+            return new AnnotationGraphNode(getClassGraphNode(((DexType) t)));
+          }
+          throw new Unimplemented(
+              "Incomplete support for annotation node on item: " + type.getClass());
+        });
+  }
+
+  ClassGraphNode getClassGraphNode(DexType type) {
+    return classNodes.computeIfAbsent(
+        type,
+        t -> {
+          DexClass definition = appView.definitionFor(t);
+          return new ClassGraphNode(
+              definition != null && definition.isNotProgramClass(),
+              Reference.classFromDescriptor(t.toDescriptorString()));
+        });
+  }
+
+  MethodGraphNode getMethodGraphNode(DexMethod context) {
+    return methodNodes.computeIfAbsent(
+        context,
+        m -> {
+          DexClass holderDefinition = appView.definitionFor(context.holder);
+          Builder<TypeReference> builder = ImmutableList.builder();
+          for (DexType param : m.proto.parameters.values) {
+            builder.add(Reference.typeFromDescriptor(param.toDescriptorString()));
+          }
+          return new MethodGraphNode(
+              holderDefinition != null && holderDefinition.isNotProgramClass(),
+              Reference.method(
+                  Reference.classFromDescriptor(m.holder.toDescriptorString()),
+                  m.name.toString(),
+                  builder.build(),
+                  m.proto.returnType.isVoidType()
+                      ? null
+                      : Reference.typeFromDescriptor(m.proto.returnType.toDescriptorString())));
+        });
+  }
+
+  FieldGraphNode getFieldGraphNode(DexField context) {
+    return fieldNodes.computeIfAbsent(
+        context,
+        f -> {
+          DexClass holderDefinition = appView.definitionFor(context.holder);
+          return new FieldGraphNode(
+              holderDefinition != null && holderDefinition.isNotProgramClass(),
+              Reference.field(
+                  Reference.classFromDescriptor(f.holder.toDescriptorString()),
+                  f.name.toString(),
+                  Reference.typeFromDescriptor(f.type.toDescriptorString())));
+        });
+  }
+
+  // Due to the combined encoding of dependent rules, ala keepclassmembers and conditional keep
+  // rules the conversion to a keep-rule graph node can be one of three forms:
+  // 1. A non-dependent keep rule. In this case precondtion == null and the rule is not an if-rule.
+  // 2. A dependent keep rule. In this case precondtion != null and rule is not an if-rule.
+  // 3. A conditional keep rule. In this case rule is an if-rule, but precondition may or may not be
+  //    null. In the non-null case, the precondition is the type the consequent may depend on,
+  //    say T for the consequent "-keep T { f; }". It is *not* the precondition of the conditional
+  //    rule.
+  KeepRuleGraphNode getKeepRuleGraphNode(DexDefinition precondition, ProguardKeepRuleBase rule) {
+    if (rule instanceof ProguardKeepRule) {
+      Set<GraphNode> preconditions =
+          precondition != null
+              ? Collections.singleton(getGraphNode(precondition.toReference()))
+              : Collections.emptySet();
+      return ruleNodes.computeIfAbsent(rule, key -> new KeepRuleGraphNode(rule, preconditions));
+    }
+    if (rule instanceof ProguardIfRule) {
+      ProguardIfRule ifRule = (ProguardIfRule) rule;
+      assert !ifRule.getPreconditions().isEmpty();
+      return ruleNodes.computeIfAbsent(
+          ifRule,
+          key -> {
+            Set<GraphNode> preconditions = new HashSet<>(ifRule.getPreconditions().size());
+            for (DexReference condition : ifRule.getPreconditions()) {
+              preconditions.add(getGraphNode(condition));
+            }
+            return new KeepRuleGraphNode(ifRule, preconditions);
+          });
+    }
+    throw new Unreachable("Unexpected type of keep rule: " + rule);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 43b875a..5f5d772 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -18,7 +18,7 @@
 
   public abstract GraphEdgeInfo.EdgeKind edgeKind();
 
-  public abstract GraphNode getSourceNode(Enqueuer enqueuer);
+  public abstract GraphNode getSourceNode(GraphReporter graphReporter);
 
   static KeepReason annotatedOn(DexDefinition definition) {
     return new AnnotatedOn(definition);
@@ -103,8 +103,8 @@
     }
 
     @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return enqueuer.getMethodGraphNode(method.method);
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
+      return graphReporter.getMethodGraphNode(method.method);
     }
   }
 
@@ -235,8 +235,8 @@
     }
 
     @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return enqueuer.getClassGraphNode(type);
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
+      return graphReporter.getClassGraphNode(type);
     }
   }
 
@@ -256,8 +256,8 @@
     }
 
     @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return enqueuer.getClassGraphNode(implementer);
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
+      return graphReporter.getClassGraphNode(implementer);
     }
   }
 
@@ -275,8 +275,8 @@
     }
 
     @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return enqueuer.getAnnotationGraphNode(holder);
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
+      return graphReporter.getAnnotationGraphNode(holder);
     }
   }
 
@@ -294,14 +294,14 @@
     }
 
     @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
       if (holder.isDexClass()) {
-        return enqueuer.getClassGraphNode(holder.asDexClass().type);
+        return graphReporter.getClassGraphNode(holder.asDexClass().type);
       } else if (holder.isDexEncodedField()) {
-        return enqueuer.getFieldGraphNode(holder.asDexEncodedField().field);
+        return graphReporter.getFieldGraphNode(holder.asDexEncodedField().field);
       } else {
         assert holder.isDexEncodedMethod();
-        return enqueuer.getMethodGraphNode(holder.asDexEncodedMethod().method);
+        return graphReporter.getMethodGraphNode(holder.asDexEncodedMethod().method);
       }
     }
   }