Cleanup invoke collections in Enqueuer and AppInfoWithLiveness

Change-Id: Ie51df5abe9f5a83324b01c75b3a7b3a4a47fda19
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
new file mode 100644
index 0000000..8cbbefe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
@@ -0,0 +1,128 @@
+// 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.graph;
+
+import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.BiConsumer;
+
+public class MethodAccessInfoCollection {
+
+  private final Map<DexMethod, ProgramMethodSet> directInvokes;
+  private final Map<DexMethod, ProgramMethodSet> interfaceInvokes;
+  private final Map<DexMethod, ProgramMethodSet> staticInvokes;
+  private final Map<DexMethod, ProgramMethodSet> superInvokes;
+  private final Map<DexMethod, ProgramMethodSet> virtualInvokes;
+
+  private MethodAccessInfoCollection(
+      Map<DexMethod, ProgramMethodSet> directInvokes,
+      Map<DexMethod, ProgramMethodSet> interfaceInvokes,
+      Map<DexMethod, ProgramMethodSet> staticInvokes,
+      Map<DexMethod, ProgramMethodSet> superInvokes,
+      Map<DexMethod, ProgramMethodSet> virtualInvokes) {
+    this.directInvokes = directInvokes;
+    this.interfaceInvokes = interfaceInvokes;
+    this.staticInvokes = staticInvokes;
+    this.superInvokes = superInvokes;
+    this.virtualInvokes = virtualInvokes;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public void forEachDirectInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
+    directInvokes.forEach(consumer);
+  }
+
+  public void forEachInterfaceInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
+    interfaceInvokes.forEach(consumer);
+  }
+
+  public void forEachStaticInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
+    staticInvokes.forEach(consumer);
+  }
+
+  public void forEachSuperInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
+    superInvokes.forEach(consumer);
+  }
+
+  public void forEachVirtualInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
+    virtualInvokes.forEach(consumer);
+  }
+
+  public MethodAccessInfoCollection rewrittenWithLens(
+      DexDefinitionSupplier definitions, GraphLens lens) {
+    return new MethodAccessInfoCollection(
+        rewriteInvokesWithLens(directInvokes, definitions, lens),
+        rewriteInvokesWithLens(interfaceInvokes, definitions, lens),
+        rewriteInvokesWithLens(staticInvokes, definitions, lens),
+        rewriteInvokesWithLens(superInvokes, definitions, lens),
+        rewriteInvokesWithLens(virtualInvokes, definitions, lens));
+  }
+
+  private static Map<DexMethod, ProgramMethodSet> rewriteInvokesWithLens(
+      Map<DexMethod, ProgramMethodSet> invokes, DexDefinitionSupplier definitions, GraphLens lens) {
+    return MapUtils.map(
+        invokes,
+        capacity -> new TreeMap<>(DexMethod::slowCompareTo),
+        lens::getRenamedMethodSignature,
+        methods -> methods.rewrittenWithLens(definitions, lens),
+        (methods, other) -> {
+          methods.addAll(other);
+          return methods;
+        });
+  }
+
+  public static class Builder {
+
+    // TODO(b/132593519): We should not need sorted maps with the new member rebinding analysis.
+    private final Map<DexMethod, ProgramMethodSet> directInvokes =
+        new TreeMap<>(DexMethod::slowCompareTo);
+    private final Map<DexMethod, ProgramMethodSet> interfaceInvokes =
+        new TreeMap<>(DexMethod::slowCompareTo);
+    private final Map<DexMethod, ProgramMethodSet> staticInvokes =
+        new TreeMap<>(DexMethod::slowCompareTo);
+    private final Map<DexMethod, ProgramMethodSet> superInvokes =
+        new TreeMap<>(DexMethod::slowCompareTo);
+    private final Map<DexMethod, ProgramMethodSet> virtualInvokes =
+        new TreeMap<>(DexMethod::slowCompareTo);
+
+    public boolean registerInvokeDirectInContext(DexMethod invokedMethod, ProgramMethod context) {
+      return registerInvokeMethodInContext(invokedMethod, context, directInvokes);
+    }
+
+    public boolean registerInvokeInterfaceInContext(
+        DexMethod invokedMethod, ProgramMethod context) {
+      return registerInvokeMethodInContext(invokedMethod, context, interfaceInvokes);
+    }
+
+    public boolean registerInvokeStaticInContext(DexMethod invokedMethod, ProgramMethod context) {
+      return registerInvokeMethodInContext(invokedMethod, context, staticInvokes);
+    }
+
+    public boolean registerInvokeSuperInContext(DexMethod invokedMethod, ProgramMethod context) {
+      return registerInvokeMethodInContext(invokedMethod, context, superInvokes);
+    }
+
+    public boolean registerInvokeVirtualInContext(DexMethod invokedMethod, ProgramMethod context) {
+      return registerInvokeMethodInContext(invokedMethod, context, virtualInvokes);
+    }
+
+    private static boolean registerInvokeMethodInContext(
+        DexMethod invokedMethod, ProgramMethod context, Map<DexMethod, ProgramMethodSet> invokes) {
+      return invokes
+          .computeIfAbsent(invokedMethod, ignore -> ProgramMethodSet.create())
+          .add(context);
+    }
+
+    public MethodAccessInfoCollection build() {
+      return new MethodAccessInfoCollection(
+          directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index a8f3018..09790f1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 
 /**
  * An optimization that merges a method override (B.m()) into the method it overrides (A.m()).
@@ -41,8 +42,8 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final IRConverter converter;
 
-  private final ProgramMethodSet candidatesForInstanceOfOptimization =
-      ProgramMethodSet.createSorted();
+  private final SortedProgramMethodSet candidatesForInstanceOfOptimization =
+      SortedProgramMethodSet.create();
 
   public CheckCastAndInstanceOfMethodSpecialization(
       AppView<AppInfoWithLiveness> appView, IRConverter converter) {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index dcd003c..b8022b9 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -16,13 +16,14 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BiForEachable;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.Map;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
@@ -127,50 +128,71 @@
     return appView.appInfo().unsafeResolveMethodDueToDexFormat(method).getSingleTarget();
   }
 
+  private void computeMethodRebinding(MethodAccessInfoCollection methodAccessInfoCollection) {
+    // Virtual invokes are on classes, so use class resolution.
+    computeMethodRebinding(
+        methodAccessInfoCollection::forEachVirtualInvoke, this::classLookup, Type.VIRTUAL);
+    // Interface invokes are always on interfaces, so use interface resolution.
+    computeMethodRebinding(
+        methodAccessInfoCollection::forEachInterfaceInvoke, this::interfaceLookup, Type.INTERFACE);
+    // Super invokes can be on both kinds, decide using the holder class.
+    computeMethodRebinding(
+        methodAccessInfoCollection::forEachSuperInvoke, this::anyLookup, Type.SUPER);
+    // Direct invokes (private/constructor) can also be on both kinds.
+    computeMethodRebinding(
+        methodAccessInfoCollection::forEachDirectInvoke, this::anyLookup, Type.DIRECT);
+    // Likewise static invokes.
+    computeMethodRebinding(
+        methodAccessInfoCollection::forEachStaticInvoke, this::anyLookup, Type.STATIC);
+  }
+
   private void computeMethodRebinding(
-      Map<DexMethod, ProgramMethodSet> methodsWithContexts,
+      BiForEachable<DexMethod, ProgramMethodSet> methodsWithContexts,
       Function<DexMethod, DexEncodedMethod> lookupTarget,
       Type invokeType) {
-    for (DexMethod method : methodsWithContexts.keySet()) {
-      // We can safely ignore array types, as the corresponding methods are defined in a library.
-      if (!method.holder.isClassType()) {
-        continue;
-      }
-      DexClass originalClass = appView.definitionFor(method.holder);
-      if (originalClass == null || originalClass.isNotProgramClass()) {
-        continue;
-      }
-      DexEncodedMethod target = lookupTarget.apply(method);
-      // TODO(b/128404854) Rebind to the lowest library class or program class. For now we allow
-      //  searching in library for methods, but this should be done on classpath instead.
-      if (target != null && target.method != method) {
-        DexClass targetClass = appView.definitionFor(target.holder());
-        if (originalClass.isProgramClass()) {
-          // In Java bytecode, it is only possible to target interface methods that are in one of
-          // the immediate super-interfaces via a super-invocation (see IndirectSuperInterfaceTest).
-          // To avoid introducing an IncompatibleClassChangeError at runtime we therefore insert a
-          // bridge method when we are about to rebind to an interface method that is not the
-          // original target.
-          if (needsBridgeForInterfaceMethod(originalClass, targetClass, invokeType)) {
-            target =
-                insertBridgeForInterfaceMethod(
-                    method, target, originalClass.asProgramClass(), targetClass, lookupTarget);
+    methodsWithContexts.forEach(
+        (method, contexts) -> {
+          // We can safely ignore array types, as the corresponding methods are defined in a
+          // library.
+          if (!method.holder.isClassType()) {
+            return;
           }
+          DexClass originalClass = appView.definitionFor(method.holder);
+          if (originalClass == null || originalClass.isNotProgramClass()) {
+            return;
+          }
+          DexEncodedMethod target = lookupTarget.apply(method);
+          // TODO(b/128404854) Rebind to the lowest library class or program class. For now we allow
+          //  searching in library for methods, but this should be done on classpath instead.
+          if (target == null || target.method == method) {
+            return;
+          }
+          DexClass targetClass = appView.definitionFor(target.holder());
+          if (originalClass.isProgramClass()) {
+            // In Java bytecode, it is only possible to target interface methods that are in one of
+            // the immediate super-interfaces via a super-invocation (see
+            // IndirectSuperInterfaceTest).
+            // To avoid introducing an IncompatibleClassChangeError at runtime we therefore insert a
+            // bridge method when we are about to rebind to an interface method that is not the
+            // original target.
+            if (needsBridgeForInterfaceMethod(originalClass, targetClass, invokeType)) {
+              target =
+                  insertBridgeForInterfaceMethod(
+                      method, target, originalClass.asProgramClass(), targetClass, lookupTarget);
+            }
 
-          // If the target class is not public but the targeted method is, we might run into
-          // visibility problems when rebinding.
-          final DexEncodedMethod finalTarget = target;
-          ProgramMethodSet contexts = methodsWithContexts.get(method);
-          if (contexts.stream()
-              .anyMatch(context -> mayNeedBridgeForVisibility(context, finalTarget))) {
-            target =
-                insertBridgeForVisibilityIfNeeded(
-                    method, target, originalClass, targetClass, lookupTarget);
+            // If the target class is not public but the targeted method is, we might run into
+            // visibility problems when rebinding.
+            final DexEncodedMethod finalTarget = target;
+            if (contexts.stream()
+                .anyMatch(context -> mayNeedBridgeForVisibility(context, finalTarget))) {
+              target =
+                  insertBridgeForVisibilityIfNeeded(
+                      method, target, originalClass, targetClass, lookupTarget);
+            }
           }
-        }
-        builder.map(method, lens.lookupMethod(validTargetFor(target.method, method)), invokeType);
-      }
-    }
+          builder.map(method, lens.lookupMethod(validTargetFor(target.method, method)), invokeType);
+        });
   }
 
   private boolean needsBridgeForInterfaceMethod(
@@ -337,16 +359,7 @@
 
   public GraphLens run() {
     AppInfoWithLiveness appInfo = appView.appInfo();
-    // Virtual invokes are on classes, so use class resolution.
-    computeMethodRebinding(appInfo.virtualInvokes, this::classLookup, Type.VIRTUAL);
-    // Interface invokes are always on interfaces, so use interface resolution.
-    computeMethodRebinding(appInfo.interfaceInvokes, this::interfaceLookup, Type.INTERFACE);
-    // Super invokes can be on both kinds, decide using the holder class.
-    computeMethodRebinding(appInfo.superInvokes, this::anyLookup, Type.SUPER);
-    // Direct invokes (private/constructor) can also be on both kinds.
-    computeMethodRebinding(appInfo.directInvokes, this::anyLookup, Type.DIRECT);
-    // Likewise static invokes.
-    computeMethodRebinding(appInfo.staticInvokes, this::anyLookup, Type.STATIC);
+    computeMethodRebinding(appInfo.getMethodAccessInfoCollection());
     computeFieldRebinding();
     GraphLens lens = builder.build(this.lens);
     appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
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 1b1d37c..92722cd 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.graph.InstantiatedSubTypeInfo;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.LookupTarget;
+import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
@@ -118,18 +119,10 @@
    * each field. The latter is used, for example, during member rebinding.
    */
   private FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
+  /** Set of all methods referenced in invokes along with their calling contexts. */
+  private final MethodAccessInfoCollection methodAccessInfoCollection;
   /** Information about instantiated classes and their allocation sites. */
   private final ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection;
-  /** Set of all methods referenced in virtual invokes, along with calling context. */
-  public final SortedMap<DexMethod, ProgramMethodSet> virtualInvokes;
-  /** Set of all methods referenced in interface invokes, along with calling context. */
-  public final SortedMap<DexMethod, ProgramMethodSet> interfaceInvokes;
-  /** Set of all methods referenced in super invokes, along with calling context. */
-  public final SortedMap<DexMethod, ProgramMethodSet> superInvokes;
-  /** Set of all methods referenced in direct invokes, along with calling context. */
-  public final SortedMap<DexMethod, ProgramMethodSet> directInvokes;
-  /** Set of all methods referenced in static invokes, along with calling context. */
-  public final SortedMap<DexMethod, ProgramMethodSet> staticInvokes;
   /**
    * Set of live call sites in the code. Note that if desugaring has taken place call site objects
    * will have been removed from the code.
@@ -212,12 +205,8 @@
       SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
       SortedSet<DexMethod> liveMethods,
       FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
+      MethodAccessInfoCollection methodAccessInfoCollection,
       ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection,
-      SortedMap<DexMethod, ProgramMethodSet> virtualInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> interfaceInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> superInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> directInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
       Set<DexCallSite> callSites,
       KeepInfoCollection keepInfo,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
@@ -255,16 +244,12 @@
     this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
     this.liveMethods = liveMethods;
     this.fieldAccessInfoCollection = fieldAccessInfoCollection;
+    this.methodAccessInfoCollection = methodAccessInfoCollection;
     this.objectAllocationInfoCollection = objectAllocationInfoCollection;
     this.keepInfo = keepInfo;
     this.mayHaveSideEffects = mayHaveSideEffects;
     this.noSideEffects = noSideEffects;
     this.assumedValues = assumedValues;
-    this.virtualInvokes = virtualInvokes;
-    this.interfaceInvokes = interfaceInvokes;
-    this.superInvokes = superInvokes;
-    this.directInvokes = directInvokes;
-    this.staticInvokes = staticInvokes;
     this.callSites = callSites;
     this.alwaysInline = alwaysInline;
     this.forceInline = forceInline;
@@ -301,6 +286,7 @@
       SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
       SortedSet<DexMethod> liveMethods,
       FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
+      MethodAccessInfoCollection methodAccessInfoCollection,
       ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection,
       SortedMap<DexMethod, ProgramMethodSet> virtualInvokes,
       SortedMap<DexMethod, ProgramMethodSet> interfaceInvokes,
@@ -347,16 +333,12 @@
     this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
     this.liveMethods = liveMethods;
     this.fieldAccessInfoCollection = fieldAccessInfoCollection;
+    this.methodAccessInfoCollection = methodAccessInfoCollection;
     this.objectAllocationInfoCollection = objectAllocationInfoCollection;
     this.keepInfo = keepInfo;
     this.mayHaveSideEffects = mayHaveSideEffects;
     this.noSideEffects = noSideEffects;
     this.assumedValues = assumedValues;
-    this.virtualInvokes = virtualInvokes;
-    this.interfaceInvokes = interfaceInvokes;
-    this.superInvokes = superInvokes;
-    this.directInvokes = directInvokes;
-    this.staticInvokes = staticInvokes;
     this.callSites = callSites;
     this.alwaysInline = alwaysInline;
     this.forceInline = forceInline;
@@ -398,12 +380,8 @@
         previous.virtualMethodsTargetedByInvokeDirect,
         previous.liveMethods,
         previous.fieldAccessInfoCollection,
+        previous.methodAccessInfoCollection,
         previous.objectAllocationInfoCollection,
-        previous.virtualInvokes,
-        previous.interfaceInvokes,
-        previous.superInvokes,
-        previous.directInvokes,
-        previous.staticInvokes,
         previous.callSites,
         previous.keepInfo,
         previous.mayHaveSideEffects,
@@ -453,12 +431,8 @@
         previous.virtualMethodsTargetedByInvokeDirect,
         previous.liveMethods,
         previous.fieldAccessInfoCollection,
+        previous.methodAccessInfoCollection,
         previous.objectAllocationInfoCollection,
-        previous.virtualInvokes,
-        previous.interfaceInvokes,
-        previous.superInvokes,
-        previous.directInvokes,
-        previous.staticInvokes,
         previous.callSites,
         extendPinnedItems(previous, additionalPinnedItems),
         previous.mayHaveSideEffects,
@@ -545,16 +519,12 @@
     this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
     this.liveMethods = previous.liveMethods;
     this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
+    this.methodAccessInfoCollection = previous.methodAccessInfoCollection;
     this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
     this.keepInfo = previous.keepInfo;
     this.mayHaveSideEffects = previous.mayHaveSideEffects;
     this.noSideEffects = previous.noSideEffects;
     this.assumedValues = previous.assumedValues;
-    this.virtualInvokes = previous.virtualInvokes;
-    this.interfaceInvokes = previous.interfaceInvokes;
-    this.superInvokes = previous.superInvokes;
-    this.directInvokes = previous.directInvokes;
-    this.staticInvokes = previous.staticInvokes;
     this.callSites = previous.callSites;
     this.alwaysInline = previous.alwaysInline;
     this.forceInline = previous.forceInline;
@@ -779,6 +749,11 @@
     return fieldAccessInfoCollection;
   }
 
+  /** This method provides immutable access to `methodAccessInfoCollection`. */
+  public MethodAccessInfoCollection getMethodAccessInfoCollection() {
+    return methodAccessInfoCollection;
+  }
+
   /** This method provides immutable access to `objectAllocationInfoCollection`. */
   public ObjectAllocationInfoCollection getObjectAllocationInfoCollection() {
     return objectAllocationInfoCollection;
@@ -1020,15 +995,9 @@
         lens.rewriteMethods(methodsTargetedByInvokeDynamic),
         lens.rewriteMethods(virtualMethodsTargetedByInvokeDirect),
         lens.rewriteMethods(liveMethods),
-        fieldAccessInfoCollection != null
-            ? fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens)
-            : null,
+        fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens),
+        methodAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens),
         objectAllocationInfoCollection.rewrittenWithLens(definitionSupplier, lens),
-        rewriteInvokesWithContexts(virtualInvokes, lens),
-        rewriteInvokesWithContexts(interfaceInvokes, lens),
-        rewriteInvokesWithContexts(superInvokes, lens),
-        rewriteInvokesWithContexts(directInvokes, lens),
-        rewriteInvokesWithContexts(staticInvokes, lens),
         // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
         //   after second tree shaking.
         callSites,
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 98def17..cb7847c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
-import static com.android.tools.r8.shaking.EnqueuerUtils.toImmutableSortedMap;
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
@@ -61,6 +60,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.LookupLambdaTarget;
 import com.android.tools.r8.graph.LookupTarget;
+import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ProgramField;
@@ -141,6 +141,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -200,13 +201,10 @@
   private AnnotationRemover.Builder annotationRemoverBuilder;
   private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
 
-  private final Map<DexMethod, ProgramMethodSet> virtualInvokes = new IdentityHashMap<>();
-  private final Map<DexMethod, ProgramMethodSet> interfaceInvokes = new IdentityHashMap<>();
-  private final Map<DexMethod, ProgramMethodSet> superInvokes = new IdentityHashMap<>();
-  private final Map<DexMethod, ProgramMethodSet> directInvokes = new IdentityHashMap<>();
-  private final Map<DexMethod, ProgramMethodSet> staticInvokes = new IdentityHashMap<>();
   private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
       new FieldAccessInfoCollectionImpl();
+  private final MethodAccessInfoCollection.Builder methodAccessInfoCollection =
+      MethodAccessInfoCollection.builder();
   private final ObjectAllocationInfoCollectionImpl.Builder objectAllocationInfoCollection;
   private final Set<DexCallSite> callSites = Sets.newIdentityHashSet();
 
@@ -784,11 +782,11 @@
   //
 
   private boolean registerMethodWithTargetAndContext(
-      Map<DexMethod, ProgramMethodSet> seen, DexMethod method, ProgramMethod context) {
+      BiPredicate<DexMethod, ProgramMethod> registration, DexMethod method, ProgramMethod context) {
     DexType baseHolder = method.holder.toBaseType(appView.dexItemFactory());
     if (baseHolder.isClassType()) {
       markTypeAsLive(baseHolder, clazz -> graphReporter.reportClassReferencedFrom(clazz, context));
-      return seen.computeIfAbsent(method, ignore -> ProgramMethodSet.create()).add(context);
+      return registration.test(method, context);
     }
     return false;
   }
@@ -1078,7 +1076,8 @@
 
   private void traceInvokeDirect(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
-    if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, context)) {
+    if (!registerMethodWithTargetAndContext(
+        methodAccessInfoCollection::registerInvokeDirectInContext, invokedMethod, context)) {
       return;
     }
     if (Log.ENABLED) {
@@ -1098,7 +1097,8 @@
 
   private void traceInvokeInterface(
       DexMethod method, ProgramMethod context, KeepReason keepReason) {
-    if (!registerMethodWithTargetAndContext(interfaceInvokes, method, context)) {
+    if (!registerMethodWithTargetAndContext(
+        methodAccessInfoCollection::registerInvokeInterfaceInContext, method, context)) {
       return;
     }
     if (Log.ENABLED) {
@@ -1137,7 +1137,8 @@
     if (invokedMethod == dexItemFactory.proxyMethods.newProxyInstance) {
       pendingReflectiveUses.add(context);
     }
-    if (!registerMethodWithTargetAndContext(staticInvokes, invokedMethod, context)) {
+    if (!registerMethodWithTargetAndContext(
+        methodAccessInfoCollection::registerInvokeStaticInContext, invokedMethod, context)) {
       return;
     }
     if (Log.ENABLED) {
@@ -1151,7 +1152,8 @@
     // We have to revisit super invokes based on the context they are found in. The same
     // method descriptor will hit different targets, depending on the context it is used in.
     DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, context);
-    if (!registerMethodWithTargetAndContext(superInvokes, invokedMethod, context)) {
+    if (!registerMethodWithTargetAndContext(
+        methodAccessInfoCollection::registerInvokeSuperInContext, invokedMethod, context)) {
       return;
     }
     if (Log.ENABLED) {
@@ -1180,7 +1182,8 @@
       // Revisit the current method to implicitly add -keep rule for items with reflective access.
       pendingReflectiveUses.add(context);
     }
-    if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context)) {
+    if (!registerMethodWithTargetAndContext(
+        methodAccessInfoCollection::registerInvokeVirtualInContext, invokedMethod, context)) {
       return;
     }
     if (Log.ENABLED) {
@@ -3037,13 +3040,8 @@
             toSortedDescriptorSet(liveMethods.getItems()),
             // Filter out library fields and pinned fields, because these are read by default.
             fieldAccessInfoCollection,
+            methodAccessInfoCollection.build(),
             objectAllocationInfoCollection.build(appInfo),
-            // TODO(b/132593519): Do we require these sets to be sorted for determinism?
-            toImmutableSortedMap(virtualInvokes, PresortedComparable::slowCompare),
-            toImmutableSortedMap(interfaceInvokes, PresortedComparable::slowCompare),
-            toImmutableSortedMap(superInvokes, PresortedComparable::slowCompare),
-            toImmutableSortedMap(directInvokes, PresortedComparable::slowCompare),
-            toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
             callSites,
             keepInfo,
             rootSet.mayHaveSideEffects,
diff --git a/src/main/java/com/android/tools/r8/utils/BiForEachable.java b/src/main/java/com/android/tools/r8/utils/BiForEachable.java
new file mode 100644
index 0000000..f1dd413
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BiForEachable.java
@@ -0,0 +1,11 @@
+// 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;
+
+import java.util.function.BiConsumer;
+
+public interface BiForEachable<S, T> {
+
+  void forEach(BiConsumer<S, T> consumer);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index 3009e49..c899a75 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -6,9 +6,31 @@
 
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.IntFunction;
 
 public class MapUtils {
 
+  public static <K, V> Map<K, V> map(
+      Map<K, V> map,
+      IntFunction<Map<K, V>> factory,
+      Function<K, K> keyMapping,
+      Function<V, V> valueMapping,
+      BiFunction<V, V, V> valueMerger) {
+    Map<K, V> result = factory.apply(map.size());
+    map.forEach(
+        (key, value) -> {
+          K newKey = keyMapping.apply(key);
+          V newValue = valueMapping.apply(value);
+          V existingValue = result.put(newKey, newValue);
+          if (existingValue != null) {
+            result.put(newKey, valueMerger.apply(existingValue, newValue));
+          }
+        });
+    return result;
+  }
+
   public static <T> void removeIdentityMappings(Map<T, T> map) {
     map.entrySet().removeIf(entry -> entry.getKey() == entry.getValue());
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
index 0732bcb..2412cfd 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
@@ -4,9 +4,11 @@
 
 package com.android.tools.r8.utils.collections;
 
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
@@ -15,26 +17,34 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
 import java.util.stream.Stream;
 
 public class ProgramMethodSet implements Iterable<ProgramMethod> {
 
-  private static final ProgramMethodSet EMPTY = new ProgramMethodSet(ImmutableMap.of());
+  private static final ProgramMethodSet EMPTY = new ProgramMethodSet(ImmutableMap::of);
 
-  private Map<DexMethod, ProgramMethod> backing;
+  private final Map<DexMethod, ProgramMethod> backing;
+  private final Supplier<? extends Map<DexMethod, ProgramMethod>> backingFactory;
 
-  ProgramMethodSet(Map<DexMethod, ProgramMethod> backing) {
+  protected ProgramMethodSet(Supplier<? extends Map<DexMethod, ProgramMethod>> backingFactory) {
+    this(backingFactory, backingFactory.get());
+  }
+
+  protected ProgramMethodSet(
+      Supplier<? extends Map<DexMethod, ProgramMethod>> backingFactory,
+      Map<DexMethod, ProgramMethod> backing) {
     this.backing = backing;
+    this.backingFactory = backingFactory;
   }
 
   public static ProgramMethodSet create() {
-    return new ProgramMethodSet(new IdentityHashMap<>());
+    return new ProgramMethodSet(IdentityHashMap::new);
   }
 
   public static ProgramMethodSet create(int capacity) {
-    return new ProgramMethodSet(new IdentityHashMap<>(capacity));
+    return new ProgramMethodSet(IdentityHashMap::new, new IdentityHashMap<>(capacity));
   }
 
   public static ProgramMethodSet create(ProgramMethod element) {
@@ -44,15 +54,11 @@
   }
 
   public static ProgramMethodSet createConcurrent() {
-    return new ProgramMethodSet(new ConcurrentHashMap<>());
+    return new ProgramMethodSet(ConcurrentHashMap::new);
   }
 
   public static ProgramMethodSet createLinked() {
-    return new ProgramMethodSet(new LinkedHashMap<>());
-  }
-
-  public static ProgramMethodSet createSorted() {
-    return new ProgramMethodSet(new TreeMap<>(DexMethod::slowCompareTo));
+    return new ProgramMethodSet(LinkedHashMap::new);
   }
 
   public static ProgramMethodSet empty() {
@@ -107,6 +113,18 @@
     return remove(method.getReference());
   }
 
+  public ProgramMethodSet rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) {
+    ProgramMethodSet rewritten = new ProgramMethodSet(backingFactory);
+    forEach(
+        method -> {
+          ProgramMethod newMethod = lens.mapProgramMethod(method, definitions);
+          if (newMethod != null) {
+            rewritten.add(newMethod);
+          }
+        });
+    return rewritten;
+  }
+
   public int size() {
     return backing.size();
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
index 4c6db49..6f6d1a5 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.utils.collections;
 
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.ForEachable;
 import com.android.tools.r8.utils.ForEachableUtils;
@@ -13,11 +15,12 @@
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.function.Supplier;
 
 public class SortedProgramMethodSet extends ProgramMethodSet {
 
-  private SortedProgramMethodSet(TreeMap<DexMethod, ProgramMethod> backing) {
-    super(backing);
+  private SortedProgramMethodSet(Supplier<TreeMap<DexMethod, ProgramMethod>> backingFactory) {
+    super(backingFactory);
   }
 
   public static SortedProgramMethodSet create() {
@@ -32,12 +35,19 @@
 
   public static SortedProgramMethodSet create(ForEachable<ProgramMethod> methods) {
     SortedProgramMethodSet result =
-        new SortedProgramMethodSet(new TreeMap<>(DexMethod::slowCompareTo));
+        new SortedProgramMethodSet(() -> new TreeMap<>(DexMethod::slowCompareTo));
     methods.forEach(result::add);
     return result;
   }
 
   @Override
+  public SortedProgramMethodSet rewrittenWithLens(
+      DexDefinitionSupplier definitions, GraphLens lens) {
+    return create(
+        consumer -> forEach(method -> consumer.accept(lens.mapProgramMethod(method, definitions))));
+  }
+
+  @Override
   public Set<DexEncodedMethod> toDefinitionSet() {
     Comparator<DexEncodedMethod> comparator =
         (x, y) -> x.getReference().slowCompareTo(y.getReference());