Rewrite minifier to use DexClassAndMethod instead of DexEncodedMethod

Change-Id: If918ebf3ed3c2a2acd03d691626ea0694c1c373d
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 14c4975..ea46ea8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -298,6 +298,10 @@
     return Iterables.filter(virtualMethods(), predicate::test);
   }
 
+  public Iterable<DexClassAndMethod> virtualClassMethods() {
+    return Iterables.transform(virtualMethods(), method -> DexClassAndMethod.create(this, method));
+  }
+
   public void addVirtualMethod(DexEncodedMethod method) {
     methodCollection.addVirtualMethod(method);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 95be1fa..4ec188f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -30,6 +30,10 @@
     return identical(this, other);
   }
 
+  public final boolean isNotIdenticalTo(DexString other) {
+    return !isIdenticalTo(other);
+  }
+
   public static final DexString[] EMPTY_ARRAY = {};
   private static final int ARRAY_CHARACTER = '[';
 
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 2f4b4b9..f15bba1 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -126,13 +127,12 @@
               }
               ReservedFieldNamingState reservationState =
                   getOrCreateReservedFieldNamingState(frontier);
-              for (DexEncodedField field : clazz.fields()) {
-                DexString reservedName = strategy.getReservedName(field, clazz);
+              for (DexClassAndField field : clazz.classFields()) {
+                DexString reservedName = strategy.getReservedName(field);
                 if (reservedName != null) {
-                  reservationState.markReserved(
-                      reservedName, field.getReference().name, field.getReference().type);
+                  reservationState.markReserved(reservedName, field);
                   // TODO(b/148846065): Consider lazily computing the renaming on actual lookups.
-                  if (reservedName != field.getReference().name) {
+                  if (reservedName.isNotIdenticalTo(field.getName())) {
                     renaming.put(field.getReference(), reservedName);
                   }
                 }
@@ -214,16 +214,15 @@
             });
   }
 
