Allow many-to-many methods mappings in graph lens

Change-Id: I1dd2a8796d28169a323a96c5ffd726a1cda6b160
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 398ed87..5a10844 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
@@ -229,7 +231,8 @@
     protected final Map<DexField, DexField> fieldMap = new IdentityHashMap<>();
 
     protected final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
-    protected final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+    protected final BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures =
+        new BidirectionalOneToOneHashMap<>();
 
     public void map(DexType from, DexType to) {
       if (from == to) {
@@ -965,7 +968,8 @@
     // Maps that store the original signature of fields and methods that have been affected, for
     // example, by vertical class merging. Needed to generate a correct Proguard map in the end.
     protected final BiMap<DexField, DexField> originalFieldSignatures;
-    protected BiMap<DexMethod, DexMethod> originalMethodSignatures;
+    protected BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
+        originalMethodSignatures;
 
     // Overrides this if the sub type needs to be a nested lens while it doesn't have any mappings
     // at all, e.g., publicizer lens that changes invocation type only.
@@ -978,7 +982,7 @@
         Map<DexMethod, DexMethod> methodMap,
         Map<DexField, DexField> fieldMap,
         BiMap<DexField, DexField> originalFieldSignatures,
-        BiMap<DexMethod, DexMethod> originalMethodSignatures,
+        BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures,
         GraphLens previousLens,
         DexItemFactory dexItemFactory) {
       super(dexItemFactory, previousLens);
@@ -1138,15 +1142,11 @@
 
     @Override
     protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
-      return originalMethodSignatures != null
-          ? originalMethodSignatures.getOrDefault(method, method)
-          : method;
+      return originalMethodSignatures.getRepresentativeValueOrDefault(method, method);
     }
 
     protected DexMethod internalGetNextMethodSignature(DexMethod method) {
-      return originalMethodSignatures != null
-          ? originalMethodSignatures.inverse().getOrDefault(method, method)
-          : method;
+      return originalMethodSignatures.getRepresentativeKeyOrDefault(method, method);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index cc0528f..d348b61 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.google.common.collect.BiMap;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -22,7 +23,7 @@
 import java.util.function.Function;
 
 public class HorizontalClassMergerGraphLens extends NestedGraphLens {
-  private final AppView<?> appView;
+
   private final Map<DexMethod, List<ExtraParameter>> methodExtraParameters;
   private final Map<DexMethod, DexMethod> extraOriginalMethodSignatures;
   private final HorizontallyMergedClasses mergedClasses;
@@ -35,7 +36,7 @@
       Map<DexField, DexField> fieldMap,
       Map<DexMethod, DexMethod> methodMap,
       BiMap<DexField, DexField> originalFieldSignatures,
-      BiMap<DexMethod, DexMethod> originalMethodSignatures,
+      BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures,
       Map<DexMethod, DexMethod> extraOriginalMethodSignatures,
       Map<DexField, DexField> extraOriginalFieldSignatures,
       GraphLens previousLens) {
@@ -47,7 +48,6 @@
         originalMethodSignatures,
         previousLens,
         appView.dexItemFactory());
-    this.appView = appView;
     this.methodExtraParameters = methodExtraParameters;
     this.extraOriginalFieldSignatures = extraOriginalFieldSignatures;
     this.extraOriginalMethodSignatures = extraOriginalMethodSignatures;
@@ -127,7 +127,7 @@
           methodExtraParameters,
           fieldMap.getForwardMap(),
           methodMap.getForwardMap(),
-          inverseFieldMap.getBiMap(),
+          inverseFieldMap.getBiMap().getForwardBacking(),
           inverseMethodMap.getBiMap(),
           inverseMethodMap.getExtraMap(),
           inverseFieldMap.getExtraMap(),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneInverseMap.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneInverseMap.java
index 8da9b0f..973162f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneInverseMap.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneInverseMap.java
@@ -4,20 +4,20 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
-import com.google.common.collect.BiMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import java.util.Map;
 
 /** The inverse of a {@link ManyToOneMap} used for generating graph lens maps. */
 public class ManyToOneInverseMap<K, V> {
-  private final BiMap<V, K> biMap;
+  private final BidirectionalOneToOneHashMap<V, K> biMap;
   private final Map<V, K> extraMap;
 
-  ManyToOneInverseMap(BiMap<V, K> biMap, Map<V, K> extraMap) {
+  ManyToOneInverseMap(BidirectionalOneToOneHashMap<V, K> biMap, Map<V, K> extraMap) {
     this.biMap = biMap;
     this.extraMap = extraMap;
   }
 
-  public BiMap<V, K> getBiMap() {
+  public BidirectionalOneToOneHashMap<V, K> getBiMap() {
     return biMap;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneMap.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneMap.java
index 675d24c..5204021 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneMap.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ManyToOneMap.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import java.util.HashMap;
@@ -49,7 +50,7 @@
   }
 
   public ManyToOneInverseMap<K, V> inverse(Function<Set<K>, K> pickRepresentative) {
-    BiMap<V, K> biMap = HashBiMap.create();
+    BidirectionalOneToOneHashMap<V, K> biMap = new BidirectionalOneToOneHashMap<>();
     Map<V, K> extraMap = new HashMap<>();
     for (Entry<V, Set<K>> entry : inverseMap.entrySet()) {
       K representative = representativeMap.get(entry.getKey());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java
index 995f693..ed8077a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java
@@ -53,7 +53,8 @@
       return null;
     }
     // Map default methods to their companion methods.
-    DexMethod mappedMethod = graphLens.getExtraOriginalMethodSignatures().inverse().get(method);
+    DexMethod mappedMethod =
+        graphLens.getExtraOriginalMethodSignatures().getRepresentativeKey(method);
     if (mappedMethod != null) {
       return mappedMethod;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index f9086e0..88e1200 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -48,8 +48,9 @@
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -514,15 +515,16 @@
   // are to static companion methods.
   public static class InterfaceProcessorNestedGraphLens extends NestedGraphLens {
 
-    private BiMap<DexMethod, DexMethod> extraOriginalMethodSignatures;
+    private BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
+        extraOriginalMethodSignatures;
 
     public InterfaceProcessorNestedGraphLens(
         Map<DexType, DexType> typeMap,
         Map<DexMethod, DexMethod> methodMap,
         Map<DexField, DexField> fieldMap,
         BiMap<DexField, DexField> originalFieldSignatures,
-        BiMap<DexMethod, DexMethod> originalMethodSignatures,
-        BiMap<DexMethod, DexMethod> extraOriginalMethodSignatures,
+        BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures,
+        BidirectionalOneToOneHashMap<DexMethod, DexMethod> extraOriginalMethodSignatures,
         GraphLens previousLens,
         DexItemFactory dexItemFactory) {
       super(
@@ -551,12 +553,13 @@
     }
 
     public void toggleMappingToExtraMethods() {
-      BiMap<DexMethod, DexMethod> tmp = originalMethodSignatures;
+      BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> tmp = originalMethodSignatures;
       this.originalMethodSignatures = extraOriginalMethodSignatures;
       this.extraOriginalMethodSignatures = tmp;
     }
 
-    public BiMap<DexMethod, DexMethod> getExtraOriginalMethodSignatures() {
+    public BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
+        getExtraOriginalMethodSignatures() {
       return extraOriginalMethodSignatures;
     }
 
@@ -577,16 +580,14 @@
 
     @Override
     protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
-      return extraOriginalMethodSignatures.getOrDefault(
-          method, originalMethodSignatures.getOrDefault(method, method));
+      return extraOriginalMethodSignatures.getRepresentativeValueOrDefault(
+          method, originalMethodSignatures.getRepresentativeValueOrDefault(method, method));
     }
 
     @Override
     protected DexMethod internalGetNextMethodSignature(DexMethod method) {
-      return originalMethodSignatures
-          .inverse()
-          .getOrDefault(
-              method, extraOriginalMethodSignatures.inverse().getOrDefault(method, method));
+      return originalMethodSignatures.getRepresentativeKeyOrDefault(
+          method, extraOriginalMethodSignatures.getRepresentativeKeyOrDefault(method, method));
     }
 
     @Override
@@ -600,7 +601,8 @@
 
     public static class Builder extends NestedGraphLens.Builder {
 
-      private final BiMap<DexMethod, DexMethod> extraOriginalMethodSignatures = HashBiMap.create();
+      private final BidirectionalOneToOneHashMap<DexMethod, DexMethod>
+          extraOriginalMethodSignatures = new BidirectionalOneToOneHashMap<>();
 
       public void recordCodeMovedToCompanionClass(DexMethod from, DexMethod to) {
         assert from != to;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 9688ae8..e4d6f65 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -42,6 +42,7 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.BiMap;
@@ -440,7 +441,7 @@
         Map<DexMethod, DexMethod> methodMap,
         Map<DexField, DexField> fieldMap,
         BiMap<DexField, DexField> originalFieldSignatures,
-        BiMap<DexMethod, DexMethod> originalMethodSignatures,
+        BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures,
         GraphLens previousLens,
         DexItemFactory dexItemFactory) {
       super(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
index 7f99055..8889ec2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
 import com.google.common.collect.ImmutableMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -35,7 +36,7 @@
         methodMap,
         ImmutableMap.of(),
         null,
-        null,
+        BidirectionalManyToManyRepresentativeMap.empty(),
         previousLens,
         appView.dexItemFactory());
     // No concurrent maps here, we do not want synchronization overhead.
@@ -110,7 +111,6 @@
   @Override
   public MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context) {
-    assert originalMethodSignatures == null;
     DexMethod bridge = methodMap.get(previous.getReference());
     if (bridge == null) {
       return previous;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c11c8bf..48002cc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -28,9 +28,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -54,7 +54,7 @@
     private final Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod;
 
     UninstantiatedTypeOptimizationGraphLens(
-        BiMap<DexMethod, DexMethod> methodMap,
+        BidirectionalOneToOneHashMap<DexMethod, DexMethod> methodMap,
         Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod,
         AppView<?> appView) {
       super(
@@ -62,7 +62,7 @@
           methodMap,
           ImmutableMap.of(),
           null,
-          methodMap.inverse(),
+          methodMap.getInverseOneToOneMap(),
           appView.graphLens(),
           appView.dexItemFactory());
       this.appView = appView;
@@ -129,7 +129,8 @@
     }
 
     Map<Wrapper<DexMethod>, Set<DexType>> changedVirtualMethods = new HashMap<>();
-    BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
+    BidirectionalOneToOneHashMap<DexMethod, DexMethod> methodMapping =
+        new BidirectionalOneToOneHashMap<>();
     Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod = new IdentityHashMap<>();
 
     TopDownClassHierarchyTraversal.forProgramClasses(appView)
@@ -139,7 +140,7 @@
                 processClass(
                     clazz,
                     changedVirtualMethods,
-                    methodMapping,
+                    methodMapping.getForwardBacking(),
                     methodPoolCollection,
                     removedArgumentsInfoPerMethod));
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 492685a..16fb558 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -26,9 +26,9 @@
 import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Streams;
@@ -48,7 +48,8 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final MethodPoolCollection methodPoolCollection;
 
-  private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
+  private final BidirectionalOneToOneHashMap<DexMethod, DexMethod> methodMapping =
+      new BidirectionalOneToOneHashMap<>();
   private final Map<DexMethod, ArgumentInfoCollection> removedArguments = new IdentityHashMap<>();
 
   public static class UnusedArgumentsGraphLens extends NestedGraphLens {
@@ -60,7 +61,7 @@
         Map<DexMethod, DexMethod> methodMap,
         Map<DexField, DexField> fieldMap,
         BiMap<DexField, DexField> originalFieldSignatures,
-        BiMap<DexMethod, DexMethod> originalMethodSignatures,
+        BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures,
         GraphLens previousLens,
         DexItemFactory dexItemFactory,
         Map<DexMethod, ArgumentInfoCollection> removedArguments) {
@@ -111,7 +112,7 @@
           methodMapping,
           ImmutableMap.of(),
           ImmutableBiMap.of(),
-          methodMapping.inverse(),
+          methodMapping.getInverseOneToOneMap(),
           appView.graphLens(),
           appView.dexItemFactory(),
           removedArguments);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index d99bf3e..b6660fc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
@@ -30,7 +31,7 @@
       Map<DexMethod, DexMethod> methodMap,
       Map<DexField, DexField> fieldMap,
       BiMap<DexField, DexField> originalFieldSignatures,
-      BiMap<DexMethod, DexMethod> originalMethodSignatures,
+      BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures,
       GraphLens previousLens,
       DexItemFactory dexItemFactory,
       Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod,
@@ -76,7 +77,8 @@
 
     protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
     protected final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
-    protected final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+    protected final BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures =
+        new BidirectionalOneToOneHashMap<>();
 
     private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
         new IdentityHashMap<>();
@@ -148,7 +150,7 @@
       }
       return new EnumUnboxingLens(
           typeMap,
-          originalMethodSignatures.inverse(),
+          originalMethodSignatures.getInverseOneToOneMap(),
           originalFieldSignatures.inverse(),
           originalFieldSignatures,
           originalMethodSignatures,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
index 6eeeb46..fbc4c12 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableMap;
 
@@ -17,13 +18,13 @@
   ClassStaticizerGraphLens(
       AppView<?> appView,
       BiMap<DexField, DexField> fieldMapping,
-      BiMap<DexMethod, DexMethod> methodMapping) {
+      BidirectionalOneToOneHashMap<DexMethod, DexMethod> methodMapping) {
     super(
         ImmutableMap.of(),
         methodMapping,
         fieldMapping,
         fieldMapping.inverse(),
-        methodMapping.inverse(),
+        methodMapping.getInverseOneToOneMap(),
         appView.graphLens(),
         appView.dexItemFactory());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index f776d44..2313a5a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -45,6 +45,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
@@ -737,7 +738,8 @@
   }
 
   private ProgramMethodSet staticizeMethodSymbols() {
-    BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
+    BidirectionalOneToOneHashMap<DexMethod, DexMethod> methodMapping =
+        new BidirectionalOneToOneHashMap<>();
     BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
 
     ProgramMethodSet staticizedMethods = ProgramMethodSet.create();
@@ -801,7 +803,7 @@
       DexProgramClass candidateClass,
       DexType hostType,
       DexProgramClass hostClass,
-      BiMap<DexMethod, DexMethod> methodMapping,
+      BidirectionalOneToOneHashMap<DexMethod, DexMethod> methodMapping,
       BiMap<DexField, DexField> fieldMapping) {
     candidateToHostMapping.put(candidateClass.type, hostType);
 
@@ -856,7 +858,7 @@
         // has just been migrated to the host class.
         staticizedMethods.createAndAdd(hostClass, newMethod);
       }
-      DexMethod originalMethod = methodMapping.inverse().get(method.method);
+      DexMethod originalMethod = methodMapping.getRepresentativeKey(method.method);
       if (originalMethod == null) {
         methodMapping.put(method.method, newMethod.method);
       } else {
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
index 52e7b3d..bd8db04 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.util.Set;
@@ -25,7 +26,7 @@
         ImmutableMap.of(),
         ImmutableMap.of(),
         null,
-        null,
+        BidirectionalManyToManyRepresentativeMap.empty(),
         appView.graphLens(),
         appView.dexItemFactory());
     this.appView = appView;
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
index d3ceea5..b871193 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 
@@ -20,11 +21,11 @@
   private RepackagingLens(
       AppView<AppInfoWithLiveness> appView,
       BiMap<DexField, DexField> originalFieldSignatures,
-      BiMap<DexMethod, DexMethod> originalMethodSignatures,
+      BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures,
       BiMap<DexType, DexType> originalTypes) {
     super(
         originalTypes.inverse(),
-        originalMethodSignatures.inverse(),
+        originalMethodSignatures.getInverseBacking(),
         originalFieldSignatures.inverse(),
         originalFieldSignatures,
         originalMethodSignatures,
@@ -48,7 +49,8 @@
 
     protected final BiMap<DexType, DexType> originalTypes = HashBiMap.create();
     protected final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
-    protected final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+    protected final BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures =
+        new BidirectionalOneToOneHashMap<>();
 
     public void recordMove(DexField from, DexField to) {
       originalFieldSignatures.put(to, from);
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 97e980c..58e85ff 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.SingletonEquivalence;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.BiMap;
@@ -199,7 +200,8 @@
   private final Map<MergeKey, Representative> representatives = new HashMap<>();
 
   private final BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
-  private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
+  private final BidirectionalOneToOneHashMap<DexMethod, DexMethod> methodMapping =
+      new BidirectionalOneToOneHashMap<>();
 
   private int numberOfMergedClasses = 0;
 
@@ -234,7 +236,8 @@
   private NestedGraphLens buildGraphLens() {
     if (!fieldMapping.isEmpty() || !methodMapping.isEmpty()) {
       BiMap<DexField, DexField> originalFieldSignatures = fieldMapping.inverse();
-      BiMap<DexMethod, DexMethod> originalMethodSignatures = methodMapping.inverse();
+      BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures =
+          methodMapping.getInverseOneToOneMap();
       return new NestedGraphLens(
           ImmutableMap.of(),
           methodMapping,
@@ -497,7 +500,7 @@
       newMethods.add(sourceMethodAfterMove);
 
       DexMethod originalMethod =
-          methodMapping.inverse().getOrDefault(sourceMethod.method, sourceMethod.method);
+          methodMapping.getRepresentativeKeyOrDefault(sourceMethod.method, sourceMethod.method);
       methodMapping.forcePut(originalMethod, sourceMethodAfterMove.method);
 
       existingMethods.add(equivalence.wrap(sourceMethodAfterMove.method));
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
index 2bee133..f634f48 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableSet;
@@ -74,7 +75,7 @@
       Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
           contextualVirtualToDirectMethodMaps,
       BiMap<DexField, DexField> originalFieldSignatures,
-      BiMap<DexMethod, DexMethod> originalMethodSignatures,
+      BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures,
       Map<DexMethod, DexMethod> originalMethodSignaturesForBridges,
       GraphLens previousLens) {
     super(
@@ -171,7 +172,8 @@
     private final Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
         contextualVirtualToDirectMethodMaps = new IdentityHashMap<>();
 
-    private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
+    private final BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures =
+        new BidirectionalOneToOneHashMap<>();
     private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
         new IdentityHashMap<>();
 
@@ -215,11 +217,12 @@
               context);
         }
       }
-      for (Map.Entry<DexMethod, DexMethod> entry : builder.originalMethodSignatures.entrySet()) {
-        newBuilder.recordMove(
-            entry.getValue(),
-            builder.getMethodSignatureAfterClassMerging(entry.getKey(), mergedClasses));
-      }
+      builder.originalMethodSignatures.forEach(
+          (renamedMethodSignature, originalMethodSignature) ->
+              newBuilder.recordMove(
+                  originalMethodSignature,
+                  builder.getMethodSignatureAfterClassMerging(
+                      renamedMethodSignature, mergedClasses)));
       for (Map.Entry<DexMethod, DexMethod> entry :
           builder.originalMethodSignaturesForBridges.entrySet()) {
         newBuilder.recordCreationOfBridgeMethod(
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index fcf2f42..aee34f6 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -88,6 +88,10 @@
     return Iterables.concat(Iterables.transform(iterable, val -> map.apply(val)));
   }
 
+  public static <T> Iterable<T> empty() {
+    return Collections.emptyList();
+  }
+
   public static <T> Iterable<T> emptyIf(Iterable<T> iterable, boolean condition) {
     if (condition) {
       return Collections.emptySet();
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyRepresentativeMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyRepresentativeMap.java
new file mode 100644
index 0000000..a4878dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyRepresentativeMap.java
@@ -0,0 +1,94 @@
+// 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.collections;
+
+import java.util.Map;
+
+public abstract class BidirectionalManyToManyRepresentativeMap<K, V> {
+
+  public static <K, V> BidirectionalManyToManyRepresentativeMap<K, V> empty() {
+    return new EmptyBidirectionalManyToManyRepresentativeMap<>();
+  }
+
+  public abstract boolean containsKey(K key);
+
+  public abstract boolean containsValue(V value);
+
+  public abstract Map<K, V> getForwardBacking();
+
+  public abstract Map<V, K> getInverseBacking();
+
+  public final Inverse getInverseManyToManyMap() {
+    return new Inverse();
+  }
+
+  public abstract K getRepresentativeKey(V value);
+
+  public final K getRepresentativeKeyOrDefault(V value, K defaultValue) {
+    K representativeKey = getRepresentativeKey(value);
+    return representativeKey != null ? representativeKey : defaultValue;
+  }
+
+  public abstract V getRepresentativeValue(K key);
+
+  public final V getRepresentativeValueOrDefault(K key, V defaultValue) {
+    V representativeValue = getRepresentativeValue(key);
+    return representativeValue != null ? representativeValue : defaultValue;
+  }
+
+  public abstract Iterable<K> getKeys(V value);
+
+  public abstract Iterable<V> getValues(K key);
+
+  public abstract boolean isEmpty();
+
+  public class Inverse extends BidirectionalManyToManyRepresentativeMap<V, K> {
+
+    @Override
+    public boolean containsKey(V key) {
+      return BidirectionalManyToManyRepresentativeMap.this.containsValue(key);
+    }
+
+    @Override
+    public boolean containsValue(K value) {
+      return BidirectionalManyToManyRepresentativeMap.this.containsKey(value);
+    }
+
+    @Override
+    public Map<V, K> getForwardBacking() {
+      return BidirectionalManyToManyRepresentativeMap.this.getInverseBacking();
+    }
+
+    @Override
+    public Map<K, V> getInverseBacking() {
+      return BidirectionalManyToManyRepresentativeMap.this.getForwardBacking();
+    }
+
+    @Override
+    public V getRepresentativeKey(K value) {
+      return BidirectionalManyToManyRepresentativeMap.this.getRepresentativeValue(value);
+    }
+
+    @Override
+    public K getRepresentativeValue(V key) {
+      return BidirectionalManyToManyRepresentativeMap.this.getRepresentativeKey(key);
+    }
+
+    @Override
+    public Iterable<V> getKeys(K value) {
+      return BidirectionalManyToManyRepresentativeMap.this.getValues(value);
+    }
+
+    @Override
+    public Iterable<K> getValues(V key) {
+      return BidirectionalManyToManyRepresentativeMap.this.getKeys(key);
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return BidirectionalManyToManyRepresentativeMap.this.isEmpty();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
new file mode 100644
index 0000000..0f85170
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
@@ -0,0 +1,134 @@
+// 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.collections;
+
+import com.android.tools.r8.utils.IterableUtils;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+public class BidirectionalOneToOneHashMap<K, V>
+    extends BidirectionalManyToManyRepresentativeMap<K, V> implements Map<K, V> {
+
+  private final BiMap<K, V> backing;
+
+  public BidirectionalOneToOneHashMap() {
+    this(HashBiMap.create());
+  }
+
+  public BidirectionalOneToOneHashMap(BiMap<K, V> backing) {
+    this.backing = backing;
+  }
+
+  @Override
+  public void clear() {
+    backing.clear();
+  }
+
+  @Override
+  public boolean containsKey(Object key) {
+    return backing.containsKey(key);
+  }
+
+  @Override
+  public boolean containsValue(Object value) {
+    return backing.containsValue(value);
+  }
+
+  @Override
+  public Set<Entry<K, V>> entrySet() {
+    return backing.entrySet();
+  }
+
+  public V forcePut(K key, V value) {
+    return backing.forcePut(key, value);
+  }
+
+  @Override
+  public V get(Object key) {
+    return getRepresentativeValue((K) key);
+  }
+
+  @Override
+  public BiMap<K, V> getForwardBacking() {
+    return backing;
+  }
+
+  @Override
+  public BiMap<V, K> getInverseBacking() {
+    return backing.inverse();
+  }
+
+  public BidirectionalOneToOneHashMap<V, K> getInverseOneToOneMap() {
+    return new BidirectionalOneToOneHashMap<>(backing.inverse());
+  }
+
+  @Override
+  public K getRepresentativeKey(V value) {
+    return backing.inverse().get(value);
+  }
+
+  @Override
+  public V getRepresentativeValue(K key) {
+    return backing.get(key);
+  }
+
+  @Override
+  public Iterable<K> getKeys(V value) {
+    if (containsValue(value)) {
+      return IterableUtils.singleton(getRepresentativeKey(value));
+    }
+    return IterableUtils.empty();
+  }
+
+  @Override
+  public Iterable<V> getValues(K key) {
+    if (containsKey(key)) {
+      return IterableUtils.singleton(getRepresentativeValue(key));
+    }
+    return IterableUtils.empty();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return backing.isEmpty();
+  }
+
+  @Override
+  public Set<K> keySet() {
+    return backing.keySet();
+  }
+
+  @Override
+  public V put(K key, V value) {
+    return backing.put(key, value);
+  }
+
+  public void putAll(BidirectionalOneToOneHashMap<K, V> map) {
+    putAll(map.backing);
+  }
+
+  @Override
+  public void putAll(Map<? extends K, ? extends V> map) {
+    backing.putAll(map);
+  }
+
+  @Override
+  public V remove(Object key) {
+    return backing.remove(key);
+  }
+
+  @Override
+  public int size() {
+    return backing.size();
+  }
+
+  @Override
+  public Collection<V> values() {
+    return backing.values();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalManyToManyRepresentativeMap.java b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalManyToManyRepresentativeMap.java
new file mode 100644
index 0000000..dbcb753
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalManyToManyRepresentativeMap.java
@@ -0,0 +1,58 @@
+// 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.collections;
+
+import com.android.tools.r8.utils.IterableUtils;
+import java.util.Collections;
+import java.util.Map;
+
+public class EmptyBidirectionalManyToManyRepresentativeMap<K, V>
+    extends BidirectionalManyToManyRepresentativeMap<K, V> {
+
+  @Override
+  public boolean containsKey(K key) {
+    return false;
+  }
+
+  @Override
+  public boolean containsValue(V value) {
+    return false;
+  }
+
+  @Override
+  public Map<K, V> getForwardBacking() {
+    return Collections.emptyMap();
+  }
+
+  @Override
+  public Map<V, K> getInverseBacking() {
+    return Collections.emptyMap();
+  }
+
+  @Override
+  public K getRepresentativeKey(V value) {
+    return null;
+  }
+
+  @Override
+  public V getRepresentativeValue(K key) {
+    return null;
+  }
+
+  @Override
+  public Iterable<K> getKeys(V value) {
+    return IterableUtils.empty();
+  }
+
+  @Override
+  public Iterable<V> getValues(K key) {
+    return IterableUtils.empty();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return true;
+  }
+}