-  @SuppressWarnings("ReferenceEquality")
   private void renameFieldsInUnrelatedClasspathClasses() {
     if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
       appView
           .appInfo()
           .forEachReferencedClasspathClass(
               clazz -> {
-                for (DexEncodedField field : clazz.fields()) {
-                  DexString reservedName = strategy.getReservedName(field, clazz);
-                  if (reservedName != null && reservedName != field.getReference().name) {
+                for (DexClassAndField field : clazz.classFields()) {
+                  DexString reservedName = strategy.getReservedName(field);
+                  if (reservedName != null && reservedName.isNotIdenticalTo(field.getName())) {
                     renaming.put(field.getReference(), reservedName);
                   }
                 }
@@ -267,8 +266,7 @@
             .forEachProgramField(
                 field -> {
                   DexString newName = renameField(field, state);
-                  namesToBeReservedInImplementsSubclasses.markReserved(
-                      newName, field.getReference().name, field.getReference().type);
+                  namesToBeReservedInImplementsSubclasses.markReserved(newName, field);
                 });
       }
     }
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
index e507112..6490228 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -49,7 +49,7 @@
   }
 
   public DexString getOrCreateNameFor(ProgramField field) {
-    DexString reservedName = strategy.getReservedName(field.getDefinition(), field.getHolder());
+    DexString reservedName = strategy.getReservedName(field);
     if (reservedName != null) {
       return reservedName;
     }
@@ -57,10 +57,6 @@
     return getOrCreateInternalState(field.getReference()).createNewName(field);
   }
 
-  public void includeReservations(ReservedFieldNamingState reservedNames) {
-    this.reservedNames.includeReservations(reservedNames);
-  }
-
   @Override
   public InternalState createInternalState() {
     return new InternalState();
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java b/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java
index 693f373..b9f8454 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.ProguardConfiguration;
 import java.util.Map;
 
 abstract class FieldNamingStateBase<T> {
@@ -36,11 +35,10 @@
     return internalStates.computeIfAbsent(internalStateKey, key -> createInternalState());
   }
 
+  @SuppressWarnings("UnusedVariable")
   private DexType getInternalStateKey(DexType type) {
-    ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
-    return proguardConfiguration.isOverloadAggressively()
-        ? type
-        : appView.dexItemFactory().voidType;
+    // Returning the given type instead of void will implement aggressive overloading.
+    return appView.dexItemFactory().voidType;
   }
 
   abstract T createInternalState();
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 4f1544e..d3bfef8 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -3,9 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
@@ -16,11 +19,12 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DisjointSets;
 import com.android.tools.r8.utils.MethodJavaSignatureEquivalence;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.DexClassAndMethodMap;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.io.PrintStream;
 import java.util.ArrayList;
@@ -110,11 +114,11 @@
       this.iface = iface;
     }
 
-    DexString getReservedName(DexEncodedMethod method) {
+    DexString getReservedName(DexClassAndMethod method) {
       // If an interface is kept and we are using applymapping, the renamed name for this method
       // is tracked on this level.
       if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
-        DexString reservedName = minifierState.getReservedName(method, iface);
+        DexString reservedName = minifierState.getReservedName(method);
         if (reservedName != null) {
           return reservedName;
         }
@@ -143,7 +147,7 @@
       this.reservationTypes.add(type);
     }
 
-    void reserveName(DexString reservedName, DexEncodedMethod method) {
+    void reserveName(DexString reservedName, DexClassAndMethod method) {
       forAll(
           s -> {
             s.reservationTypes.forEach(
@@ -154,7 +158,7 @@
           });
     }
 
-    boolean isAvailable(DexString candidate, DexEncodedMethod method) {
+    boolean isAvailable(DexString candidate, DexClassAndMethod method) {
       Boolean result =
           forAny(
               s -> {
@@ -169,14 +173,14 @@
       return result == null || result;
     }
 
-    void addRenaming(DexString newName, DexEncodedMethod method) {
+    void addRenaming(DexString newName, DexClassAndMethod method) {
       forAll(
           s ->
               s.reservationTypes.forEach(
                   resType -> minifierState.getNamingState(resType).addRenaming(newName, method)));
     }
 
-    <T> void forAll(Consumer<InterfaceReservationState> action) {
+    void forAll(Consumer<InterfaceReservationState> action) {
       forAny(
           s -> {
             action.accept(s);
@@ -238,20 +242,19 @@
   class InterfaceMethodGroupState implements Comparable<InterfaceMethodGroupState> {
 
     private final Set<DexCallSite> callSites = new HashSet<>();
-    private final Map<DexEncodedMethod, Set<InterfaceReservationState>> methodStates =
-        new HashMap<>();
-    private final List<DexEncodedMethod> callSiteCollidingMethods = new ArrayList<>();
+    private final DexClassAndMethodMap<Set<InterfaceReservationState>> methodStates =
+        DexClassAndMethodMap.create();
+    private final List<DexClassAndMethod> callSiteCollidingMethods = new ArrayList<>();
 
-    void addState(DexEncodedMethod method, InterfaceReservationState interfaceState) {
-      methodStates.computeIfAbsent(method, m -> new HashSet<>()).add(interfaceState);
+    void addState(DexClassAndMethod method, InterfaceReservationState interfaceState) {
+      methodStates.computeIfAbsent(method, ignoreKey(HashSet::new)).add(interfaceState);
     }
 
     void appendMethodGroupState(InterfaceMethodGroupState state) {
       callSites.addAll(state.callSites);
       callSiteCollidingMethods.addAll(state.callSiteCollidingMethods);
-      for (DexEncodedMethod key : state.methodStates.keySet()) {
-        methodStates.computeIfAbsent(key, k -> new HashSet<>()).addAll(state.methodStates.get(key));
-      }
+      state.methodStates.forEach(
+          (key, value) -> methodStates.computeIfAbsent(key, ignoreKey(HashSet::new)).addAll(value));
     }
 
     void addCallSite(DexCallSite callSite) {
@@ -260,7 +263,6 @@
       callSites.add(callSite);
     }
 
-    @SuppressWarnings("ReferenceEquality")
     DexString getReservedName() {
       if (methodStates.isEmpty()) {
         return null;
@@ -268,13 +270,11 @@
       // It is perfectly fine to have multiple reserved names inside a group. If we have an identity
       // reservation, we have to prioritize that over the others, otherwise we just propose the
       // first ordered reserved name since we do not allow overwriting the name.
-      List<DexEncodedMethod> sortedMethods = Lists.newArrayList(methodStates.keySet());
-      sortedMethods.sort((x, y) -> x.getReference().compareTo(y.getReference()));
       DexString reservedName = null;
-      for (DexEncodedMethod method : sortedMethods) {
+      for (DexClassAndMethod method : methodStates.getKeysSorted()) {
         for (InterfaceReservationState state : methodStates.get(method)) {
           DexString stateReserved = state.getReservedName(method);
-          if (stateReserved == method.getName()) {
+          if (method.getName().isIdenticalTo(stateReserved)) {
             return method.getName();
           } else if (stateReserved != null) {
             reservedName = stateReserved;
@@ -320,7 +320,7 @@
           });
     }
 
-    void forEachState(BiConsumer<DexEncodedMethod, InterfaceReservationState> action) {
+    void forEachState(BiConsumer<DexClassAndMethod, InterfaceReservationState> action) {
       forAnyState(
           (s, i) -> {
             action.accept(s, i);
@@ -328,21 +328,25 @@
           });
     }
 
-    <T> T forAnyState(BiFunction<DexEncodedMethod, InterfaceReservationState, T> callback) {
-      T returnValue;
-      for (Map.Entry<DexEncodedMethod, Set<InterfaceReservationState>> entry :
-          methodStates.entrySet()) {
-        for (InterfaceReservationState state : entry.getValue()) {
-          returnValue = callback.apply(entry.getKey(), state);
-          if (returnValue != null) {
-            return returnValue;
-          }
-        }
+    <T> T forAnyState(BiFunction<DexClassAndMethod, InterfaceReservationState, T> callback) {
+      TraversalContinuation<T, Void> traversalContinuation =
+          methodStates.traverse(
+              (key, value) -> {
+                for (InterfaceReservationState state : value) {
+                  T returnValue = callback.apply(key, state);
+                  if (returnValue != null) {
+                    return TraversalContinuation.doBreak(returnValue);
+                  }
+                }
+                return TraversalContinuation.doContinue();
+              });
+      if (traversalContinuation.isBreak()) {
+        return traversalContinuation.asBreak().getValue();
       }
       return null;
     }
 
-    boolean containsReservation(DexEncodedMethod method, DexType reservationType) {
+    boolean containsReservation(DexClassAndMethod method, DexType reservationType) {
       Set<InterfaceReservationState> states = methodStates.get(method);
       if (states != null) {
         for (InterfaceReservationState state : states) {
@@ -361,15 +365,30 @@
     }
   }
 
+  // Replacing the use of MethodJavaSignatureEquivalence by MethodSignatureEquivalence implements
+  // aggressive overloading.
+  private static final Equivalence<DexClassAndMethod> equivalence =
+      new Equivalence<>() {
+
+        @Override
+        protected boolean doEquivalent(DexClassAndMethod method, DexClassAndMethod other) {
+          return MethodJavaSignatureEquivalence.get()
+              .equivalent(method.getReference(), other.getReference());
+        }
+
+        @Override
+        protected int doHash(DexClassAndMethod method) {
+          return MethodJavaSignatureEquivalence.get().hash(method.getReference());
+        }
+      };
+
   private final AppView<AppInfoWithLiveness> appView;
   private final SubtypingInfo subtypingInfo;
-  private final Equivalence<DexMethod> equivalence;
-  private final Equivalence<DexEncodedMethod> definitionEquivalence;
   private final MethodNameMinifier.State minifierState;
 
   /** A map from DexMethods to all the states linked to interfaces they appear in. */
-  private final Map<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> globalStateMap =
-      new HashMap<>();
+  private final DexClassAndMethodMap<InterfaceMethodGroupState> globalStateMap =
+      createDexClassAndMethodMap();
 
   /** A map for caching all interface states. */
   private final Map<DexType, InterfaceReservationState> interfaceStateMap = new HashMap<>();
@@ -379,25 +398,19 @@
     this.appView = appView;
     this.minifierState = minifierState;
     this.subtypingInfo = subtypingInfo;
-    this.equivalence =
-        appView.options().getProguardConfiguration().isOverloadAggressively()
-            ? MethodSignatureEquivalence.get()
-            : MethodJavaSignatureEquivalence.get();
-    this.definitionEquivalence =
-        new Equivalence<>() {
-          @Override
-          protected boolean doEquivalent(DexEncodedMethod method, DexEncodedMethod other) {
-            return equivalence.equivalent(method.getReference(), other.getReference());
-          }
-
-          @Override
-          protected int doHash(DexEncodedMethod method) {
-            return equivalence.hash(method.getReference());
-          }
-        };
   }
 
-  private Comparator<Wrapper<DexEncodedMethod>> getDefaultInterfaceMethodOrdering() {
+  private static <V> DexClassAndMethodMap<V> createDexClassAndMethodMap() {
+    return new DexClassAndMethodMap<>(new HashMap<>()) {
+
+      @Override
+      protected Wrapper<DexClassAndMethod> wrap(DexClassAndMethod method) {
+        return equivalence.wrap(method);
+      }
+    };
+  }
+
+  private Comparator<DexClassAndMethod> getDefaultInterfaceMethodOrdering() {
     return Comparator.comparing(globalStateMap::get);
   }
 
@@ -430,10 +443,9 @@
     for (DexClass iface : interfaces) {
       InterfaceReservationState inheritanceState = interfaceStateMap.get(iface.type);
       assert inheritanceState != null;
-      for (DexEncodedMethod method : iface.methods()) {
-        Wrapper<DexEncodedMethod> key = definitionEquivalence.wrap(method);
+      for (DexClassAndMethod method : iface.classMethods()) {
         globalStateMap
-            .computeIfAbsent(key, k -> new InterfaceMethodGroupState())
+            .computeIfAbsent(method, ignoreKey(InterfaceMethodGroupState::new))
             .addState(method, inheritanceState);
       }
     }
@@ -451,21 +463,21 @@
     // Note that if the input does not use multi-interface lambdas unificationParent will remain
     // empty.
     timing.begin("Union-find");
-    DisjointSets<Wrapper<DexEncodedMethod>> unification = new DisjointSets<>();
+    DisjointSets<Wrapper<DexClassAndMethod>> unification = new DisjointSets<>();
 
     liveCallSites.forEach(
         callSite -> {
-          Set<Wrapper<DexEncodedMethod>> callSiteMethods = new HashSet<>();
+          Set<Wrapper<DexClassAndMethod>> callSiteMethods = new HashSet<>();
           // Don't report errors, as the set of call sites is a conservative estimate, and can
           // refer to interfaces which has been removed.
-          Set<DexEncodedMethod> implementedMethods =
+          DexClassAndMethodSet implementedMethods =
               appView.appInfo().lookupLambdaImplementedMethods(callSite, appView);
-          for (DexEncodedMethod method : implementedMethods) {
-            Wrapper<DexEncodedMethod> wrapped = definitionEquivalence.wrap(method);
-            InterfaceMethodGroupState groupState = globalStateMap.get(wrapped);
-            assert groupState != null : wrapped;
+          for (DexClassAndMethod method : implementedMethods) {
+            Wrapper<DexClassAndMethod> wrapper = equivalence.wrap(method);
+            InterfaceMethodGroupState groupState = globalStateMap.get(wrapper);
+            assert groupState != null : wrapper;
             groupState.addCallSite(callSite);
-            callSiteMethods.add(wrapped);
+            callSiteMethods.add(wrapper);
           }
           if (callSiteMethods.isEmpty()) {
             return;
@@ -480,8 +492,8 @@
               // name.
               DexClass iface = appView.definitionFor(implementedInterfaces.get(i));
               assert iface.isInterface();
-              for (DexEncodedMethod implementedMethod : implementedMethods) {
-                for (DexEncodedMethod virtualMethod : iface.virtualMethods()) {
+              for (DexClassAndMethod implementedMethod : implementedMethods) {
+                for (DexClassAndMethod virtualMethod : iface.virtualClassMethods()) {
                   boolean differentName = implementedMethod.getName() != virtualMethod.getName();
                   if (differentName
                       && MethodJavaSignatureEquivalence.getEquivalenceIgnoreName()
@@ -489,8 +501,7 @@
                               implementedMethod.getReference(), virtualMethod.getReference())) {
                     InterfaceMethodGroupState interfaceMethodGroupState =
                         globalStateMap.computeIfAbsent(
-                            definitionEquivalence.wrap(implementedMethod),
-                            k -> new InterfaceMethodGroupState());
+                            implementedMethod, ignoreKey(InterfaceMethodGroupState::new));
                     interfaceMethodGroupState.callSiteCollidingMethods.add(virtualMethod);
                   }
                 }
@@ -499,9 +510,9 @@
           }
           if (callSiteMethods.size() > 1) {
             // Implemented interfaces have different protos. Unify them.
-            Wrapper<DexEncodedMethod> mainKey = callSiteMethods.iterator().next();
-            Wrapper<DexEncodedMethod> representative = unification.findOrMakeSet(mainKey);
-            for (Wrapper<DexEncodedMethod> key : callSiteMethods) {
+            Wrapper<DexClassAndMethod> mainKey = callSiteMethods.iterator().next();
+            Wrapper<DexClassAndMethod> representative = unification.findOrMakeSet(mainKey);
+            for (Wrapper<DexClassAndMethod> key : callSiteMethods) {
               unification.unionWithMakeSet(representative, key);
             }
           }
@@ -512,32 +523,32 @@
     // We now have roots for all unions. Add all of the states for the groups to the method state
     // for the unions to allow consistent naming across different protos.
     timing.begin("States for union");
-    Map<Wrapper<DexEncodedMethod>, Set<Wrapper<DexEncodedMethod>>> unions =
-        unification.collectSets();
-
-    for (Wrapper<DexEncodedMethod> wrapped : unions.keySet()) {
-      InterfaceMethodGroupState groupState = globalStateMap.get(wrapped);
-      assert groupState != null;
-
-      for (Wrapper<DexEncodedMethod> groupedMethod : unions.get(wrapped)) {
-        DexEncodedMethod method = groupedMethod.get();
-        assert method != null;
-        groupState.appendMethodGroupState(globalStateMap.get(groupedMethod));
-      }
-    }
+    DexClassAndMethodMap<Set<Wrapper<DexClassAndMethod>>> unions = createDexClassAndMethodMap();
+    unification.consumeSets(
+        (representative, element) ->
+            unions.computeIfAbsent(representative, ignoreKey(HashSet::new)).add(element));
+    unions.forEach(
+        (representative, elements) -> {
+          InterfaceMethodGroupState groupState = globalStateMap.get(representative);
+          assert groupState != null;
+          for (Wrapper<DexClassAndMethod> groupedMethod : elements) {
+            groupState.appendMethodGroupState(globalStateMap.get(groupedMethod));
+          }
+        });
     timing.end();
 
     timing.begin("Sort");
     // Filter out the groups that is included both in the unification and in the map. We sort the
     // methods by the number of dependent states, so that we use short names for method that are
     // referenced in many places.
-    List<Wrapper<DexEncodedMethod>> interfaceMethodGroups =
-        globalStateMap.keySet().stream()
+    List<DexClassAndMethod> interfaceMethodGroups =
+        globalStateMap
+            .streamWrappedKeys()
             .filter(unification::isRepresentativeOrNotPresent)
+            .map(Wrapper::get)
             .sorted(
                 appView
-                    .options()
-                    .testing
+                    .testing()
                     .minifier
                     .getInterfaceMethodOrderingOrDefault(getDefaultInterfaceMethodOrdering()))
             .collect(Collectors.toList());
@@ -549,8 +560,8 @@
     timing.begin("Reserve in groups");
     // It is important that this entire phase is run before given new names, to ensure all
     // reservations are propagated to all naming states.
-    List<Wrapper<DexEncodedMethod>> nonReservedMethodGroups = new ArrayList<>();
-    for (Wrapper<DexEncodedMethod> interfaceMethodGroup : interfaceMethodGroups) {
+    List<DexClassAndMethod> nonReservedMethodGroups = new ArrayList<>();
+    for (DexClassAndMethod interfaceMethodGroup : interfaceMethodGroups) {
       InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
       assert groupState != null;
       DexString reservedName = groupState.getReservedName();
@@ -564,39 +575,43 @@
     timing.end();
 
     timing.begin("Rename in groups");
-    for (Wrapper<DexEncodedMethod> interfaceMethodGroup : nonReservedMethodGroups) {
+    for (DexClassAndMethod interfaceMethodGroup : nonReservedMethodGroups) {
       InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
       assert groupState != null;
       assert groupState.getReservedName() == null;
-      DexString newName = assignNewName(interfaceMethodGroup.get(), groupState);
+      DexString newName = assignNewName(interfaceMethodGroup, groupState);
       assert newName != null;
       Set<String> loggingFilter = appView.options().extensiveInterfaceMethodMinifierLoggingFilter;
       if (!loggingFilter.isEmpty()) {
-        Set<DexEncodedMethod> sourceMethods = groupState.methodStates.keySet();
-        if (sourceMethods.stream()
-            .map(DexEncodedMethod::toSourceString)
+        if (groupState
+            .methodStates
+            .streamKeys()
+            .map(DexClassAndMethod::toSourceString)
             .anyMatch(loggingFilter::contains)) {
-          print(interfaceMethodGroup.get().getReference(), sourceMethods, System.out);
+          print(
+              interfaceMethodGroup.getReference(),
+              groupState.methodStates.getKeysSorted(),
+              System.out);
         }
       }
     }
 
     // After all naming is completed for callsites, we must ensure to rename all interface methods
     // that can collide with the callsite method name.
-    for (Wrapper<DexEncodedMethod> interfaceMethodGroup : nonReservedMethodGroups) {
+    for (DexClassAndMethod interfaceMethodGroup : nonReservedMethodGroups) {
       InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
       if (groupState.callSiteCollidingMethods.isEmpty()) {
         continue;
       }
-      DexEncodedMethod key = interfaceMethodGroup.get();
-      MethodNamingState<?> keyNamingState = minifierState.getNamingState(key.getHolderType());
-      DexString existingRenaming = keyNamingState.newOrReservedNameFor(key);
+      MethodNamingState<?> keyNamingState =
+          minifierState.getNamingState(interfaceMethodGroup.getHolderType());
+      DexString existingRenaming = keyNamingState.newOrReservedNameFor(interfaceMethodGroup);
       assert existingRenaming != null;
-      for (DexEncodedMethod collidingMethod : groupState.callSiteCollidingMethods) {
+      for (DexClassAndMethod collidingMethod : groupState.callSiteCollidingMethods) {
         DexString newNameInGroup = newNameInGroup(collidingMethod, keyNamingState, groupState);
         minifierState.putRenaming(collidingMethod, newNameInGroup);
         MethodNamingState<?> methodNamingState =
-            minifierState.getNamingState(collidingMethod.getReference().holder);
+            minifierState.getNamingState(collidingMethod.getHolderType());
         methodNamingState.addRenaming(newNameInGroup, collidingMethod);
         keyNamingState.addRenaming(newNameInGroup, collidingMethod);
       }
@@ -606,7 +621,7 @@
     timing.end(); // end compute timing
   }
 
-  private DexString assignNewName(DexEncodedMethod method, InterfaceMethodGroupState groupState) {
+  private DexString assignNewName(DexClassAndMethod method, InterfaceMethodGroupState groupState) {
     assert groupState.getReservedName() == null;
     assert groupState.methodStates.containsKey(method);
     assert groupState.containsReservation(method, method.getHolderType());
@@ -620,7 +635,7 @@
   }
 
   private DexString newNameInGroup(
-      DexEncodedMethod method,
+      DexClassAndMethod method,
       MethodNamingState<?> namingState,
       InterfaceMethodGroupState groupState) {
     // Check if the name is available in all states.
@@ -663,50 +678,52 @@
                     }));
   }
 
-  private boolean verifyAllCallSitesAreRepresentedIn(List<Wrapper<DexEncodedMethod>> groups) {
-    Set<Wrapper<DexEncodedMethod>> unifiedMethods = new HashSet<>(groups);
+  private boolean verifyAllCallSitesAreRepresentedIn(List<DexClassAndMethod> groups) {
+    Set<Wrapper<DexClassAndMethod>> unifiedMethods = new HashSet<>(groups.size());
+    groups.forEach(group -> unifiedMethods.add(equivalence.wrap(group)));
     Set<DexCallSite> unifiedSeen = new HashSet<>();
     Set<DexCallSite> seen = new HashSet<>();
-    for (Map.Entry<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> state :
-        globalStateMap.entrySet()) {
-      for (DexCallSite callSite : state.getValue().callSites) {
-        seen.add(callSite);
-        if (unifiedMethods.contains(state.getKey())) {
-          boolean added = unifiedSeen.add(callSite);
-          assert added;
-        }
-      }
-    }
+    globalStateMap.forEach(
+        (key, value) -> {
+          for (DexCallSite callSite : value.callSites) {
+            seen.add(callSite);
+            if (unifiedMethods.contains(equivalence.wrap(key))) {
+              boolean added = unifiedSeen.add(callSite);
+              assert added;
+            }
+          }
+        });
     assert seen.size() == unifiedSeen.size();
     assert unifiedSeen.containsAll(seen);
     return true;
   }
 
-  private boolean verifyAllMethodsAreRepresentedIn(List<Wrapper<DexEncodedMethod>> groups) {
-    Set<Wrapper<DexEncodedMethod>> unifiedMethods = new HashSet<>(groups);
+  private boolean verifyAllMethodsAreRepresentedIn(List<DexClassAndMethod> groups) {
+    Set<Wrapper<DexClassAndMethod>> unifiedMethods = new HashSet<>(groups.size());
+    groups.forEach(group -> unifiedMethods.add(equivalence.wrap(group)));
     Set<DexEncodedMethod> unifiedSeen = Sets.newIdentityHashSet();
     Set<DexEncodedMethod> seen = Sets.newIdentityHashSet();
-    for (Map.Entry<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> state :
-        globalStateMap.entrySet()) {
-      for (DexEncodedMethod method : state.getValue().methodStates.keySet()) {
-        seen.add(method);
-        if (unifiedMethods.contains(state.getKey())) {
-          boolean added = unifiedSeen.add(method);
-          assert added;
-        }
-      }
-    }
+    globalStateMap.forEach(
+        (representative, value) ->
+            value.methodStates.forEachKey(
+                method -> {
+                  seen.add(method.getDefinition());
+                  if (unifiedMethods.contains(equivalence.wrap(representative))) {
+                    boolean added = unifiedSeen.add(method.getDefinition());
+                    assert added;
+                  }
+                }));
     assert seen.size() == unifiedSeen.size();
     assert unifiedSeen.containsAll(seen);
     return true;
   }
 
-  private void print(DexMethod method, Set<DexEncodedMethod> sourceMethods, PrintStream out) {
+  private void print(DexMethod method, List<DexClassAndMethod> sourceMethods, PrintStream out) {
     out.println("-----------------------------------------------------------------------");
     out.println("assignNameToInterfaceMethod(`" + method.toSourceString() + "`)");
     out.println("-----------------------------------------------------------------------");
     out.println("Source methods:");
-    for (DexEncodedMethod sourceMethod : sourceMethods) {
+    for (DexClassAndMethod sourceMethod : sourceMethods) {
       out.println("  " + sourceMethod.toSourceString());
     }
     out.println("States:");
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index 58a4dc6..15b2aae 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -5,8 +5,9 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.ProgramField;
@@ -15,7 +16,7 @@
 public interface MemberNamingStrategy {
 
   DexString next(
-      DexEncodedMethod method,
+      DexClassAndMethod method,
       InternalNamingState internalState,
       BiPredicate<DexString, DexMethod> isAvailable);
 
@@ -24,9 +25,13 @@
       InternalNamingState internalState,
       BiPredicate<DexString, ProgramField> isAvailable);
 
-  DexString getReservedName(DexEncodedMethod method, DexClass holder);
+  DexString getReservedName(DexClassAndMethod method);
 
-  DexString getReservedName(DexEncodedField field, DexClass holder);
+  DexString getReservedName(DexClassAndField field);
 
-  boolean allowMemberRenaming(DexClass holder);
+  boolean allowMemberRenaming(DexClass clazz);
+
+  default boolean allowMemberRenaming(DexClassAndMember<?, ?> member) {
+    return allowMemberRenaming(member.getHolder());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 4dd3d15..fa5089d 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -8,6 +8,8 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
@@ -19,15 +21,17 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -105,9 +109,8 @@
   // from the method name minifier to the interface method name minifier.
   class State {
 
-    @SuppressWarnings("ReferenceEquality")
-    void putRenaming(DexEncodedMethod key, DexString newName) {
-      if (newName != key.getName()) {
+    void putRenaming(DexClassAndMethod key, DexString newName) {
+      if (newName.isNotIdenticalTo(key.getName())) {
         renaming.put(key.getReference(), newName);
       }
     }
@@ -129,8 +132,8 @@
       return frontiers.getOrDefault(type, type);
     }
 
-    DexString getReservedName(DexEncodedMethod method, DexClass holder) {
-      return strategy.getReservedName(method, holder);
+    DexString getReservedName(DexClassAndMethod method) {
+      return strategy.getReservedName(method);
     }
   }
 
@@ -163,14 +166,9 @@
   }
 
   private Function<DexMethod, ?> getReservationKeyTransform() {
-    if (appView.options().getProguardConfiguration().isOverloadAggressively()
-        && appView.options().isGeneratingClassFiles()) {
-      // Use the full proto as key, hence reuse names based on full signature.
-      return method -> method.proto;
-    } else {
-      // Only use the parameters as key, hence do not reuse names on return type.
-      return method -> method.proto.parameters;
-    }
+    // Only use the parameters as key, hence do not reuse names on return type. Returning the full
+    // proto here implements aggressive overloading.
+    return DexMethod::getParameters;
   }
 
   private Function<DexMethod, ?> getNamingKeyTransform() {
@@ -244,23 +242,27 @@
                               .getOrDefault(clazz.superType, rootNamingState)
                               .createChild(reservationState));
               if (strategy.allowMemberRenaming(clazz)) {
-                for (DexEncodedMethod method : clazz.allMethodsSorted()) {
-                  assignNameToMethod(clazz, method, namingState);
+                List<DexClassAndMethod> allMethodsSorted =
+                    ListUtils.sort(
+                        clazz.classMethods(),
+                        Comparator.comparing(DexClassAndMember::getReference),
+                        clazz.getMethodCollection().size());
+                for (DexClassAndMethod method : allMethodsSorted) {
+                  assignNameToMethod(method, namingState);
                 }
               }
             });
   }
 
-  @SuppressWarnings("ReferenceEquality")
   private void renameMethodsInUnrelatedClasspathClasses() {
     if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
       appView
           .appInfo()
           .forEachReferencedClasspathClass(
               clazz -> {
-                for (DexEncodedMethod method : clazz.methods()) {
-                  DexString reservedName = strategy.getReservedName(method, clazz);
-                  if (reservedName != null && reservedName != method.getReference().name) {
+                for (DexClassAndMethod method : clazz.classMethods()) {
+                  DexString reservedName = strategy.getReservedName(method);
+                  if (reservedName != null && reservedName.isNotIdenticalTo(method.getName())) {
                     renaming.put(method.getReference(), reservedName);
                   }
                 }
@@ -268,20 +270,18 @@
     }
   }
 
-  @SuppressWarnings("ReferenceEquality")
-  private void assignNameToMethod(
-      DexClass holder, DexEncodedMethod method, MethodNamingState<?> state) {
-    if (method.isInitializer()) {
+  private void assignNameToMethod(DexClassAndMethod method, MethodNamingState<?> state) {
+    if (method.getDefinition().isInitializer()) {
       return;
     }
     // The strategy may have an explicit naming for this member which we query first. It may be that
     // the strategy will return the identity name, for which we have to look into a previous
     // renaming tracked by the state.
-    DexString newName = strategy.getReservedName(method, holder);
-    if (newName == null || newName == method.getName()) {
+    DexString newName = strategy.getReservedName(method);
+    if (newName == null || newName.isIdenticalTo(method.getName())) {
       newName = state.newOrReservedNameFor(method);
     }
-    if (method.getName() != newName) {
+    if (newName.isNotIdenticalTo(method.getName())) {
       renaming.put(method.getReference(), newName);
     }
     state.addRenaming(newName, method);
@@ -335,20 +335,22 @@
       // have to do byte-code rewriting against a mapping file to observe the issue. Doing that they
       // may as well just adjust the keep rules to keep the targets of bridges.
       // See b/290711987 for an actual issue regarding this.
-      Set<DexEncodedMethod> bridgeMethodCandidates = Sets.newIdentityHashSet();
-      Iterable<DexEncodedMethod> methods = shuffleMethods(holder.methods(), appView.options());
-      for (DexEncodedMethod method : methods) {
-        DexString reservedName = strategy.getReservedName(method, holder);
+      DexClassAndMethodSet bridgeMethodCandidates = DexClassAndMethodSet.create();
+      Iterable<DexClassAndMethod> methods =
+          shuffleMethods(holder.classMethods(), appView.options());
+      for (DexClassAndMethod method : methods) {
+        DexString reservedName = strategy.getReservedName(method);
         if (reservedName != null) {
           state.reserveName(reservedName, method);
-        } else if (appView.options().isGeneratingClassFiles() && method.isSyntheticBridgeMethod()) {
+        } else if (appView.options().isGeneratingClassFiles()
+            && method.getDefinition().isSyntheticBridgeMethod()) {
           bridgeMethodCandidates.add(method);
         }
       }
       Map<DexString, Set<Integer>> methodNamesToReserve =
           computeBridgesThatAreReserved(holder, bridgeMethodCandidates);
       if (!methodNamesToReserve.isEmpty()) {
-        for (DexEncodedMethod method : methods) {
+        for (DexClassAndMethod method : methods) {
           if (methodNamesToReserve
               .getOrDefault(method.getName(), Collections.emptySet())
               .contains(method.getProto().getArity())) {
@@ -360,7 +362,7 @@
   }
 
   private Map<DexString, Set<Integer>> computeBridgesThatAreReserved(
-      DexClass holder, Set<DexEncodedMethod> methods) {
+      DexClass holder, DexClassAndMethodSet methods) {
     if (methods.isEmpty()) {
       return Collections.emptyMap();
     }
@@ -495,8 +497,8 @@
 
   // Shuffles the given methods if assertions are enabled and deterministic debugging is disabled.
   // Used to ensure that the generated output is deterministic.
-  private static Iterable<DexEncodedMethod> shuffleMethods(
-      Iterable<DexEncodedMethod> methods, InternalOptions options) {
-    return options.testing.irOrdering.order(methods);
+  private static Iterable<DexClassAndMethod> shuffleMethods(
+      Iterable<DexClassAndMethod> methods, InternalOptions options) {
+    return options.testing.irOrdering.orderClassMethods(methods);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index 922c062..7a38768 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.naming.MethodNamingState.InternalNewNameState;
@@ -45,12 +45,12 @@
         this, this.keyTransform, this.namingStrategy, frontierReservationState);
   }
 
-  DexString newOrReservedNameFor(DexEncodedMethod method) {
+  DexString newOrReservedNameFor(DexClassAndMethod method) {
     return newOrReservedNameFor(method, this::isAvailable);
   }
 
   DexString newOrReservedNameFor(
-      DexEncodedMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
+      DexClassAndMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
     DexString newName = getAssignedName(method.getReference());
     if (newName != null) {
       return newName;
@@ -67,14 +67,14 @@
     return nextName(method, isAvailable);
   }
 
-  DexString nextName(DexEncodedMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
+  DexString nextName(DexClassAndMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
     InternalNewNameState internalState = getOrCreateInternalState(method.getReference());
     DexString newName = namingStrategy.next(method, internalState, isAvailable);
     assert newName != null;
     return newName;
   }
 
-  void addRenaming(DexString newName, DexEncodedMethod method) {
+  void addRenaming(DexString newName, DexClassAndMethod method) {
     InternalNewNameState internalState = getOrCreateInternalState(method.getReference());
     internalState.addRenaming(newName, method.getReference());
   }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodReservationState.java b/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
index 61aed28..68d0378 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.naming;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.naming.MethodReservationState.InternalReservationState;
@@ -37,7 +37,7 @@
     return new MethodReservationState<>(this, this.keyTransform);
   }
 
-  void reserveName(DexString reservedName, DexEncodedMethod method) {
+  void reserveName(DexString reservedName, DexClassAndMethod method) {
     try {
       getOrCreateInternalState(method.getReference()).reserveName(method, reservedName);
     } catch (AssertionError err) {
@@ -92,7 +92,7 @@
       return originalToReservedNames.get(MethodSignatureEquivalence.get().wrap(method));
     }
 
-    void reserveName(DexEncodedMethod method, DexString name) {
+    void reserveName(DexClassAndMethod method, DexString name) {
       if (reservedNames == null) {
         assert originalToReservedNames == null;
         originalToReservedNames = new HashMap<>();
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 96275ab..6c08cef 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -3,19 +3,22 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
 import static com.android.tools.r8.utils.SymbolGenerationUtils.RESERVED_NAMES;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
@@ -206,8 +209,9 @@
 
     @Override
     public DexString reservedDescriptor(DexType type) {
-      if (!appView.appInfo().isMinificationAllowed(type)) {
-        return type.descriptor;
+      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+      if (clazz == null || !appView.getKeepInfo(clazz).isMinificationAllowed(appView.options())) {
+        return type.getDescriptor();
       }
       return null;
     }
@@ -277,10 +281,14 @@
 
     @Override
     public DexString next(
-        DexEncodedMethod method,
+        DexClassAndMethod method,
         InternalNamingState internalState,
         BiPredicate<DexString, DexMethod> isAvailable) {
-      assert checkAllowMemberRenaming(method.getHolderType());
+      if (!method.isProgramMethod()) {
+        assert isAvailable.test(method.getName(), method.getReference());
+        return method.getName();
+      }
+      assert allowMemberRenaming(method);
       DexString candidate;
       do {
         candidate = getNextName(internalState);
@@ -293,7 +301,7 @@
         ProgramField field,
         InternalNamingState internalState,
         BiPredicate<DexString, ProgramField> isAvailable) {
-      assert checkAllowMemberRenaming(field.getHolderType());
+      assert allowMemberRenaming(field);
       DexString candidate;
       do {
         candidate = getNextName(internalState);
@@ -306,40 +314,39 @@
     }
 
     @Override
-    public DexString getReservedName(DexEncodedMethod method, DexClass holder) {
-      if (!allowMemberRenaming(holder)
-          || holder.accessFlags.isAnnotation()
-          || method.accessFlags.isConstructor()
-          || !appView.appInfo().isMinificationAllowed(method)) {
-        return method.getReference().name;
+    public DexString getReservedName(DexClassAndMethod method) {
+      if (!allowMemberRenaming(method)) {
+        return method.getName();
+      }
+      assert method.isProgramMethod();
+      ProgramMethod programMethod = method.asProgramMethod();
+      if (method.getHolder().isAnnotation()
+          || method.getAccessFlags().isConstructor()
+          || !appView.getKeepInfo(programMethod).isMinificationAllowed(appView.options())) {
+        return method.getName();
       }
       if (desugaredLibraryRenaming
-          && method.isLibraryMethodOverride().isTrue()
-          && appView.typeRewriter.hasRewrittenTypeInSignature(
-              method.getReference().proto, appView)) {
+          && method.getDefinition().isLibraryMethodOverride().isTrue()
+          && appView.typeRewriter.hasRewrittenTypeInSignature(method.getProto(), appView)) {
         // With desugared library, call-backs names are reserved here.
-        return method.getReference().name;
+        return method.getName();
       }
       return null;
     }
 
     @Override
-    public DexString getReservedName(DexEncodedField field, DexClass holder) {
-      if (holder.isLibraryClass() || !appView.appInfo().isMinificationAllowed(field)) {
-        return field.getReference().name;
+    public DexString getReservedName(DexClassAndField field) {
+      ProgramField programField = field.asProgramField();
+      if (programField == null
+          || !appView.getKeepInfo(programField).isMinificationAllowed(appView.options())) {
+        return field.getName();
       }
       return null;
     }
 
     @Override
-    public boolean allowMemberRenaming(DexClass holder) {
-      return holder.isProgramClass();
-    }
-
-    public boolean checkAllowMemberRenaming(DexType holder) {
-      DexClass clazz = appView.definitionFor(holder);
-      assert clazz != null && allowMemberRenaming(clazz);
-      return true;
+    public boolean allowMemberRenaming(DexClass clazz) {
+      return clazz.isProgramClass();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index ed4bbbb..3e551cf 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.google.common.collect.Sets;
 import java.util.Arrays;
 import java.util.Set;
@@ -59,7 +61,7 @@
       return callSite.methodName;
     }
     AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-    Set<DexEncodedMethod> lambdaImplementedMethods =
+    DexClassAndMethodSet lambdaImplementedMethods =
         appViewWithLiveness.appInfo().lookupLambdaImplementedMethods(callSite, appViewWithLiveness);
     if (lambdaImplementedMethods.isEmpty()) {
       return callSite.methodName;
@@ -70,7 +72,7 @@
         lookupMethod(lambdaImplementedMethodReference, appView.dexItemFactory()).getName();
     // Verify that all lambda implemented methods are renamed consistently.
     assert lambdaImplementedMethods.stream()
-        .map(DexEncodedMethod::getReference)
+        .map(DexClassAndMethod::getReference)
         .map(reference -> lookupMethod(reference, appView.dexItemFactory()))
         .map(DexMethod::getName)
         .allMatch(name -> name == renamedMethodName);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 588666a..ddb57d6 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.naming;
 
 import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass;
 import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.getInterfaceClassType;
 import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.isCompanionClassType;
@@ -13,12 +14,15 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
 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.DexItemFactory;
 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.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -187,10 +191,9 @@
           memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, false));
     } else {
       // We have to ensure we do not rename to an existing member, that cannot be renamed.
-      if (clazz == null || !appView.options().isMinifying()) {
-        notMappedReferences.add(type);
-      } else if (appView.options().isMinifying()
-          && !appView.appInfo().isMinificationAllowed(type)) {
+      DexProgramClass programClass = asProgramClassOrNull(clazz);
+      if (programClass == null
+          || !appView.getKeepInfo(programClass).isMinificationAllowed(appView.options())) {
         notMappedReferences.add(type);
       }
     }
@@ -400,7 +403,9 @@
     public DexString next(
         DexType type, char[] packagePrefix, InternalNamingState state, Predicate<String> isUsed) {
       assert !mappings.containsKey(type);
-      assert appView.appInfo().isMinificationAllowed(type);
+      assert appView
+          .getKeepInfo(appView.definitionFor(type).asProgramClass())
+          .isMinificationAllowed(appView.options());
       return super.next(
           type,
           packagePrefix,
@@ -420,19 +425,21 @@
       //  members that can be reserved differently in the hierarchy.
       DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(type);
       if (clazz == null) {
-        return type.descriptor;
-      }
-      if (clazz.isNotProgramClass() && mappings.containsKey(type)) {
-        return mappings.get(type);
+        return type.getDescriptor();
       }
       if (clazz.isProgramClass()) {
-        if (appView.appInfo().isMinificationAllowed(clazz.asProgramClass())) {
+        DexProgramClass programClass = clazz.asProgramClass();
+        if (appView.getKeepInfo(programClass).isMinificationAllowed(appView.options())) {
           return mappings.get(type);
         }
         // TODO(b/136694827): Report a warning here if in the mapping since the user may find this
         //  non intuitive.
+      } else {
+        if (mappings.containsKey(type)) {
+          return mappings.get(type);
+        }
       }
-      return type.descriptor;
+      return type.getDescriptor();
     }
 
     @Override
@@ -456,13 +463,11 @@
     @Override
     @SuppressWarnings("ReferenceEquality")
     public DexString next(
-        DexEncodedMethod method,
+        DexClassAndMethod method,
         InternalNamingState internalState,
         BiPredicate<DexString, DexMethod> isAvailable) {
       DexMethod reference = method.getReference();
-      DexClass holder = appView.definitionForHolder(reference);
-      assert holder != null;
-      DexString reservedName = getReservedName(method, reference.name, holder);
+      DexString reservedName = getReservedName(method, reference.getName());
       DexString nextName;
       if (reservedName != null) {
         if (!isAvailable.test(reservedName, reference)) {
@@ -471,11 +476,14 @@
         nextName = reservedName;
       } else {
         assert !mappedNames.containsKey(reference);
-        assert appView.appInfo().isMinificationAllowed(method);
+        assert !method.isProgramMethod()
+            || appView
+                .getKeepInfo(method.asProgramMethod())
+                .isMinificationAllowed(appView.options());
         nextName = super.next(method, internalState, isAvailable);
       }
-      assert nextName == reference.name || !method.isInitializer();
-      assert nextName == reference.name || !holder.isAnnotation();
+      assert nextName.isIdenticalTo(reference.getName()) || !method.getDefinition().isInitializer();
+      assert nextName.isIdenticalTo(reference.getName()) || !method.getHolder().isAnnotation();
       return nextName;
     }
 
@@ -485,8 +493,7 @@
         InternalNamingState internalState,
         BiPredicate<DexString, ProgramField> isAvailable) {
       DexField reference = field.getReference();
-      DexString reservedName =
-          getReservedName(field.getDefinition(), reference.name, field.getHolder());
+      DexString reservedName = getReservedName(field, reference.getName());
       if (reservedName != null) {
         if (!isAvailable.test(reservedName, field)) {
           reportReservationError(reference, reservedName);
@@ -494,35 +501,34 @@
         return reservedName;
       }
       assert !mappedNames.containsKey(reference);
-      assert appView.appInfo().isMinificationAllowed(field);
+      assert appView.getKeepInfo(field).isMinificationAllowed(appView.options());
       return super.next(field, internalState, isAvailable);
     }
 
     @Override
-    public DexString getReservedName(DexEncodedMethod method, DexClass holder) {
-      return getReservedName(method, method.getReference().name, holder);
+    public DexString getReservedName(DexClassAndMethod method) {
+      return getReservedName(method, method.getName());
     }
 
     @Override
-    public DexString getReservedName(DexEncodedField field, DexClass holder) {
-      return getReservedName(field, field.getReference().name, holder);
+    public DexString getReservedName(DexClassAndField field) {
+      return getReservedName(field, field.getName());
     }
 
-    private DexString getReservedName(DexDefinition definition, DexString name, DexClass holder) {
-      assert definition.isDexEncodedMethod() || definition.isDexEncodedField();
+    private DexString getReservedName(DexClassAndMember<?, ?> definition, DexString name) {
       // Always consult the mapping for renamed members that are not on program path.
       DexReference reference = definition.getReference();
-      if (holder.isNotProgramClass()) {
+      if (definition.getHolder().isNotProgramClass()) {
         if (mappedNames.containsKey(reference)) {
           return factory.createString(mappedNames.get(reference).getRenamedName());
         }
         return name;
       }
-      assert holder.isProgramClass();
+      assert definition.isProgramMember();
       DexString reservedName =
-          definition.isDexEncodedMethod()
-              ? super.getReservedName(definition.asDexEncodedMethod(), holder)
-              : super.getReservedName(definition.asDexEncodedField(), holder);
+          definition.isMethod()
+              ? super.getReservedName(definition.asMethod())
+              : super.getReservedName(definition.asField());
       if (reservedName != null) {
         return reservedName;
       }
@@ -533,7 +539,7 @@
     }
 
     @Override
-    public boolean allowMemberRenaming(DexClass holder) {
+    public boolean allowMemberRenaming(DexClass clazz) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
index 2b66d18..fe4799b 100644
--- a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.ReservedFieldNamingState.InternalState;
@@ -43,8 +44,8 @@
     return internalState == null ? null : internalState.getReservedByName(name);
   }
 
-  void markReserved(DexString name, DexString originalName, DexType type) {
-    getOrCreateInternalState(type).markReserved(name, originalName);
+  void markReserved(DexString name, DexClassAndField field) {
+    getOrCreateInternalState(field.getType()).markReserved(name, field.getName());
   }
 
   void includeReservations(ReservedFieldNamingState reservedNames) {
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 e222e20..6fb0c2f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -42,7 +42,6 @@
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
-import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
@@ -71,6 +70,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.ThrowingSet;
 import com.android.tools.r8.utils.structural.Ordered;
@@ -764,14 +764,14 @@
    * @return Methods implemented by the lambda expression that created the {@code callSite}.
    */
   @SuppressWarnings("ReferenceEquality")
-  public Set<DexEncodedMethod> lookupLambdaImplementedMethods(
+  public DexClassAndMethodSet lookupLambdaImplementedMethods(
       DexCallSite callSite, AppView<AppInfoWithLiveness> appView) {
     assert checkIfObsolete();
     List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, appView);
     if (callSiteInterfaces == null || callSiteInterfaces.isEmpty()) {
-      return Collections.emptySet();
+      return DexClassAndMethodSet.empty();
     }
-    Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+    DexClassAndMethodSet result = DexClassAndMethodSet.create();
     Deque<DexType> worklist = new ArrayDeque<>(callSiteInterfaces);
     Set<DexType> visited = Sets.newIdentityHashSet();
     while (!worklist.isEmpty()) {
@@ -794,8 +794,9 @@
         continue;
       }
       assert clazz.isInterface();
-      for (DexEncodedMethod method : clazz.virtualMethods()) {
-        if (method.getReference().name == callSite.methodName && method.accessFlags.isAbstract()) {
+      for (DexClassAndMethod method : clazz.virtualClassMethods()) {
+        if (method.getName().isIdenticalTo(callSite.methodName)
+            && method.getAccessFlags().isAbstract()) {
           result.add(method);
         }
       }
@@ -1009,30 +1010,6 @@
     return this;
   }
 
-  @Deprecated
-  public boolean isMinificationAllowed(DexProgramClass clazz) {
-    return options().isMinificationEnabled()
-        && keepInfo.getInfo(clazz).isMinificationAllowed(options());
-  }
-
-  @Deprecated
-  public boolean isMinificationAllowed(ProgramDefinition definition) {
-    return options().isMinificationEnabled()
-        && keepInfo.getInfo(definition).isMinificationAllowed(options());
-  }
-
-  @Deprecated
-  public boolean isMinificationAllowed(DexDefinition definition) {
-    return options().isMinificationEnabled()
-        && keepInfo.getInfo(definition, this).isMinificationAllowed(options());
-  }
-
-  @Deprecated
-  public boolean isMinificationAllowed(DexType reference) {
-    return options().isMinificationEnabled()
-        && keepInfo.getClassInfo(reference, this).isMinificationAllowed(options());
-  }
-
   public boolean isRepackagingAllowed(DexProgramClass clazz, AppView<?> appView) {
     if (!keepInfo.getInfo(clazz).isRepackagingAllowed(options())) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index d7e4ff2..98bb147 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -609,10 +609,6 @@
     return rules;
   }
 
-  public boolean isOverloadAggressively() {
-    return false;
-  }
-
   public List<String> getObfuscationDictionary() {
     return obfuscationDictionary;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/DexClassAndMethodEquivalence.java b/src/main/java/com/android/tools/r8/utils/DexClassAndMethodEquivalence.java
new file mode 100644
index 0000000..acb221d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DexClassAndMethodEquivalence.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2023, 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 com.android.tools.r8.graph.DexClassAndMethod;
+import com.google.common.base.Equivalence;
+
+public class DexClassAndMethodEquivalence extends Equivalence<DexClassAndMethod> {
+
+  private static final DexClassAndMethodEquivalence INSTANCE = new DexClassAndMethodEquivalence();
+
+  private DexClassAndMethodEquivalence() {}
+
+  public static DexClassAndMethodEquivalence get() {
+    return INSTANCE;
+  }
+
+  @Override
+  protected boolean doEquivalent(DexClassAndMethod method, DexClassAndMethod other) {
+    return method.getDefinition() == other.getDefinition();
+  }
+
+  @Override
+  protected int doHash(DexClassAndMethod method) {
+    return method.getReference().hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DisjointSets.java b/src/main/java/com/android/tools/r8/utils/DisjointSets.java
index d8e42b9..bc5e129 100644
--- a/src/main/java/com/android/tools/r8/utils/DisjointSets.java
+++ b/src/main/java/com/android/tools/r8/utils/DisjointSets.java
@@ -4,10 +4,13 @@
 
 package com.android.tools.r8.utils;
 
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
 
 /**
  * Disjoint sets of instances of type T. Each of the sets will be represented by one of the
@@ -134,12 +137,18 @@
   /** Returns the sets currently represented. */
   public Map<T, Set<T>> collectSets() {
     Map<T, Set<T>> unification = new HashMap<>();
+    consumeSets(
+        (representative, element) ->
+            unification.computeIfAbsent(representative, ignoreKey(HashSet::new)).add(element));
+    return unification;
+  }
+
+  public void consumeSets(BiConsumer<T, T> consumer) {
     for (T element : parent.keySet()) {
       // Find root with path-compression.
       T representative = findSet(element);
-      unification.computeIfAbsent(representative, k -> new HashSet<>()).add(element);
+      consumer.accept(representative, element);
     }
-    return unification;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/utils/IROrdering.java b/src/main/java/com/android/tools/r8/utils/IROrdering.java
index db97cfa..4d1508d 100644
--- a/src/main/java/com/android/tools/r8/utils/IROrdering.java
+++ b/src/main/java/com/android/tools/r8/utils/IROrdering.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.google.common.collect.Lists;
 import java.util.Collection;
@@ -16,6 +17,8 @@
 
   Iterable<DexEncodedMethod> order(Iterable<DexEncodedMethod> methods);
 
+  Iterable<DexClassAndMethod> orderClassMethods(Iterable<DexClassAndMethod> methods);
+
   Collection<DexEncodedMethod> order(Collection<DexEncodedMethod> methods);
 
   Set<DexEncodedMethod> order(Set<DexEncodedMethod> methods);
@@ -36,6 +39,11 @@
     }
 
     @Override
+    public Iterable<DexClassAndMethod> orderClassMethods(Iterable<DexClassAndMethod> methods) {
+      return methods;
+    }
+
+    @Override
     public Collection<DexEncodedMethod> order(Collection<DexEncodedMethod> methods) {
       return methods;
     }
@@ -64,6 +72,13 @@
     }
 
     @Override
+    public List<DexClassAndMethod> orderClassMethods(Iterable<DexClassAndMethod> methods) {
+      List<DexClassAndMethod> toShuffle = Lists.newArrayList(methods);
+      Collections.shuffle(toShuffle);
+      return toShuffle;
+    }
+
+    @Override
     public List<DexEncodedMethod> order(Collection<DexEncodedMethod> methods) {
       return order((Iterable<DexEncodedMethod>) methods);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 24a5e7e..e38cfe8 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.graph.AppView.WholeProgramOptimizations;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
@@ -114,7 +115,6 @@
 import com.android.tools.r8.verticalclassmerging.VerticalClassMergerOptions;
 import com.android.tools.r8.verticalclassmerging.VerticallyMergedClasses;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -2502,11 +2502,10 @@
 
       public Comparator<DexMethod> interfaceMethodOrdering = null;
 
-      public Comparator<Wrapper<DexEncodedMethod>> getInterfaceMethodOrderingOrDefault(
-          Comparator<Wrapper<DexEncodedMethod>> comparator) {
+      public Comparator<? super DexClassAndMethod> getInterfaceMethodOrderingOrDefault(
+          Comparator<DexClassAndMethod> comparator) {
         if (interfaceMethodOrdering != null) {
-          return (a, b) ->
-              interfaceMethodOrdering.compare(a.get().getReference(), b.get().getReference());
+          return (a, b) -> interfaceMethodOrdering.compare(a.getReference(), b.getReference());
         }
         return comparator;
       }
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 98402c3..bbec355 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -297,6 +298,13 @@
     void accept(T item, int index);
   }
 
+  public static <T> List<T> sort(Iterable<T> items, Comparator<T> comparator, int numberOfItems) {
+    List<T> sorted = new ArrayList<>(numberOfItems);
+    Iterables.addAll(sorted, items);
+    sorted.sort(comparator);
+    return sorted;
+  }
+
   public static <T> List<T> sort(Collection<T> items, Comparator<T> comparator) {
     List<T> sorted = new ArrayList<>(items);
     sorted.sort(comparator);
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramMethodEquivalence.java b/src/main/java/com/android/tools/r8/utils/ProgramMethodEquivalence.java
index cdb98b8..91553b8 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramMethodEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramMethodEquivalence.java
@@ -18,7 +18,6 @@
   }
 
   @Override
-  @SuppressWarnings("ReferenceEquality")
   protected boolean doEquivalent(ProgramMethod method, ProgramMethod other) {
     return method.getDefinition() == other.getDefinition();
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java
index 522ff17..ae458e4 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java
@@ -18,7 +18,7 @@
   }
 
   @Override
-  Wrapper<K> wrap(K field) {
+  protected Wrapper<K> wrap(K field) {
     return DexClassAndFieldEquivalence.get().wrap(field);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
index eb747e4..6dd73cf 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
@@ -5,8 +5,12 @@
 package com.android.tools.r8.utils.collections;
 
 import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.TriPredicate;
 import com.google.common.base.Equivalence.Wrapper;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.function.BiConsumer;
@@ -15,6 +19,7 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
+import java.util.stream.Stream;
 
 public abstract class DexClassAndMemberMap<K extends DexClassAndMember<?, ?>, V> {
 
@@ -37,7 +42,11 @@
   }
 
   public V computeIfAbsent(K member, Function<K, V> fn) {
-    return backing.computeIfAbsent(wrap(member), key -> fn.apply(key.get()));
+    return computeIfAbsent(wrap(member), fn);
+  }
+
+  public V computeIfAbsent(Wrapper<K> wrapper, Function<K, V> fn) {
+    return backing.computeIfAbsent(wrapper, key -> fn.apply(key.get()));
   }
 
   public boolean containsKey(K member) {
@@ -48,6 +57,10 @@
     backing.forEach((wrapper, value) -> consumer.accept(wrapper.get(), value));
   }
 
+  public void forEachKey(Consumer<K> consumer) {
+    backing.keySet().forEach(wrapper -> consumer.accept(wrapper.get()));
+  }
+
   public void forEachValue(Consumer<V> consumer) {
     backing.values().forEach(consumer);
   }
@@ -56,6 +69,16 @@
     return backing.get(wrap(member));
   }
 
+  public V get(Wrapper<K> wrapper) {
+    return backing.get(wrapper);
+  }
+
+  public List<K> getKeysSorted() {
+    List<K> keys = new ArrayList<>(size());
+    backing.keySet().forEach(key -> keys.add(key.get()));
+    return ListUtils.sort(keys, (x, y) -> x.getReference().compareTo(y.getReference()));
+  }
+
   public V getOrDefault(K member, V defaultValue) {
     return backing.getOrDefault(wrap(member), defaultValue);
   }
@@ -94,5 +117,25 @@
     return backing.size();
   }
 
-  abstract Wrapper<K> wrap(K member);
+  public Stream<K> streamKeys() {
+    return streamWrappedKeys().map(Wrapper::get);
+  }
+
+  public Stream<Wrapper<K>> streamWrappedKeys() {
+    return backing.keySet().stream();
+  }
+
+  public <TB, TC> TraversalContinuation<TB, TC> traverse(
+      BiFunction<K, V, TraversalContinuation<TB, TC>> fn) {
+    for (Entry<Wrapper<K>, V> entry : backing.entrySet()) {
+      TraversalContinuation<TB, TC> traversalContinuation =
+          fn.apply(entry.getKey().get(), entry.getValue());
+      if (traversalContinuation.shouldBreak()) {
+        return traversalContinuation;
+      }
+    }
+    return TraversalContinuation.doContinue();
+  }
+
+  protected abstract Wrapper<K> wrap(K member);
 }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodMap.java
new file mode 100644
index 0000000..0728fc0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodMap.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2023, 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.collections;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.utils.DexClassAndMethodEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+public class DexClassAndMethodMap<V> extends DexClassAndMemberMap<DexClassAndMethod, V> {
+
+  private static final DexClassAndMethodMap<?> EMPTY = new DexClassAndMethodMap<>(ImmutableMap::of);
+
+  private DexClassAndMethodMap(Supplier<Map<Wrapper<DexClassAndMethod>, V>> backingFactory) {
+    super(backingFactory);
+  }
+
+  protected DexClassAndMethodMap(Map<Wrapper<DexClassAndMethod>, V> backing) {
+    super(backing);
+  }
+
+  public static <V> DexClassAndMethodMap<V> create() {
+    return new DexClassAndMethodMap<>(HashMap::new);
+  }
+
+  public static <V> DexClassAndMethodMap<V> create(int capacity) {
+    return new DexClassAndMethodMap<>(new HashMap<>(capacity));
+  }
+
+  public static <V> DexClassAndMethodMap<V> createConcurrent() {
+    return new DexClassAndMethodMap<>(ConcurrentHashMap::new);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <V> DexClassAndMethodMap<V> empty() {
+    return (DexClassAndMethodMap<V>) EMPTY;
+  }
+
+  @Override
+  protected Wrapper<DexClassAndMethod> wrap(DexClassAndMethod method) {
+    return DexClassAndMethodEquivalence.get().wrap(method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
index 007aa51..9b77365 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
@@ -42,7 +42,6 @@
   @Override
   public boolean add(T method) {
     T existing = backing.put(method.getReference(), method);
-    assert existing == null || existing.isStructurallyEqualTo(method);
     return existing == null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
index b5c3c26..5a0a814 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
@@ -43,7 +43,7 @@
   }
 
   @Override
-  Wrapper<ProgramMethod> wrap(ProgramMethod method) {
+  protected Wrapper<ProgramMethod> wrap(ProgramMethod method) {
     return ProgramMethodEquivalence.get().wrap(method);
   }
 }