Remove collision detection from vertical class merging

Change-Id: Id844c246af9417cd2ccfa90bee40f31b41cffd94
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6923215..12a84ab 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -34,7 +34,6 @@
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
-import com.android.tools.r8.graph.lens.AppliedGraphLens;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -483,7 +482,9 @@
       // should therefore be run after the publicizer.
       new NestReducer(appViewWithLiveness).run(executorService, timing);
 
-      new MemberRebindingAnalysis(appViewWithLiveness).run(executorService);
+      appView.setGraphLens(MemberRebindingIdentityLensFactory.create(appView, executorService));
+
+      new MemberRebindingAnalysis(appViewWithLiveness).run();
       appViewWithLiveness.appInfo().notifyMemberRebindingFinished(appViewWithLiveness);
 
       assert ArtProfileCompletenessChecker.verify(appView);
@@ -540,9 +541,7 @@
       // At this point all code has been mapped according to the graph lens. We cannot remove the
       // graph lens entirely, though, since it is needed for mapping all field and method signatures
       // back to the original program.
-      timing.time(
-          "AppliedGraphLens construction",
-          () -> appView.setGraphLens(new AppliedGraphLens(appView)));
+      timing.time("AppliedGraphLens construction", appView::flattenGraphLenses);
       timing.end();
 
       RuntimeTypeCheckInfo.Builder finalRuntimeTypeCheckInfoBuilder = null;
diff --git a/src/main/java/com/android/tools/r8/classmerging/ClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/classmerging/ClassMergerGraphLens.java
index 724079b..300d399 100644
--- a/src/main/java/com/android/tools/r8/classmerging/ClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/classmerging/ClassMergerGraphLens.java
@@ -31,7 +31,7 @@
       GL extends ClassMergerGraphLens, MC extends MergedClasses> {
 
     public abstract void addExtraParameters(
-        DexMethod methodSignature, List<? extends ExtraParameter> extraParameters);
+        DexMethod from, DexMethod to, List<? extends ExtraParameter> extraParameters);
 
     public abstract void commitPendingUpdates();
 
diff --git a/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
index 08933ed..5e0445e 100644
--- a/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.graph.classmerging.MergedClasses;
 import com.android.tools.r8.graph.fixup.TreeFixerBase;
 import com.android.tools.r8.horizontalclassmerging.SubtypingForrestForClasses;
-import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AnnotationFixer;
@@ -27,13 +26,14 @@
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.OptionalBool;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
+import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.IdentityHashMap;
-import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -52,8 +52,8 @@
   private final SyntheticArgumentClass syntheticArgumentClass;
 
   private final Map<DexProgramClass, DexType> originalSuperTypes = new IdentityHashMap<>();
-  private final BiMap<DexMethodSignature, DexMethodSignature> reservedInterfaceSignatures =
-      HashBiMap.create();
+  private final DexMethodSignatureBiMap<DexMethodSignature> reservedInterfaceSignatures =
+      new DexMethodSignatureBiMap<>();
 
   public ClassMergerTreeFixer(
       AppView<?> appView,
@@ -68,10 +68,11 @@
     this.syntheticArgumentClass = syntheticArgumentClass;
   }
 
-  public GL run(ExecutorService executorService) throws ExecutionException {
+  public GL run(ExecutorService executorService, Timing timing) throws ExecutionException {
     if (!appView.enableWholeProgramOptimizations()) {
-      return lensBuilder.build(appView, mergedClasses);
+      return timing.time("Fixup", () -> lensBuilder.build(appView, mergedClasses));
     }
+    timing.begin("Fixup");
     AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
     Collection<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
     Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
@@ -81,15 +82,22 @@
         new SubtypingForrestForClasses(appView.withClassHierarchy());
     // TODO(b/170078037): parallelize this code segment.
     for (DexProgramClass root : subtypingForrest.getProgramRoots()) {
-      subtypingForrest.traverseNodeDepthFirst(root, HashBiMap.create(), this::fixupProgramClass);
+      subtypingForrest.traverseNodeDepthFirst(
+          root, new DexMethodSignatureBiMap<>(), this::fixupProgramClass);
     }
+    postprocess();
     GL lens = lensBuilder.build(appViewWithLiveness, mergedClasses);
     new AnnotationFixer(appView, lens).run(appView.appInfo().classes(), executorService);
+    timing.end();
     return lens;
   }
 
   public abstract boolean isRunningBeforePrimaryOptimizationPass();
 
+  public void postprocess() {
+    // Intentionally empty.
+  }
+
   public void fixupAttributes(DexProgramClass clazz) {
     if (clazz.hasEnclosingMethodAttribute()) {
       EnclosingMethodAttribute enclosingMethodAttribute = clazz.getEnclosingMethodAttribute();
@@ -115,24 +123,25 @@
     clazz.setInterfaces(fixupInterfaces(clazz, clazz.getInterfaces()));
   }
 
-  private BiMap<DexMethodSignature, DexMethodSignature> fixupProgramClass(
-      DexProgramClass clazz, BiMap<DexMethodSignature, DexMethodSignature> remappedVirtualMethods) {
+  private DexMethodSignatureBiMap<DexMethodSignature> fixupProgramClass(
+      DexProgramClass clazz, DexMethodSignatureBiMap<DexMethodSignature> remappedVirtualMethods) {
     assert !clazz.isInterface();
 
-    // TODO(b/169395592): ensure merged classes have been removed using:
-    //   assert !mergedClasses.hasBeenMergedIntoDifferentType(clazz.type);
-
-    BiMap<DexMethodSignature, DexMethodSignature> remappedClassVirtualMethods =
-        HashBiMap.create(remappedVirtualMethods);
-
-    Set<DexMethodSignature> newMethodReferences = Sets.newHashSet();
+    MutableBidirectionalOneToOneMap<DexEncodedMethod, DexMethodSignature> newMethodSignatures =
+        createLocallyReservedMethodSignatures(clazz, remappedVirtualMethods);
+    DexMethodSignatureBiMap<DexMethodSignature> remappedClassVirtualMethods =
+        new DexMethodSignatureBiMap<>(remappedVirtualMethods);
     clazz
         .getMethodCollection()
         .replaceAllVirtualMethods(
-            method -> fixupVirtualMethod(remappedClassVirtualMethods, newMethodReferences, method));
+            method ->
+                fixupVirtualMethod(
+                    clazz, method, remappedClassVirtualMethods, newMethodSignatures));
     clazz
         .getMethodCollection()
-        .replaceAllDirectMethods(method -> fixupDirectMethod(newMethodReferences, clazz, method));
+        .replaceAllDirectMethods(
+            method ->
+                fixupDirectMethod(clazz, method, remappedClassVirtualMethods, newMethodSignatures));
 
     Set<DexField> newFieldReferences = Sets.newIdentityHashSet();
     DexEncodedField[] instanceFields = clazz.clearInstanceFields();
@@ -185,10 +194,15 @@
   }
 
   private void fixupInterfaceClass(DexProgramClass iface) {
-    Set<DexMethodSignature> newDirectMethods = new LinkedHashSet<>();
+    DexMethodSignatureBiMap<DexMethodSignature> remappedVirtualMethods =
+        DexMethodSignatureBiMap.empty();
+    MutableBidirectionalOneToOneMap<DexEncodedMethod, DexMethodSignature> newMethodSignatures =
+        new BidirectionalOneToOneHashMap<>();
     iface
         .getMethodCollection()
-        .replaceDirectMethods(method -> fixupDirectMethod(newDirectMethods, iface, method));
+        .replaceDirectMethods(
+            method ->
+                fixupDirectMethod(iface, method, remappedVirtualMethods, newMethodSignatures));
     iface.getMethodCollection().replaceVirtualMethods(this::fixupVirtualInterfaceMethod);
 
     assert !iface.hasInstanceFields();
@@ -211,7 +225,18 @@
   }
 
   private DexEncodedMethod fixupProgramMethod(
-      DexMethod newMethodReference, DexEncodedMethod method) {
+      DexProgramClass clazz, DexEncodedMethod method, DexMethod newMethodReference) {
+    // Convert out of DefaultInstanceInitializerCode, since this piece of code will require lens
+    // code rewriting.
+    if (isRunningBeforePrimaryOptimizationPass()
+        && method.hasCode()
+        && method.getCode().isDefaultInstanceInitializerCode()
+        && mergedClasses.isMergeSourceOrTarget(clazz.getSuperType())) {
+      DexType originalSuperType = originalSuperTypes.getOrDefault(clazz, clazz.getSuperType());
+      DefaultInstanceInitializerCode.uncanonicalizeCode(
+          appView, method.asProgramMethod(clazz), originalSuperType);
+    }
+
     DexMethod originalMethodReference = method.getReference();
     if (newMethodReference.isIdenticalTo(originalMethodReference)) {
       return method;
@@ -232,85 +257,109 @@
   }
 
   private DexEncodedMethod fixupDirectMethod(
-      Set<DexMethodSignature> newMethods, DexProgramClass clazz, DexEncodedMethod method) {
+      DexProgramClass clazz,
+      DexEncodedMethod method,
+      DexMethodSignatureBiMap<DexMethodSignature> remappedVirtualMethods,
+      MutableBidirectionalOneToOneMap<DexEncodedMethod, DexMethodSignature> newMethodSignatures) {
     DexMethod originalMethodReference = method.getReference();
 
     // Fix all type references in the method prototype.
-    DexMethod newMethodReference = fixupMethodReference(originalMethodReference);
+    DexMethodSignature reservedMethodSignature = newMethodSignatures.get(method);
+    DexMethod newMethodReference;
+    if (reservedMethodSignature != null) {
+      newMethodReference = reservedMethodSignature.withHolder(clazz, dexItemFactory);
+    } else {
+      newMethodReference = fixupMethodReference(originalMethodReference);
+      if (newMethodSignatures.containsValue(newMethodReference.getSignature())) {
+        // If the method collides with a direct method on the same class then rename it to a
+        // globally
+        // fresh name and record the signature.
+        if (method.isInstanceInitializer()) {
+          // If the method is an instance initializer, then add extra nulls.
+          Box<Set<DexType>> usedSyntheticArgumentClasses = new Box<>();
+          newMethodReference =
+              dexItemFactory.createInstanceInitializerWithFreshProto(
+                  newMethodReference,
+                  syntheticArgumentClass.getArgumentClasses(),
+                  tryMethod -> !newMethodSignatures.containsValue(tryMethod.getSignature()),
+                  usedSyntheticArgumentClasses::set);
+          lensBuilder.addExtraParameters(
+              originalMethodReference,
+              newMethodReference,
+              ExtraUnusedNullParameter.computeExtraUnusedNullParameters(
+                  originalMethodReference, newMethodReference));
 
-    if (newMethods.contains(newMethodReference.getSignature())) {
-      // If the method collides with a direct method on the same class then rename it to a globally
-      // fresh name and record the signature.
-
-      if (method.isInstanceInitializer()) {
-        // If the method is an instance initializer, then add extra nulls.
-        Box<Set<DexType>> usedSyntheticArgumentClasses = new Box<>();
-        newMethodReference =
-            dexItemFactory.createInstanceInitializerWithFreshProto(
-                newMethodReference,
-                syntheticArgumentClass.getArgumentClasses(),
-                tryMethod -> !newMethods.contains(tryMethod.getSignature()),
-                usedSyntheticArgumentClasses::set);
-        lensBuilder.addExtraParameters(
-            originalMethodReference,
-            ExtraUnusedNullParameter.computeExtraUnusedNullParameters(
-                originalMethodReference, newMethodReference));
-
-        // Amend the art profile collection.
-        if (usedSyntheticArgumentClasses.isSet()) {
-          Set<DexMethod> previousMethodReferences =
-              lensBuilder.getOriginalMethodReferences(originalMethodReference);
-          if (previousMethodReferences.isEmpty()) {
-            profileCollectionAdditions.applyIfContextIsInProfile(
-                originalMethodReference,
-                additionsBuilder ->
-                    usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
-          } else {
-            for (DexMethod previousMethodReference : previousMethodReferences) {
+          // Amend the art profile collection.
+          if (usedSyntheticArgumentClasses.isSet()) {
+            Set<DexMethod> previousMethodReferences =
+                lensBuilder.getOriginalMethodReferences(originalMethodReference);
+            if (previousMethodReferences.isEmpty()) {
               profileCollectionAdditions.applyIfContextIsInProfile(
-                  previousMethodReference,
+                  originalMethodReference,
                   additionsBuilder ->
                       usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
+            } else {
+              for (DexMethod previousMethodReference : previousMethodReferences) {
+                profileCollectionAdditions.applyIfContextIsInProfile(
+                    previousMethodReference,
+                    additionsBuilder ->
+                        usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
+              }
             }
           }
+        } else {
+          newMethodReference =
+              dexItemFactory.createFreshMethodNameWithoutHolder(
+                  newMethodReference.getName().toSourceString(),
+                  newMethodReference.getProto(),
+                  newMethodReference.getHolderType(),
+                  tryMethod ->
+                      !reservedInterfaceSignatures.containsValue(tryMethod.getSignature())
+                          && !remappedVirtualMethods.containsValue(tryMethod.getSignature())
+                          && !newMethodSignatures.containsValue(tryMethod.getSignature()));
         }
-      } else {
-        newMethodReference =
-            dexItemFactory.createFreshMethodNameWithoutHolder(
-                newMethodReference.getName().toSourceString(),
-                newMethodReference.proto,
-                newMethodReference.holder,
-                tryMethod ->
-                    !reservedInterfaceSignatures.containsValue(tryMethod.getSignature())
-                        && !newMethods.contains(tryMethod.getSignature()));
+      }
+
+      assert !newMethodSignatures.containsValue(newMethodReference.getSignature());
+      newMethodSignatures.put(method, newMethodReference.getSignature());
+    }
+
+    return fixupProgramMethod(clazz, method, newMethodReference);
+  }
+
+  private MutableBidirectionalOneToOneMap<DexEncodedMethod, DexMethodSignature>
+      createLocallyReservedMethodSignatures(
+          DexProgramClass clazz,
+          DexMethodSignatureBiMap<DexMethodSignature> remappedVirtualMethods) {
+    MutableBidirectionalOneToOneMap<DexEncodedMethod, DexMethodSignature> newMethodSignatures =
+        new BidirectionalOneToOneHashMap<>();
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.belongsToVirtualPool()) {
+        DexMethodSignature reservedMethodSignature =
+            lookupReservedVirtualName(method, remappedVirtualMethods);
+        if (reservedMethodSignature != null) {
+          newMethodSignatures.put(method, reservedMethodSignature);
+          continue;
+        }
+      }
+      // Reserve the method signature if it is unchanged and not globally reserved.
+      DexMethodSignature newMethodSignature = fixupMethodSignature(method);
+      if (newMethodSignature.equals(method.getName(), method.getProto())
+          && !reservedInterfaceSignatures.containsValue(newMethodSignature)
+          && !remappedVirtualMethods.containsValue(newMethodSignature)) {
+        newMethodSignatures.put(method, newMethodSignature);
       }
     }
-
-    boolean changed = newMethods.add(newMethodReference.getSignature());
-    assert changed;
-
-    // Convert out of DefaultInstanceInitializerCode, since this piece of code will require lens
-    // code rewriting.
-    if (isRunningBeforePrimaryOptimizationPass()
-        && method.hasCode()
-        && method.getCode().isDefaultInstanceInitializerCode()
-        && mergedClasses.isMergeSourceOrTarget(clazz.getSuperType())) {
-      DexType originalSuperType = originalSuperTypes.getOrDefault(clazz, clazz.getSuperType());
-      DefaultInstanceInitializerCode.uncanonicalizeCode(
-          appView, method.asProgramMethod(clazz), originalSuperType);
-    }
-
-    return fixupProgramMethod(newMethodReference, method);
+    return newMethodSignatures;
   }
 
   private DexMethodSignature lookupReservedVirtualName(
-      DexMethod originalMethodReference,
-      BiMap<DexMethodSignature, DexMethodSignature> renamedClassVirtualMethods) {
-    DexMethodSignature originalSignature = originalMethodReference.getSignature();
+      DexEncodedMethod method,
+      DexMethodSignatureBiMap<DexMethodSignature> renamedClassVirtualMethods) {
+    DexMethodSignature originalSignature = method.getSignature();
 
     // Determine if the original method has been rewritten by a parent class
     DexMethodSignature renamedVirtualName = renamedClassVirtualMethods.get(originalSignature);
-
     if (renamedVirtualName == null) {
       // Determine if there is a signature mapping.
       DexMethodSignature mappedInterfaceSignature =
@@ -321,63 +370,40 @@
     } else {
       assert !reservedInterfaceSignatures.containsKey(originalSignature);
     }
-
     return renamedVirtualName;
   }
 
   private DexEncodedMethod fixupVirtualMethod(
-      BiMap<DexMethodSignature, DexMethodSignature> renamedClassVirtualMethods,
-      Set<DexMethodSignature> newMethods,
-      DexEncodedMethod method) {
-    DexMethod originalMethodReference = method.getReference();
-    DexMethodSignature originalSignature = originalMethodReference.getSignature();
-
-    DexMethodSignature renamedVirtualName =
-        lookupReservedVirtualName(originalMethodReference, renamedClassVirtualMethods);
-
-    // Fix all type references in the method prototype.
-    DexMethodSignature newSignature = fixupMethodSignature(method);
-
-    if (renamedVirtualName != null) {
-      // If the method was renamed in a parent, rename it in the child.
-      newSignature = renamedVirtualName;
-
-      assert !newMethods.contains(newSignature);
-    } else if (reservedInterfaceSignatures.containsValue(newSignature)
-        || newMethods.contains(newSignature)
-        || renamedClassVirtualMethods.containsValue(newSignature)) {
-      // If the method potentially collides with an interface method or with another virtual method
-      // rename it to a globally fresh name and record the name.
-
+      DexProgramClass clazz,
+      DexEncodedMethod method,
+      DexMethodSignatureBiMap<DexMethodSignature> renamedClassVirtualMethods,
+      MutableBidirectionalOneToOneMap<DexEncodedMethod, DexMethodSignature> newMethodSignatures) {
+    DexMethodSignature newSignature = newMethodSignatures.get(method);
+    if (newSignature == null) {
+      // Fix all type references in the method prototype.
       newSignature =
           dexItemFactory.createFreshMethodSignatureName(
-              originalMethodReference.getName().toSourceString(),
+              method.getName().toSourceString(),
               null,
-              newSignature.getProto(),
+              fixupProto(method.getProto()),
               trySignature ->
                   !reservedInterfaceSignatures.containsValue(trySignature)
-                      && !newMethods.contains(trySignature)
+                      && !newMethodSignatures.containsValue(trySignature)
                       && !renamedClassVirtualMethods.containsValue(trySignature));
-
-      // Record signature renaming so that subclasses perform the identical rename.
-      renamedClassVirtualMethods.put(originalSignature, newSignature);
-    } else {
-      // There was no reserved name and the new signature is available.
-
-      if (Iterables.any(
-          newSignature.getProto().getParameterBaseTypes(dexItemFactory),
-          mergedClasses::isMergeTarget)) {
-        // If any of the parameter types have been merged, record the signature mapping.
-        renamedClassVirtualMethods.put(originalSignature, newSignature);
-      }
+      newMethodSignatures.put(method, newSignature);
     }
 
-    boolean changed = newMethods.add(newSignature);
-    assert changed;
+    // If any of the parameter types have been merged, record the signature mapping so that
+    // subclasses perform the identical rename.
+    if (!reservedInterfaceSignatures.containsKey(method)
+        && Iterables.any(
+            newSignature.getProto().getParameterBaseTypes(dexItemFactory),
+            mergedClasses::isMergeTarget)) {
+      renamedClassVirtualMethods.put(method.getSignature(), newSignature);
+    }
 
-    DexMethod newMethodReference =
-        newSignature.withHolder(fixupType(originalMethodReference.holder), dexItemFactory);
-    return fixupProgramMethod(newMethodReference, method);
+    DexMethod newMethodReference = newSignature.withHolder(clazz, dexItemFactory);
+    return fixupProgramMethod(clazz, method, newMethodReference);
   }
 
   @SuppressWarnings("ReferenceEquality")
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/classmerging/SyntheticArgumentClass.java
similarity index 78%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
rename to src/main/java/com/android/tools/r8/classmerging/SyntheticArgumentClass.java
index 1074a5c..a8dcd49 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/classmerging/SyntheticArgumentClass.java
@@ -1,12 +1,13 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.horizontalclassmerging;
+package com.android.tools.r8.classmerging;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
 import com.google.common.base.Suppliers;
@@ -48,7 +49,7 @@
 
     private final AppView<AppInfoWithLiveness> appView;
 
-    Builder(AppView<AppInfoWithLiveness> appView) {
+    public Builder(AppView<AppInfoWithLiveness> appView) {
       this.appView = appView;
     }
 
@@ -60,22 +61,28 @@
     }
 
     public SyntheticArgumentClass build(Collection<MergeGroup> mergeGroups) {
-      DexProgramClass context = getDeterministicContext(mergeGroups);
+      return build(getDeterministicContext(mergeGroups));
+    }
+
+    public SyntheticArgumentClass build(DexProgramClass deterministicContext) {
       List<Supplier<DexType>> syntheticArgumentTypes = new ArrayList<>();
       syntheticArgumentTypes.add(
           Suppliers.memoize(
               () ->
-                  synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_1)
+                  synthesizeClass(
+                          deterministicContext, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_1)
                       .getType()));
       syntheticArgumentTypes.add(
           Suppliers.memoize(
               () ->
-                  synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_2)
+                  synthesizeClass(
+                          deterministicContext, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_2)
                       .getType()));
       syntheticArgumentTypes.add(
           Suppliers.memoize(
               () ->
-                  synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_3)
+                  synthesizeClass(
+                          deterministicContext, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_3)
                       .getType()));
       return new SyntheticArgumentClass(syntheticArgumentTypes);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 693d2ac..3e0bc95 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
 import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis.ResourceAnalysisResult;
 import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
+import com.android.tools.r8.graph.lens.AppliedGraphLens;
 import com.android.tools.r8.graph.lens.ClearCodeRewritingGraphLens;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.lens.InitClassLens;
@@ -38,6 +39,8 @@
 import com.android.tools.r8.ir.optimize.library.LibraryMethodSideEffectModelCollection;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.SeedMapper;
+import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
+import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.optimize.compose.ComposeReferences;
 import com.android.tools.r8.optimize.interfaces.collection.OpenClosedInterfacesCollection;
@@ -434,8 +437,18 @@
     allCodeProcessed = true;
   }
 
-  public void clearCodeRewritings() {
+  public void clearCodeRewritings(ExecutorService executorService) throws ExecutionException {
     setGraphLens(new ClearCodeRewritingGraphLens(withClassHierarchy()));
+
+    MemberRebindingIdentityLens memberRebindingIdentityLens =
+        MemberRebindingIdentityLensFactory.rebuild(withClassHierarchy(), executorService);
+    setGraphLens(memberRebindingIdentityLens);
+  }
+
+  public void flattenGraphLenses() {
+    GraphLens graphLens = graphLens();
+    setGraphLens(GraphLens.getIdentityLens());
+    setGraphLens(new AppliedGraphLens(withClassHierarchy(), graphLens));
   }
 
   public AppServices appServices() {
@@ -1246,19 +1259,13 @@
     if (!firstUnappliedLens.isMemberRebindingLens()
         && !firstUnappliedLens.isMemberRebindingIdentityLens()) {
       NonIdentityGraphLens appliedMemberRebindingLens =
-          firstUnappliedLens.findPrevious(
-              previous ->
-                  previous.isMemberRebindingLens() || previous.isMemberRebindingIdentityLens());
+          firstUnappliedLens.findPrevious(GraphLens::isMemberRebindingIdentityLens);
       if (appliedMemberRebindingLens != null) {
         newMemberRebindingLens =
-            appliedMemberRebindingLens.isMemberRebindingLens()
-                ? appliedMemberRebindingLens
-                    .asMemberRebindingLens()
-                    .toRewrittenFieldRebindingLens(appView, appliedLens, appliedMemberRebindingLens)
-                : appliedMemberRebindingLens
-                    .asMemberRebindingIdentityLens()
-                    .toRewrittenMemberRebindingIdentityLens(
-                        appView, appliedLens, appliedMemberRebindingLens);
+            appliedMemberRebindingLens
+                .asMemberRebindingIdentityLens()
+                .toRewrittenMemberRebindingIdentityLens(
+                    appView, appliedLens, appliedMemberRebindingLens);
       }
     }
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 77398b6..3b8ba07 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -854,13 +854,6 @@
       GraphLens graphLens,
       ProgramMethod context) {
     InliningConstraints inliningConstraints = new InliningConstraints(appView, graphLens);
-    if (appView.options().isInterfaceMethodDesugaringEnabled()) {
-      // TODO(b/120130831): Conservatively need to say "no" at this point if there are invocations
-      //  to static interface methods. This should be fixed by making sure that the desugared
-      //  versions of default and static interface methods are present in the application during
-      //  IR processing.
-      inliningConstraints.disallowStaticInterfaceMethodCalls();
-    }
     ConstraintWithTarget constraint = ConstraintWithTarget.ALWAYS;
     assert inliningConstraints.forMonitor().isAlways();
     for (CfInstruction insn : instructions) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index a484fdd..88e91d8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8.graph;
 
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Streams;
 import java.util.function.Function;
+import java.util.function.Predicate;
 
 public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
     extends DexReference implements NamingLensComparable<R> {
@@ -70,5 +72,11 @@
     return Iterables.transform(getReferencedTypes(), type -> type.toBaseType(dexItemFactory));
   }
 
+  public boolean verifyReferencedBaseTypesMatches(
+      Predicate<DexType> predicate, DexItemFactory dexItemFactory) {
+    assert Streams.stream(getReferencedBaseTypes(dexItemFactory)).allMatch(predicate);
+    return true;
+  }
+
   public abstract DexMember<D, R> withHolder(DexReference holder, DexItemFactory dexItemFactory);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java
index 866cc8a..2c0c5bc 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/AppliedGraphLens.java
@@ -45,22 +45,23 @@
   private final Map<DexMethod, DexMethod> extraOriginalMethodSignatures = new IdentityHashMap<>();
 
   @SuppressWarnings("ReferenceEquality")
-  public AppliedGraphLens(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    super(appView, GraphLens.getIdentityLens());
+  public AppliedGraphLens(
+      AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens graphLens) {
+    super(appView);
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       // TODO(b/169395592): If merged classes were removed from the application this would not be
       //  necessary.
-      if (appView.graphLens().lookupType(clazz.getType()) != clazz.getType()) {
+      if (graphLens.lookupType(clazz.getType()) != clazz.getType()) {
         continue;
       }
 
       // Record original type names.
-      recordOriginalTypeNames(clazz, appView);
+      recordOriginalTypeNames(clazz, graphLens);
 
       // Record original field signatures.
       for (DexEncodedField encodedField : clazz.fields()) {
         DexField field = encodedField.getReference();
-        DexField original = appView.graphLens().getOriginalFieldSignature(field);
+        DexField original = graphLens.getOriginalFieldSignature(field);
         if (original != field) {
           DexField existing = originalFieldSignatures.forcePut(field, original);
           assert existing == null;
@@ -70,12 +71,12 @@
       // Record original method signatures.
       for (DexEncodedMethod encodedMethod : clazz.methods()) {
         DexMethod method = encodedMethod.getReference();
-        DexMethod original = appView.graphLens().getOriginalMethodSignatureForMapping(method);
+        DexMethod original = graphLens.getOriginalMethodSignatureForMapping(method);
         DexMethod existing = originalMethodSignatures.inverse().get(original);
         if (existing == null) {
           originalMethodSignatures.put(method, original);
         } else {
-          DexMethod renamed = appView.graphLens().getRenamedMethodSignature(original);
+          DexMethod renamed = graphLens.getRenamedMethodSignature(original);
           if (renamed == existing) {
             extraOriginalMethodSignatures.put(method, original);
           } else {
@@ -92,11 +93,10 @@
   }
 
   @SuppressWarnings("ReferenceEquality")
-  private void recordOriginalTypeNames(
-      DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
+  private void recordOriginalTypeNames(DexProgramClass clazz, GraphLens graphLens) {
     DexType type = clazz.getType();
 
-    List<DexType> originalTypes = Lists.newArrayList(appView.graphLens().getOriginalTypes(type));
+    List<DexType> originalTypes = Lists.newArrayList(graphLens.getOriginalTypes(type));
     boolean isIdentity = originalTypes.size() == 1 && originalTypes.get(0) == type;
     if (!isIdentity) {
       originalTypes.forEach(
@@ -104,7 +104,7 @@
             assert !renamedTypeNames.containsKey(originalType);
             renamedTypeNames.put(originalType, type);
           });
-      renamedTypeNames.setRepresentative(type, appView.graphLens().getOriginalType(type));
+      renamedTypeNames.setRepresentative(type, graphLens.getOriginalType(type));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java
index df70495..26376cd 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java
@@ -67,7 +67,7 @@
   @Override
   protected MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
-    return previous;
+    return previous.verify(this, codeLens);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
index a7ef09b..b0292c6 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -225,12 +225,6 @@
 
   public abstract String lookupPackageName(String pkg);
 
-  @Deprecated
-  public final DexType lookupClassType(DexType type) {
-    GraphLens appliedLens = getIdentityLens();
-    return lookupClassType(type, appliedLens);
-  }
-
   public final DexType lookupClassType(DexType type, GraphLens appliedLens) {
     return getRenamedReference(
         type, appliedLens, NonIdentityGraphLens::getNextClassType, DexType::isPrimitiveType);
@@ -322,7 +316,7 @@
       GraphLens codeLens,
       LookupMethodContinuation continuation);
 
-  interface LookupMethodContinuation {
+  public interface LookupMethodContinuation {
 
     MethodLookupResult lookupMethod(MethodLookupResult previous);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java
index e72f128..1497895 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/IdentityGraphLens.java
@@ -55,7 +55,7 @@
   public MethodLookupResult lookupMethod(
       DexMethod method, DexMethod context, InvokeType type, GraphLens codeLens) {
     assert codeLens == null || codeLens.isIdentityLens();
-    return MethodLookupResult.builder(this).setReference(method).setType(type).build();
+    return MethodLookupResult.builder(this, codeLens).setReference(method).setType(type).build();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/lens/MemberLookupResult.java b/src/main/java/com/android/tools/r8/graph/lens/MemberLookupResult.java
index 7ec7c85..9b89ff1 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/MemberLookupResult.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/MemberLookupResult.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.graph.lens;
 
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.ObjectUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
 import java.util.Map;
 import java.util.function.Function;
@@ -31,6 +35,20 @@
     return rewritings.getOrDefault(reference, reference);
   }
 
+  @SuppressWarnings("unchecked")
+  public R getRewrittenReferenceFromRewrittenReboundReference(
+      R rewrittenReboundReference,
+      Function<DexType, DexType> typeRewriter,
+      DexItemFactory dexItemFactory) {
+    R rewrittenReference =
+        ObjectUtils.identical(reference, reboundReference)
+            ? rewrittenReboundReference
+            : (R)
+                rewrittenReboundReference.withHolder(
+                    typeRewriter.apply(reference.getHolderType()), dexItemFactory);
+    return rewrittenReference;
+  }
+
   public boolean hasReboundReference() {
     return reboundReference != null;
   }
@@ -39,7 +57,11 @@
     return reboundReference;
   }
 
-  public R getRewrittenReboundReference(BidirectionalManyToOneRepresentativeMap<R, R> rewritings) {
+  public R getRewrittenReboundReference(BidirectionalManyToManyRepresentativeMap<R, R> rewritings) {
+    return rewritings.getRepresentativeValueOrDefault(reboundReference, reboundReference);
+  }
+
+  public R getRewrittenReboundReference(Map<R, R> rewritings) {
     return rewritings.getOrDefault(reboundReference, reboundReference);
   }
 
@@ -53,11 +75,19 @@
     R reference;
     R reboundReference;
 
+    public R getReference() {
+      return reference;
+    }
+
     public Self setReference(R reference) {
       this.reference = reference;
       return self();
     }
 
+    public R getReboundReference() {
+      return reboundReference;
+    }
+
     public Self setReboundReference(R reboundReference) {
       this.reboundReference = reboundReference;
       return self();
diff --git a/src/main/java/com/android/tools/r8/graph/lens/MethodLookupResult.java b/src/main/java/com/android/tools/r8/graph/lens/MethodLookupResult.java
index d0c3ded..450585d 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/MethodLookupResult.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/MethodLookupResult.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.optimize.bridgehoisting.BridgeHoistingLens;
 
 /**
  * Result of a method lookup in a GraphLens.
@@ -30,29 +31,44 @@
     this.prototypeChanges = prototypeChanges;
   }
 
-  public static Builder builder(GraphLens lens) {
-    return new Builder(lens);
+  public static Builder builder(GraphLens lens, GraphLens codeLens) {
+    return new Builder(lens, codeLens);
   }
 
   public InvokeType getType() {
     return type;
   }
 
-  @SuppressWarnings("UnusedVariable")
   public RewrittenPrototypeDescription getPrototypeChanges() {
     return prototypeChanges;
   }
 
+  public MethodLookupResult verify(GraphLens lens, GraphLens codeLens) {
+    assert getReference() != null;
+    assert lens.isIdentityLens()
+            || lens.isAppliedLens()
+            || lens.asNonIdentityLens().isD8Lens()
+            || getReference().getHolderType().isArrayType()
+            || hasReboundReference()
+            // TODO: Disallow the following.
+            || lens.isEnumUnboxerLens()
+            || lens.isNumberUnboxerLens()
+            || lens instanceof BridgeHoistingLens
+        : lens;
+    return this;
+  }
+
   public static class Builder extends MemberLookupResult.Builder<DexMethod, Builder> {
 
-    @SuppressWarnings("UnusedVariable")
     private final GraphLens lens;
+    private final GraphLens codeLens;
 
     private RewrittenPrototypeDescription prototypeChanges = RewrittenPrototypeDescription.none();
     private InvokeType type;
 
-    private Builder(GraphLens lens) {
+    private Builder(GraphLens lens, GraphLens codeLens) {
       this.lens = lens;
+      this.codeLens = codeLens;
     }
 
     public Builder setPrototypeChanges(RewrittenPrototypeDescription prototypeChanges) {
@@ -66,9 +82,8 @@
     }
 
     public MethodLookupResult build() {
-      assert reference != null;
-      // TODO(b/168282032): All non-identity graph lenses should set the rebound reference.
-      return new MethodLookupResult(reference, reboundReference, type, prototypeChanges);
+      return new MethodLookupResult(reference, reboundReference, type, prototypeChanges)
+          .verify(lens, codeLens);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
index 63aa73a..1186ce2 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NestedGraphLens.java
@@ -172,12 +172,14 @@
               : // This assumes that the holder will always be moved in lock-step with the method!
               rewrittenReboundReference.withHolder(
                   getNextClassType(previous.getReference().getHolderType()), dexItemFactory());
-      return MethodLookupResult.builder(this)
+      return MethodLookupResult.builder(this, codeLens)
           .setReference(rewrittenReference)
           .setReboundReference(rewrittenReboundReference)
           .setPrototypeChanges(
               internalDescribePrototypeChanges(
-                  previous.getPrototypeChanges(), rewrittenReboundReference))
+                  previous.getPrototypeChanges(),
+                  previous.getReboundReference(),
+                  rewrittenReboundReference))
           .setType(
               mapInvocationType(
                   rewrittenReboundReference, previous.getReference(), previous.getType()))
@@ -190,14 +192,15 @@
         newMethod = previous.getReference();
       }
       RewrittenPrototypeDescription newPrototypeChanges =
-          internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod);
+          internalDescribePrototypeChanges(
+              previous.getPrototypeChanges(), previous.getReference(), newMethod);
       if (newMethod == previous.getReference()
           && newPrototypeChanges == previous.getPrototypeChanges()) {
-        return previous;
+        return previous.verify(this, codeLens);
       }
       // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
       //  that only subclasses which are known to need it actually do it?
-      return MethodLookupResult.builder(this)
+      return MethodLookupResult.builder(this, codeLens)
           .setReference(newMethod)
           .setPrototypeChanges(newPrototypeChanges)
           .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
@@ -214,11 +217,13 @@
     DexMethod previous = getPreviousMethodSignature(method);
     RewrittenPrototypeDescription lookup =
         getPrevious().lookupPrototypeChangesForMethodDefinition(previous, codeLens);
-    return internalDescribePrototypeChanges(lookup, method);
+    return internalDescribePrototypeChanges(lookup, previous, method);
   }
 
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
-      RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+      RewrittenPrototypeDescription prototypeChanges,
+      DexMethod previousMethod,
+      DexMethod newMethod) {
     return prototypeChanges;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java
index 07fac2c..987bfe3 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.utils.ThrowingAction;
+import com.google.common.collect.Streams;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Predicate;
@@ -82,20 +83,19 @@
   }
 
   @Override
-  @SuppressWarnings("ReferenceEquality")
   public MethodLookupResult lookupMethod(
-      DexMethod method, DexMethod context, InvokeType type, GraphLens codeLens) {
+      DexMethod method, DexMethod context, InvokeType invokeType, GraphLens codeLens) {
     if (method.getHolderType().isArrayType()) {
-      assert lookupType(method.getReturnType()) == method.getReturnType();
-      assert method.getParameters().stream()
-          .allMatch(parameterType -> lookupType(parameterType) == parameterType);
-      return MethodLookupResult.builder(this)
-          .setReference(method.withHolder(lookupType(method.getHolderType()), dexItemFactory))
-          .setType(type)
+      assert Streams.stream(method.getReferencedBaseTypes(dexItemFactory))
+          .allMatch(type -> type.isIdenticalTo(lookupClassType(type, codeLens)));
+      return MethodLookupResult.builder(this, codeLens)
+          .setReference(
+              method.withHolder(lookupType(method.getHolderType(), codeLens), dexItemFactory))
+          .setType(invokeType)
           .build();
     }
     assert method.getHolderType().isClassType();
-    return internalLookupMethod(method, context, type, codeLens, result -> result);
+    return internalLookupMethod(method, context, invokeType, codeLens, result -> result);
   }
 
   @Override
@@ -186,6 +186,10 @@
 
   public abstract DexMethod getNextMethodSignature(DexMethod method);
 
+  public final boolean isD8Lens() {
+    return !appView.enableWholeProgramOptimizations();
+  }
+
   @Override
   public final boolean isIdentityLens() {
     return false;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index d6b92e0..ecbf167 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -7,6 +7,7 @@
 import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.classmerging.SyntheticArgumentClass;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 141e7f6..57ff66a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
 
+import com.android.tools.r8.classmerging.SyntheticArgumentClass;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -14,9 +15,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.lens.MethodLookupResult;
 import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
-import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
@@ -175,7 +174,8 @@
             mode,
             profileCollectionAdditions,
             syntheticArgumentClass,
-            executorService);
+            executorService,
+            timing);
     profileCollectionAdditions =
         profileCollectionAdditions.rewriteMethodReferences(
             horizontalClassMergerGraphLens::getNextMethodToInvoke);
@@ -255,14 +255,10 @@
               for (VirtuallyMergedMethodsKeepInfo virtuallyMergedMethodsKeepInfo :
                   virtuallyMergedMethodsKeepInfos) {
                 DexMethod representative = virtuallyMergedMethodsKeepInfo.getRepresentative();
-                MethodLookupResult lookupResult =
-                    horizontalClassMergerGraphLens.lookupMethod(
-                        representative,
-                        null,
-                        InvokeType.VIRTUAL,
-                        horizontalClassMergerGraphLens.getPrevious());
+                DexMethod mergedMethodReference =
+                    horizontalClassMergerGraphLens.getNextMethodToInvoke(representative);
                 ProgramMethod mergedMethod =
-                    asProgramMethodOrNull(appView.definitionFor(lookupResult.getReference()));
+                    asProgramMethodOrNull(appView.definitionFor(mergedMethodReference));
                 if (mergedMethod != null) {
                   keepInfo.joinMethod(
                       mergedMethod,
@@ -427,7 +423,8 @@
       Mode mode,
       ProfileCollectionAdditions profileCollectionAdditions,
       SyntheticArgumentClass syntheticArgumentClass,
-      ExecutorService executorService)
+      ExecutorService executorService,
+      Timing timing)
       throws ExecutionException {
     return new HorizontalClassMergerTreeFixer(
             appView,
@@ -436,7 +433,7 @@
             mode,
             profileCollectionAdditions,
             syntheticArgumentClass)
-        .run(executorService);
+        .run(executorService, timing);
   }
 
   @SuppressWarnings("ReferenceEquality")
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 200a943..24e3f18 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -15,9 +15,11 @@
 import com.android.tools.r8.graph.lens.FieldLookupResult;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
@@ -40,9 +42,14 @@
       HorizontallyMergedClasses mergedClasses,
       Map<DexMethod, List<ExtraParameter>> methodExtraParameters,
       BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
-      Map<DexMethod, DexMethod> methodMap,
+      BidirectionalManyToOneMap<DexMethod, DexMethod> methodMap,
       BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) {
-    super(appView, fieldMap, methodMap, mergedClasses.getBidirectionalMap(), newMethodSignatures);
+    super(
+        appView,
+        fieldMap,
+        methodMap.getForwardMap(),
+        mergedClasses.getBidirectionalMap(),
+        newMethodSignatures);
     this.methodExtraParameters = methodExtraParameters;
     this.mergedClasses = mergedClasses;
   }
@@ -67,6 +74,32 @@
     return IterableUtils.prependSingleton(previous, mergedClasses.getSourcesFor(previous));
   }
 
+  @Override
+  protected MethodLookupResult internalLookupMethod(
+      DexMethod reference,
+      DexMethod context,
+      InvokeType type,
+      GraphLens codeLens,
+      LookupMethodContinuation continuation) {
+    if (this == codeLens) {
+      // We sometimes create code objects that have the HorizontalClassMergerGraphLens as code lens.
+      // When using this lens as a code lens there is no lens that will insert the rebound reference
+      // since the MemberRebindingIdentityLens is an ancestor of the HorizontalClassMergerGraphLens.
+      // We therefore use the reference itself as the rebound reference here, which is safe since
+      // the code objects created during horizontal class merging are guaranteed not to contain
+      // any non-rebound method references.
+      // TODO(b/315284255): Actually guarantee the above!
+      MethodLookupResult lookupResult =
+          MethodLookupResult.builder(this, codeLens)
+              .setReboundReference(reference)
+              .setReference(reference)
+              .setType(type)
+              .build();
+      return continuation.lookupMethod(lookupResult);
+    }
+    return super.internalLookupMethod(reference, context, type, codeLens, continuation);
+  }
+
   /**
    * If an overloaded constructor is requested, add the constructor id as a parameter to the
    * constructor. Otherwise return the lookup on the underlying graph lens.
@@ -74,12 +107,18 @@
   @Override
   public MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
-    List<ExtraParameter> extraParameters = methodExtraParameters.get(previous.getReference());
+    if (!previous.hasReboundReference()) {
+      return super.internalDescribeLookupMethod(previous, context, codeLens);
+    }
+    assert previous.hasReboundReference();
+    List<ExtraParameter> extraParameters =
+        methodExtraParameters.get(previous.getReboundReference());
     MethodLookupResult lookup = super.internalDescribeLookupMethod(previous, context, codeLens);
     if (extraParameters == null) {
       return lookup;
     }
-    return MethodLookupResult.builder(this)
+    return MethodLookupResult.builder(this, codeLens)
+        .setReboundReference(lookup.getReboundReference())
         .setReference(lookup.getReference())
         .setPrototypeChanges(lookup.getPrototypeChanges().withExtraParameters(extraParameters))
         .setType(lookup.getType())
@@ -144,7 +183,7 @@
           mergedClasses,
           methodExtraParameters,
           newFieldSignatures,
-          methodMap.getForwardMap(),
+          methodMap,
           newMethodSignatures);
     }
 
@@ -237,9 +276,7 @@
       if (originalMethodSignatures.isEmpty()) {
         pendingMethodMapUpdates.put(oldMethodSignature, newMethodSignature);
       } else {
-        for (DexMethod originalMethodSignature : originalMethodSignatures) {
-          pendingMethodMapUpdates.put(originalMethodSignature, newMethodSignature);
-        }
+        pendingMethodMapUpdates.put(originalMethodSignatures, newMethodSignature);
       }
     }
 
@@ -253,9 +290,7 @@
       if (oldMemberSignatures.isEmpty()) {
         pendingNewMemberSignatureUpdates.put(oldMemberSignature, newMemberSignature);
       } else {
-        for (R originalMethodSignature : oldMemberSignatures) {
-          pendingNewMemberSignatureUpdates.put(originalMethodSignature, newMemberSignature);
-        }
+        pendingNewMemberSignatureUpdates.put(oldMemberSignatures, newMemberSignature);
         R representative = newMemberSignatures.getRepresentativeKey(oldMemberSignature);
         if (representative != null) {
           pendingNewMemberSignatureUpdates.setRepresentative(newMemberSignature, representative);
@@ -298,11 +333,11 @@
 
     @Override
     public void addExtraParameters(
-        DexMethod methodSignature, List<? extends ExtraParameter> extraParameters) {
-      Set<DexMethod> originalMethodSignatures = methodMap.getKeys(methodSignature);
+        DexMethod from, DexMethod to, List<? extends ExtraParameter> extraParameters) {
+      Set<DexMethod> originalMethodSignatures = methodMap.getKeys(from);
       if (originalMethodSignatures.isEmpty()) {
         methodExtraParameters
-            .computeIfAbsent(methodSignature, ignore -> new ArrayList<>(extraParameters.size()))
+            .computeIfAbsent(from, ignore -> new ArrayList<>(extraParameters.size()))
             .addAll(extraParameters);
       } else {
         for (DexMethod originalMethodSignature : originalMethodSignatures) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java
index 260ec11..fb27321 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerTreeFixer.java
@@ -5,9 +5,11 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.classmerging.ClassMergerTreeFixer;
+import com.android.tools.r8.classmerging.SyntheticArgumentClass;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
+import com.android.tools.r8.utils.Timing;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -89,9 +91,9 @@
    * </ul>
    */
   @Override
-  public HorizontalClassMergerGraphLens run(ExecutorService executorService)
+  public HorizontalClassMergerGraphLens run(ExecutorService executorService, Timing timing)
       throws ExecutionException {
-    return super.run(executorService);
+    return super.run(executorService, timing);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
index 837157e..e09f988 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
@@ -102,9 +102,7 @@
       int classIdLocalIndex = maxLocals - 1 - extraNulls;
       instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
       instructionBuilder.add(new CfLoad(ValueType.INT, classIdLocalIndex));
-      instructionBuilder.add(
-          new CfInstanceFieldWrite(
-              lens.getRenamedFieldSignature(classIdField, lens.getPrevious())));
+      instructionBuilder.add(new CfInstanceFieldWrite(lens.getNextFieldSignature(classIdField)));
       maxStack.set(2);
     }
 
@@ -123,7 +121,8 @@
     instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
 
     // Load constructor arguments.
-    MethodLookupResult parentConstructorLookup = lens.lookupInvokeDirect(parentConstructor, method);
+    MethodLookupResult parentConstructorLookup =
+        lens.lookupInvokeDirect(parentConstructor, method, appView.codeLens());
 
     int i = 0;
     for (InstanceFieldInitializationInfo initializationInfo : parentConstructorArguments) {
@@ -195,7 +194,7 @@
           int stackSizeForInitializationInfo =
               addCfInstructionsForInitializationInfo(
                   instructionBuilder, initializationInfo, argumentToLocalIndex, field.getType());
-          DexField rewrittenField = lens.getRenamedFieldSignature(field, lens.getPrevious());
+          DexField rewrittenField = lens.getNextFieldSignature(field);
 
           // Insert a check to ensure the program continues to type check according to Java type
           // checking. Otherwise, instance initializer merging may cause open interfaces. If
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java
index 6f6a957..6698a71 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java
@@ -141,7 +141,7 @@
           lens.lookupInvokeSuper(superMethod.getReboundReference(), method).getReference();
       fallthroughTarget =
           reboundFallthroughTarget.withHolder(
-              lens.lookupClassType(superMethod.getReference().getHolderType()),
+              lens.getNextClassType(superMethod.getReference().getHolderType()),
               appView.dexItemFactory());
     }
     instructions.add(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
index 29db86f..67022ed 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -73,6 +74,7 @@
     InstanceInitializerDescription.Builder builder =
         InstanceInitializerDescription.builder(appView, instanceInitializer);
     IRCode code = codeProvider.buildIR(instanceInitializer);
+    GraphLens codeLens = instanceInitializer.getDefinition().getCode().getCodeLens(appView);
     WorkList<BasicBlock> workList = WorkList.newIdentityWorkList(code.entryBlock());
     while (workList.hasNext()) {
       BasicBlock block = workList.next();
@@ -105,7 +107,7 @@
               // Check that this writes a field on the enclosing class.
               DexField fieldReference = instancePut.getField();
               DexField lensRewrittenFieldReference =
-                  appView.graphLens().lookupField(fieldReference);
+                  appView.graphLens().lookupField(fieldReference, codeLens);
               if (lensRewrittenFieldReference.getHolderType()
                   != instanceInitializer.getHolderType()) {
                 return invalid();
@@ -143,7 +145,7 @@
               DexMethod lensRewrittenInvokedMethod =
                   appView
                       .graphLens()
-                      .lookupInvokeDirect(invokedMethod, instanceInitializer)
+                      .lookupInvokeDirect(invokedMethod, instanceInitializer, codeLens)
                       .getReference();
 
               // TODO(b/189296638): Consider allowing constructor forwarding.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 38b47fc..54f4898 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.dex.Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX;
 
 import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.classmerging.SyntheticArgumentClass;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index 8b4cbce..e87faba 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -130,7 +130,7 @@
     // All the code has been processed so the rewriting required by the lenses is done everywhere,
     // we clear lens code rewriting so that the lens rewriter can be re-executed in phase 2 if new
     // lenses with code rewriting are added.
-    appView.clearCodeRewritings();
+    appView.clearCodeRewritings(executorService);
 
     // Commit synthetics from the primary optimization pass.
     commitPendingSyntheticItems(appView);
@@ -215,7 +215,7 @@
     // All the code that should be impacted by the lenses inserted between phase 1 and phase 2
     // have now been processed and rewritten, we clear code lens rewriting so that the class
     // staticizer and phase 3 does not perform again the rewriting.
-    appView.clearCodeRewritings();
+    appView.clearCodeRewritings(executorService);
 
     // Commit synthetics before creating a builder (otherwise the builder will not include the
     // synthetics.)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 2a3da47..2107f3d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -53,10 +53,6 @@
     return graphLens;
   }
 
-  public void disallowStaticInterfaceMethodCalls() {
-    allowStaticInterfaceMethodCalls = false;
-  }
-
   public ConstraintWithTarget forAlwaysMaterializingUser() {
     return ConstraintWithTarget.ALWAYS;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 7935419..cb5bbbe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -515,16 +515,17 @@
       inliner.performForcedInlining(
           method, code, methodCallsOnInstance, inliningIRProvider, methodProcessor, Timing.empty());
     } else {
-      assert indirectMethodCallsOnInstance.stream()
-          .filter(method -> method.getDefinition().getOptimizationInfo().mayHaveSideEffects())
-          .allMatch(
-              method ->
-                  method.getDefinition().isInstanceInitializer()
-                      && !method
-                          .getDefinition()
-                          .getOptimizationInfo()
-                          .getContextInsensitiveInstanceInitializerInfo()
-                          .mayHaveOtherSideEffectsThanInstanceFieldAssignments());
+      // TODO(b/315284776): Diagnose if this should be removed or reenabled.
+      /*assert indirectMethodCallsOnInstance.stream()
+      .filter(method -> method.getDefinition().getOptimizationInfo().mayHaveSideEffects())
+      .allMatch(
+          method ->
+              method.getDefinition().isInstanceInitializer()
+                  && !method
+                      .getDefinition()
+                      .getOptimizationInfo()
+                      .getContextInsensitiveInstanceInitializerInfo()
+                      .mayHaveOtherSideEffectsThanInstanceFieldAssignments());*/
     }
     return true;
   }
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 37b6314..1eee403 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
@@ -135,7 +135,31 @@
   }
 
   @Override
-  @SuppressWarnings("ReferenceEquality")
+  protected MethodLookupResult internalLookupMethod(
+      DexMethod reference,
+      DexMethod context,
+      InvokeType type,
+      GraphLens codeLens,
+      LookupMethodContinuation continuation) {
+    if (this == codeLens) {
+      // We sometimes create code objects that have the EnumUnboxingLens as code lens.
+      // When using this lens as a code lens there is no lens that will insert the rebound reference
+      // since the MemberRebindingIdentityLens is an ancestor of the EnumUnboxingLens.
+      // We therefore use the reference itself as the rebound reference here, which is safe since
+      // the code objects created during enum unboxing are guaranteed not to contain any non-rebound
+      // method references.
+      MethodLookupResult lookupResult =
+          MethodLookupResult.builder(this, codeLens)
+              .setReboundReference(reference)
+              .setReference(reference)
+              .setType(type)
+              .build();
+      return continuation.lookupMethod(lookupResult);
+    }
+    return super.internalLookupMethod(reference, context, type, codeLens, continuation);
+  }
+
+  @Override
   public MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
     assert context != null || verifyIsContextFreeForMethod(previous.getReference(), codeLens);
@@ -146,9 +170,9 @@
       DexMethod previousContext = getPreviousMethodSignature(context);
       DexType superEnum = unboxedEnums.representativeType(previousContext.getHolderType());
       if (unboxedEnums.isUnboxedEnum(superEnum)) {
-        if (superEnum != previousContext.getHolderType()) {
+        if (superEnum.isNotIdenticalTo(previousContext.getHolderType())) {
           DexMethod reference = previous.getReference();
-          if (reference.getHolderType() != superEnum) {
+          if (reference.getHolderType().isNotIdenticalTo(superEnum)) {
             // We are in an enum subtype where super-invokes are rebound differently.
             reference = reference.withHolder(superEnum, dexItemFactory());
           }
@@ -156,7 +180,7 @@
         } else {
           // This is a super-invoke to a library method, not rewritten by the lens.
           // This is rewritten by the EnumUnboxerRewriter.
-          return previous;
+          return previous.verify(this, codeLens);
         }
       } else {
         result = methodMap.apply(previous.getReference());
@@ -165,12 +189,13 @@
       result = methodMap.apply(previous.getReference());
     }
     if (result == null) {
-      return previous;
+      return previous.verify(this, codeLens);
     }
-    return MethodLookupResult.builder(this)
+    return MethodLookupResult.builder(this, codeLens)
         .setReference(result)
         .setPrototypeChanges(
-            internalDescribePrototypeChanges(previous.getPrototypeChanges(), result))
+            internalDescribePrototypeChanges(
+                previous.getPrototypeChanges(), previous.getReference(), result))
         .setType(mapInvocationType(result, previous.getReference(), previous.getType()))
         .build();
   }
@@ -178,7 +203,9 @@
   @Override
   @SuppressWarnings("ReferenceEquality")
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
-      RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+      RewrittenPrototypeDescription prototypeChanges,
+      DexMethod previousMethod,
+      DexMethod newMethod) {
     // Rewrite the single value of the given RewrittenPrototypeDescription if it is referring to an
     // unboxed enum field.
     if (prototypeChanges.hasRewrittenReturnInfo()) {
@@ -198,12 +225,8 @@
         }
       }
     }
-
-    // During the second IR processing enum unboxing is the only optimization rewriting
-    // prototype description, if this does not hold, remove the assertion and merge
-    // the two prototype changes.
     RewrittenPrototypeDescription enumUnboxingPrototypeChanges =
-        prototypeChangesPerMethod.getOrDefault(method, RewrittenPrototypeDescription.none());
+        prototypeChangesPerMethod.getOrDefault(newMethod, RewrittenPrototypeDescription.none());
     return prototypeChanges.combine(enumUnboxingPrototypeChanges);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java
index eeb1768..8e7c278 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java
@@ -35,10 +35,12 @@
 
   @Override
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
-      RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
-    RewrittenPrototypeDescription enumUnboxingPrototypeChanges =
-        prototypeChangesPerMethod.getOrDefault(method, RewrittenPrototypeDescription.none());
-    return prototypeChanges.combine(enumUnboxingPrototypeChanges);
+      RewrittenPrototypeDescription previousPrototypeChanges,
+      DexMethod previousMethod,
+      DexMethod newMethod) {
+    RewrittenPrototypeDescription prototypeChanges =
+        prototypeChangesPerMethod.getOrDefault(newMethod, RewrittenPrototypeDescription.none());
+    return previousPrototypeChanges.combine(prototypeChanges);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
deleted file mode 100644
index 4b71a32..0000000
--- a/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2018, 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.optimize;
-
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.lens.DefaultNonIdentityGraphLens;
-import com.android.tools.r8.graph.lens.FieldLookupResult;
-import com.android.tools.r8.graph.lens.GraphLens;
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-/**
- * This lens is used to populate the rebound field reference during lookup, such that both the
- * non-rebound and rebound field references are available to all descendants of this lens.
- *
- * <p>TODO(b/157616970): All uses of this should be replaced by {@link MemberRebindingIdentityLens}.
- */
-public class FieldRebindingIdentityLens extends DefaultNonIdentityGraphLens {
-
-  private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap;
-
-  private FieldRebindingIdentityLens(
-      Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap,
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      GraphLens previousLens) {
-    super(appView, previousLens);
-    this.nonReboundFieldReferenceToDefinitionMap = nonReboundFieldReferenceToDefinitionMap;
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  @Override
-  public boolean hasCodeRewritings() {
-    return false;
-  }
-
-  @Override
-  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
-    assert !previous.hasReadCastType();
-    assert !previous.hasReboundReference();
-    return FieldLookupResult.builder(this)
-        .setReference(previous.getReference())
-        .setReboundReference(getReboundFieldReference(previous.getReference()))
-        .build();
-  }
-
-  private DexField getReboundFieldReference(DexField field) {
-    return nonReboundFieldReferenceToDefinitionMap.getOrDefault(field, field);
-  }
-
-  public static class Builder {
-
-    private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap =
-        new IdentityHashMap<>();
-
-    private Builder() {}
-
-    void recordDefinitionForNonReboundFieldReference(
-        DexField nonReboundFieldReference, DexField reboundFieldReference) {
-      nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
-    }
-
-    FieldRebindingIdentityLens build(AppView<? extends AppInfoWithClassHierarchy> appView) {
-      // This intentionally does not return null when the map is empty. In this case there are no
-      // non-rebound field references, but the member rebinding lens is still needed to populate the
-      // rebound reference during field lookup.
-      return new FieldRebindingIdentityLens(
-          nonReboundFieldReferenceToDefinitionMap, appView, GraphLens.getIdentityLens());
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index b608f37..5acd3f2 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -16,13 +16,11 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -30,8 +28,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.TriConsumer;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Iterables;
@@ -40,9 +36,7 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
@@ -64,10 +58,6 @@
     this.lensBuilder = MemberRebindingLens.builder(appView);
   }
 
-  private AppView<AppInfoWithLiveness> appView() {
-    return appView;
-  }
-
   private DexMethod validMemberRebindingTargetForNonProgramMethod(
       DexClassAndMethod resolvedMethod,
       SingleResolutionResult<?> resolutionResult,
@@ -515,141 +505,13 @@
     return null;
   }
 
-  private void recordNonReboundFieldAccesses(ExecutorService executorService)
-      throws ExecutionException {
-    assert verifyFieldAccessCollectionContainsAllNonReboundFieldReferences(executorService);
-    FieldAccessInfoCollection<?> fieldAccessInfoCollection =
-        appView.appInfo().getFieldAccessInfoCollection();
-    fieldAccessInfoCollection.forEach(lensBuilder::recordNonReboundFieldAccesses);
-  }
-
-  public void run(ExecutorService executorService) throws ExecutionException {
+  public void run() throws ExecutionException {
     AppInfoWithLiveness appInfo = appView.appInfo();
     computeMethodRebinding(appInfo.getMethodAccessInfoCollection());
-    recordNonReboundFieldAccesses(executorService);
     appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
     MemberRebindingLens memberRebindingLens = lensBuilder.build();
     appView.setGraphLens(memberRebindingLens);
     eventConsumer.finished(appView, memberRebindingLens);
     appView.notifyOptimizationFinishedForTesting();
   }
-
-  @SuppressWarnings("ReferenceEquality")
-  private boolean verifyFieldAccessCollectionContainsAllNonReboundFieldReferences(
-      ExecutorService executorService) throws ExecutionException {
-    Set<DexField> nonReboundFieldReferences = computeNonReboundFieldReferences(executorService);
-    FieldAccessInfoCollection<?> fieldAccessInfoCollection =
-        appView.appInfo().getFieldAccessInfoCollection();
-    fieldAccessInfoCollection.forEach(
-        info -> {
-          DexField reboundFieldReference = info.getField();
-          info.forEachIndirectAccess(
-              nonReboundFieldReference -> {
-                assert reboundFieldReference != nonReboundFieldReference;
-                assert reboundFieldReference
-                    == appView
-                        .appInfo()
-                        .resolveField(nonReboundFieldReference)
-                        .getResolvedFieldReference();
-                nonReboundFieldReferences.remove(nonReboundFieldReference);
-              });
-        });
-    assert nonReboundFieldReferences.isEmpty();
-    return true;
-  }
-
-  private Set<DexField> computeNonReboundFieldReferences(ExecutorService executorService)
-      throws ExecutionException {
-    Set<DexField> nonReboundFieldReferences = SetUtils.newConcurrentHashSet();
-    ThreadUtils.processItems(
-        appView.appInfo()::forEachMethod,
-        method -> {
-          if (method.getDefinition().hasCode()) {
-            method.registerCodeReferences(
-                new UseRegistry<ProgramMethod>(appView, method) {
-
-                  @Override
-                  public void registerInstanceFieldRead(DexField field) {
-                    registerFieldReference(field);
-                  }
-
-                  @Override
-                  public void registerInstanceFieldWrite(DexField field) {
-                    registerFieldReference(field);
-                  }
-
-                  @Override
-                  public void registerStaticFieldRead(DexField field) {
-                    registerFieldReference(field);
-                  }
-
-                  @Override
-                  public void registerStaticFieldWrite(DexField field) {
-                    registerFieldReference(field);
-                  }
-
-                  @SuppressWarnings("ReferenceEquality")
-                  private void registerFieldReference(DexField field) {
-                    appView()
-                        .appInfo()
-                        .resolveField(field)
-                        .forEachSuccessfulFieldResolutionResult(
-                            resolutionResult -> {
-                              if (resolutionResult.getResolvedField().getReference() != field) {
-                                nonReboundFieldReferences.add(field);
-                              }
-                            });
-                  }
-
-                  @Override
-                  public void registerInitClass(DexType type) {
-                    // Intentionally empty.
-                  }
-
-                  @Override
-                  public void registerInvokeDirect(DexMethod method) {
-                    // Intentionally empty.
-                  }
-
-                  @Override
-                  public void registerInvokeInterface(DexMethod method) {
-                    // Intentionally empty.
-                  }
-
-                  @Override
-                  public void registerInvokeStatic(DexMethod method) {
-                    // Intentionally empty.
-                  }
-
-                  @Override
-                  public void registerInvokeSuper(DexMethod method) {
-                    // Intentionally empty.
-                  }
-
-                  @Override
-                  public void registerInvokeVirtual(DexMethod method) {
-                    // Intentionally empty.
-                  }
-
-                  @Override
-                  public void registerNewInstance(DexType type) {
-                    // Intentionally empty.
-                  }
-
-                  @Override
-                  public void registerInstanceOf(DexType type) {
-                    // Intentionally empty.
-                  }
-
-                  @Override
-                  public void registerTypeReference(DexType type) {
-                    // Intentionally empty.
-                  }
-                });
-          }
-        },
-        appView.options().getThreadingModule(),
-        executorService);
-    return nonReboundFieldReferences;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index 60d5c62..234b353 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -77,7 +77,7 @@
   public MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
     assert previous.getReboundReference() == null;
-    return MethodLookupResult.builder(this)
+    return MethodLookupResult.builder(this, codeLens)
         .setReference(previous.getReference())
         .setReboundReference(getReboundMethodReference(previous.getReference()))
         .setPrototypeChanges(previous.getPrototypeChanges())
@@ -85,10 +85,9 @@
         .build();
   }
 
-  @SuppressWarnings("ReferenceEquality")
   private DexMethod getReboundMethodReference(DexMethod method) {
     DexMethod rebound = nonReboundMethodReferenceToDefinitionMap.get(method);
-    assert method != rebound;
+    assert method.isNotIdenticalTo(rebound);
     while (rebound != null) {
       method = rebound;
       rebound = nonReboundMethodReferenceToDefinitionMap.get(method);
@@ -130,8 +129,10 @@
                   lens.lookupType(
                       nonReboundFieldReference.getHolderType(), appliedMemberRebindingLens),
                   dexItemFactory);
-          builder.recordNonReboundFieldAccess(
-              rewrittenNonReboundFieldReference, rewrittenReboundFieldReference);
+          if (rewrittenNonReboundFieldReference.isNotIdenticalTo(rewrittenReboundFieldReference)) {
+            builder.recordNonReboundFieldAccess(
+                rewrittenNonReboundFieldReference, rewrittenReboundFieldReference);
+          }
         });
     nonReboundMethodReferenceToDefinitionMap.forEach(
         (nonReboundMethodReference, reboundMethodReference) -> {
@@ -142,8 +143,11 @@
                   lens.lookupType(
                       nonReboundMethodReference.getHolderType(), appliedMemberRebindingLens),
                   dexItemFactory);
-          builder.recordNonReboundMethodAccess(
-              rewrittenNonReboundMethodReference, rewrittenReboundMethodReference);
+          if (rewrittenNonReboundMethodReference.isNotIdenticalTo(
+              rewrittenReboundMethodReference)) {
+            builder.recordNonReboundMethodAccess(
+                rewrittenNonReboundMethodReference, rewrittenReboundMethodReference);
+          }
         });
     return builder.build();
   }
@@ -171,11 +175,13 @@
 
     private void recordNonReboundFieldAccess(
         DexField nonReboundFieldReference, DexField reboundFieldReference) {
+      assert nonReboundFieldReference.isNotIdenticalTo(reboundFieldReference);
       nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
     }
 
     private void recordNonReboundMethodAccess(
         DexMethod nonReboundMethodReference, DexMethod reboundMethodReference) {
+      assert nonReboundMethodReference.isNotIdenticalTo(reboundMethodReference);
       nonReboundMethodReferenceToDefinitionMap.put(
           nonReboundMethodReference, reboundMethodReference);
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
index 331d29e..19e25d2 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
@@ -37,6 +37,30 @@
    * Otherwise we apply the {@link NonReboundMemberReferencesRegistry} below to all code objects to
    * compute the mapping.
    */
+  public static MemberRebindingIdentityLens rebuild(
+      AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executorService)
+      throws ExecutionException {
+    FieldAccessInfoCollectionImpl mutableFieldAccessInfoCollection =
+        new FieldAccessInfoCollectionImpl(new ConcurrentHashMap<>());
+    MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder =
+        MethodAccessInfoCollection.concurrentBuilder();
+    initializeMemberAccessInfoCollectionsForMemberRebinding(
+        appView,
+        mutableFieldAccessInfoCollection,
+        methodAccessInfoCollectionBuilder,
+        executorService);
+    return create(
+        appView, mutableFieldAccessInfoCollection, methodAccessInfoCollectionBuilder.build());
+  }
+
+  /**
+   * In order to construct an instance of {@link MemberRebindingIdentityLens} we need a mapping from
+   * non-rebound field and method references to their definitions.
+   *
+   * <p>If shrinking or minification is enabled, we retrieve these from {@link AppInfoWithLiveness}.
+   * Otherwise we apply the {@link NonReboundMemberReferencesRegistry} below to all code objects to
+   * compute the mapping.
+   */
   public static MemberRebindingIdentityLens create(
       AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executorService)
       throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
index 6b2903e..6445afd 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -6,17 +6,12 @@
 
 import static com.android.tools.r8.graph.lens.NestedGraphLens.mapVirtualInterfaceInvocationTypes;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-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.FieldAccessInfo;
 import com.android.tools.r8.graph.lens.DefaultNonIdentityGraphLens;
 import com.android.tools.r8.graph.lens.FieldLookupResult;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.lens.MethodLookupResult;
-import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
 import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collections;
@@ -26,15 +21,11 @@
 public class MemberRebindingLens extends DefaultNonIdentityGraphLens {
 
   private final Map<InvokeType, Map<DexMethod, DexMethod>> methodMaps;
-  private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap;
 
   public MemberRebindingLens(
-      AppView<AppInfoWithLiveness> appView,
-      Map<InvokeType, Map<DexMethod, DexMethod>> methodMaps,
-      Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap) {
+      AppView<AppInfoWithLiveness> appView, Map<InvokeType, Map<DexMethod, DexMethod>> methodMaps) {
     super(appView);
     this.methodMaps = methodMaps;
-    this.nonReboundFieldReferenceToDefinitionMap = nonReboundFieldReferenceToDefinitionMap;
   }
 
   public static Builder builder(AppView<AppInfoWithLiveness> appView) {
@@ -53,16 +44,7 @@
 
   @Override
   protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
-    assert !previous.hasReadCastType();
-    assert !previous.hasReboundReference();
-    return FieldLookupResult.builder(this)
-        .setReference(previous.getReference())
-        .setReboundReference(getReboundFieldReference(previous.getReference()))
-        .build();
-  }
-
-  private DexField getReboundFieldReference(DexField field) {
-    return nonReboundFieldReferenceToDefinitionMap.getOrDefault(field, field);
+    return previous;
   }
 
   @Override
@@ -70,16 +52,15 @@
       MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
     Map<DexMethod, DexMethod> methodMap =
         methodMaps.getOrDefault(previous.getType(), Collections.emptyMap());
-    DexMethod newMethod = methodMap.get(previous.getReference());
-    if (newMethod == null) {
-      return previous;
-    }
-    return MethodLookupResult.builder(this)
-        .setReference(newMethod)
+    DexMethod newReboundMethod =
+        methodMap.getOrDefault(previous.getReference(), previous.getReference());
+    return MethodLookupResult.builder(this, codeLens)
+        .setReboundReference(newReboundMethod)
+        .setReference(newReboundMethod)
         .setPrototypeChanges(previous.getPrototypeChanges())
         .setType(
             mapVirtualInterfaceInvocationTypes(
-                appView, newMethod, previous.getReference(), previous.getType()))
+                appView, newReboundMethod, previous.getReference(), previous.getType()))
         .build();
   }
 
@@ -91,33 +72,10 @@
     return getPrevious().isIdentityLensForFields(codeLens);
   }
 
-  public FieldRebindingIdentityLens toRewrittenFieldRebindingLens(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      GraphLens lens,
-      NonIdentityGraphLens appliedMemberRebindingLens) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    FieldRebindingIdentityLens.Builder builder = FieldRebindingIdentityLens.builder();
-    nonReboundFieldReferenceToDefinitionMap.forEach(
-        (nonReboundFieldReference, reboundFieldReference) -> {
-          DexField rewrittenReboundFieldReference =
-              lens.getRenamedFieldSignature(reboundFieldReference, appliedMemberRebindingLens);
-          DexField rewrittenNonReboundFieldReference =
-              rewrittenReboundFieldReference.withHolder(
-                  lens.lookupType(
-                      nonReboundFieldReference.getHolderType(), appliedMemberRebindingLens),
-                  dexItemFactory);
-          builder.recordDefinitionForNonReboundFieldReference(
-              rewrittenNonReboundFieldReference, rewrittenReboundFieldReference);
-        });
-    return builder.build(appView);
-  }
-
   public static class Builder {
 
     private final AppView<AppInfoWithLiveness> appView;
     private final Map<InvokeType, Map<DexMethod, DexMethod>> methodMaps = new IdentityHashMap<>();
-    private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap =
-        new IdentityHashMap<>();
 
     private Builder(AppView<AppInfoWithLiveness> appView) {
       this.appView = appView;
@@ -135,20 +93,8 @@
       methodMap.put(from, to);
     }
 
-    void recordNonReboundFieldAccesses(FieldAccessInfo info) {
-      DexField reboundFieldReference = info.getField();
-      info.forEachIndirectAccess(
-          nonReboundFieldReference ->
-              recordNonReboundFieldAccess(nonReboundFieldReference, reboundFieldReference));
-    }
-
-    private void recordNonReboundFieldAccess(
-        DexField nonReboundFieldReference, DexField reboundFieldReference) {
-      nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
-    }
-
     public MemberRebindingLens build() {
-      return new MemberRebindingLens(appView, methodMaps, nonReboundFieldReferenceToDefinitionMap);
+      return new MemberRebindingLens(appView, methodMaps);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java
index 8a649e1..81edcbf 100644
--- a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierLens.java
@@ -56,27 +56,34 @@
   }
 
   @Override
-  @SuppressWarnings("ReferenceEquality")
   public MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
-    assert !previous.hasReboundReference();
-    DexMethod newMethod = getNextMethodSignature(previous.getReference());
+    DexMethod newReboundReference = getNextMethodSignature(previous.getReboundReference());
+    assert newReboundReference
+        .getHolderType()
+        .isIdenticalTo(previous.getReboundReference().getHolderType());
+
     InvokeType newInvokeType = previous.getType();
-    if (previous.getType() == InvokeType.DIRECT) {
-      if (publicizedPrivateInterfaceMethods.contains(newMethod)) {
+    if (previous.getType().isDirect()) {
+      if (publicizedPrivateInterfaceMethods.contains(newReboundReference)) {
         newInvokeType = InvokeType.INTERFACE;
-      } else if (publicizedPrivateVirtualMethods.contains(newMethod)) {
+      } else if (publicizedPrivateVirtualMethods.contains(newReboundReference)) {
         newInvokeType = InvokeType.VIRTUAL;
       }
     }
-    if (newInvokeType != previous.getType() || newMethod != previous.getReference()) {
-      return MethodLookupResult.builder(this)
-          .setReference(newMethod)
+
+    if (newInvokeType != previous.getType()
+        || newReboundReference.isNotIdenticalTo(previous.getReboundReference())) {
+      DexMethod newReference =
+          newReboundReference.withHolder(previous.getReference(), dexItemFactory());
+      return MethodLookupResult.builder(this, codeLens)
+          .setReboundReference(newReboundReference)
+          .setReference(newReference)
           .setPrototypeChanges(previous.getPrototypeChanges())
           .setType(newInvokeType)
           .build();
     }
-    return previous;
+    return previous.verify(this, codeLens);
   }
 
   public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
index d20c06b..a5a4a61 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -57,10 +57,9 @@
   }
 
   @Override
-  @SuppressWarnings("ReferenceEquality")
   protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
     FieldLookupResult lookupResult = super.internalDescribeLookupField(previous);
-    if (lookupResult.getReference().getType() != previous.getReference().getType()) {
+    if (lookupResult.getReference().getType().isNotIdenticalTo(previous.getReference().getType())) {
       return FieldLookupResult.builder(this)
           .setReboundReference(lookupResult.getReboundReference())
           .setReference(lookupResult.getReference())
@@ -73,15 +72,17 @@
 
   @Override
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
-      RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
-    DexMethod previous = getPreviousMethodSignature(method);
-    if (!hasPrototypeChanges(method)) {
+      RewrittenPrototypeDescription prototypeChanges,
+      DexMethod previousMethod,
+      DexMethod newMethod) {
+    DexMethod previous = getPreviousMethodSignature(newMethod);
+    if (!hasPrototypeChanges(newMethod)) {
       return prototypeChanges;
     }
     RewrittenPrototypeDescription newPrototypeChanges =
-        prototypeChanges.combine(getPrototypeChanges(method));
+        prototypeChanges.combine(getPrototypeChanges(newMethod));
     assert previous.getReturnType().isVoidType()
-        || !method.getReturnType().isVoidType()
+        || !newMethod.getReturnType().isVoidType()
         || newPrototypeChanges.hasRewrittenReturnInfo();
     return newPrototypeChanges;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java
index 6e8a445..166b08e 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingLens.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
 import java.util.Set;
 
-class BridgeHoistingLens extends DefaultNonIdentityGraphLens {
+public class BridgeHoistingLens extends DefaultNonIdentityGraphLens {
 
   // Mapping from non-hoisted bridge methods to hoisted bridge methods.
   private final BidirectionalManyToOneMap<DexMethod, DexMethod> bridgeToHoistedBridgeMap;
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraph.java b/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraph.java
index 0cbf138..8364488 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraph.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ComposableCallGraph.java
@@ -87,7 +87,8 @@
     // TODO(b/302483644): Parallelize identification of @Composable call sites.
     private void addCallEdgesToComposableFunctions() {
       // Code is fully rewritten so no need to lens rewrite in registry.
-      assert appView.codeLens() == appView.graphLens();
+      assert appView.graphLens().isMemberRebindingIdentityLens();
+      assert appView.codeLens() == appView.graphLens().asNonIdentityLens().getPrevious();
 
       for (DexProgramClass clazz : appView.appInfo().classes()) {
         clazz.forEachProgramMethodMatching(
diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
index 74e3046..dd3f5bd 100644
--- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
@@ -58,24 +58,28 @@
   }
 
   @Override
-  @SuppressWarnings("ReferenceEquality")
   protected MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
-    DexMethod methodSignature = previous.getReference();
-    DexMethod newMethodSignature = getNextMethodSignature(methodSignature);
-    if (methodSignature == newMethodSignature) {
-      return previous;
+    assert previous.hasReboundReference();
+    DexMethod previousReboundReference = previous.getReboundReference();
+    DexMethod newReboundReference = getNextMethodSignature(previousReboundReference);
+    if (newReboundReference.isIdenticalTo(previousReboundReference)) {
+      return previous.verify(this, codeLens);
     }
-    assert !previous.hasReboundReference()
-        || previous.getReference() == previous.getReboundReference();
-    return MethodLookupResult.builder(this)
+    DexMethod previousReference = previous.getReference();
+    DexMethod newReference =
+        previousReference.isIdenticalTo(previousReboundReference)
+            ? newReboundReference
+            : newReboundReference.withHolder(previousReference.getHolderType(), dexItemFactory());
+    return MethodLookupResult.builder(this, codeLens)
         .setPrototypeChanges(
             previous
                 .getPrototypeChanges()
                 .combine(
                     prototypeChanges.getOrDefault(
-                        newMethodSignature, RewrittenPrototypeDescription.none())))
-        .setReference(newMethodSignature)
+                        newReboundReference, RewrittenPrototypeDescription.none())))
+        .setReboundReference(newReboundReference)
+        .setReference(newReference)
         .setType(previous.getType())
         .build();
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
index 9de23a8..5e40a11 100644
--- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
@@ -47,11 +47,16 @@
       } while (methodMap.containsKey(newReference));
       boolean holderTypeIsInterface = interfaces.contains(newReference.getHolderType());
       if (previous.getType().isSuper() && holderTypeIsInterface) {
-        return previous;
+        return MethodLookupResult.builder(this, codeLens)
+            .setReboundReference(newReference)
+            .setReference(previous.getReference())
+            .setPrototypeChanges(previous.getPrototypeChanges())
+            .setType(previous.getType())
+            .build();
       }
-      return MethodLookupResult.builder(this)
-          .setReference(newReference)
+      return MethodLookupResult.builder(this, codeLens)
           .setReboundReference(newReference)
+          .setReference(newReference)
           .setPrototypeChanges(previous.getPrototypeChanges())
           .setType(
               holderTypeIsInterface && previous.getType().isVirtual()
@@ -59,7 +64,7 @@
                   : previous.getType())
           .build();
     }
-    return previous;
+    return previous.verify(this, codeLens);
   }
 
   public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
index 5fb872d..166b1b1 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
@@ -39,8 +39,6 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.MethodLookupResult;
-import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -94,7 +92,7 @@
       DexProgramClass source,
       DexProgramClass target) {
     this.appView = appView;
-    this.deferredRenamings = new VerticalClassMergerGraphLens.Builder(appView);
+    this.deferredRenamings = new VerticalClassMergerGraphLens.Builder();
     this.dexItemFactory = appView.dexItemFactory();
     this.lensBuilder = lensBuilder;
     this.verticallyMergedClassesBuilder = verticallyMergedClassesBuilder;
@@ -131,7 +129,7 @@
                         availableMethodSignatures.test(candidate)
                             && source.lookupVirtualMethod(candidate) == null);
             add(directMethods, resultingConstructor, MethodSignatureEquivalence.get());
-            blockRedirectionOfSuperCalls(resultingConstructor.getReference());
+            blockRedirectionOfSuperCalls(resultingConstructor);
           } else {
             DexEncodedMethod resultingDirectMethod =
                 renameMethod(
@@ -139,11 +137,8 @@
                     availableMethodSignatures,
                     definition.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
             add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
-            deferredRenamings.map(
-                directMethod.getReference(), resultingDirectMethod.getReference());
-            deferredRenamings.recordMove(
-                directMethod.getReference(), resultingDirectMethod.getReference());
-            blockRedirectionOfSuperCalls(resultingDirectMethod.getReference());
+            deferredRenamings.recordMove(directMethod.getDefinition(), resultingDirectMethod);
+            blockRedirectionOfSuperCalls(resultingDirectMethod);
 
             // Private methods in the parent class may be targeted with invoke-super if the two
             // classes are in the same nest. Ensure such calls are mapped to invoke-direct.
@@ -151,12 +146,10 @@
                 && definition.isPrivate()
                 && AccessControl.isMemberAccessible(directMethod, source, target, appView)
                     .isTrue()) {
-              deferredRenamings.mapVirtualMethodToDirectInType(
-                  directMethod.getReference(),
-                  prototypeChanges ->
-                      new MethodLookupResult(
-                          resultingDirectMethod.getReference(), null, DIRECT, prototypeChanges),
-                  target.getType());
+              // TODO(b/315283465): Add a test for correct rewriting of invoke-super to nest members
+              //  and determine if we need to record something here or not.
+              // deferredRenamings.mapVirtualMethodToDirectInType(
+              //    directMethod.getReference(), target.getType());
             }
           }
         });
@@ -168,15 +161,12 @@
           // Remove abstract/interface methods that are shadowed. The identity mapping below is
           // needed to ensure we correctly fixup the mapping in case the signature refers to
           // merged classes.
-          deferredRenamings
-              .map(virtualMethod.getReference(), shadowedBy.getReference())
-              .map(shadowedBy.getReference(), shadowedBy.getReference())
-              .recordMerge(virtualMethod.getReference(), shadowedBy.getReference());
+          deferredRenamings.recordSplit(virtualMethod, shadowedBy, null, null);
 
           // The override now corresponds to the method in the parent, so unset its synthetic flag
           // if the method in the parent is not synthetic.
           if (!virtualMethod.isSyntheticMethod() && shadowedBy.isSyntheticMethod()) {
-            shadowedBy.accessFlags.demoteFromSynthetic();
+            shadowedBy.getAccessFlags().demoteFromSynthetic();
           }
           continue;
         }
@@ -202,17 +192,14 @@
           DexEncodedMethod resultingVirtualMethod =
               renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
           resultingVirtualMethod.setLibraryMethodOverride(virtualMethod.isLibraryMethodOverride());
-          deferredRenamings.map(
-              virtualMethod.getReference(), resultingVirtualMethod.getReference());
-          deferredRenamings.recordMove(
-              virtualMethod.getReference(), resultingVirtualMethod.getReference());
+          deferredRenamings.recordMove(virtualMethod, resultingVirtualMethod);
           add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
           continue;
         }
       }
 
       DexEncodedMethod resultingMethod;
-      if (source.accessFlags.isInterface()) {
+      if (source.isInterface()) {
         // Moving a default interface method into its subtype. This method could be hit directly
         // via an invoke-super instruction from any of the transitive subtypes of this interface,
         // due to the way invoke-super works on default interface methods. In order to be able
@@ -231,16 +218,12 @@
                 resultingMethodReference, dexItemFactory);
         makeStatic(resultingMethod);
       } else {
-        // This virtual method could be called directly from a sub class via an invoke-super in-
-        // struction. Therefore, we translate this virtual method into an instance method with a
+        // This virtual method could be called directly from a sub class via an invoke-super
+        // instruction. Therefore, we translate this virtual method into an instance method with a
         // unique name, such that relevant invoke-super instructions can be rewritten to target
         // this method directly.
         resultingMethod = renameMethod(virtualMethod, availableMethodSignatures, Rename.ALWAYS);
-        if (appView.options().getProguardConfiguration().isAccessModificationAllowed()) {
-          makePublic(resultingMethod);
-        } else {
-          makePrivate(resultingMethod);
-        }
+        makePublicFinal(resultingMethod);
       }
 
       add(
@@ -250,18 +233,17 @@
 
       // Record that invoke-super instructions in the target class should be redirected to the
       // newly created direct method.
-      redirectSuperCallsInTarget(virtualMethod, resultingMethod);
-      blockRedirectionOfSuperCalls(resultingMethod.getReference());
+      redirectSuperCallsInTarget(virtualMethod);
 
+      DexEncodedMethod bridge = null;
+      DexEncodedMethod override = shadowedBy;
       if (shadowedBy == null) {
         // In addition to the newly added direct method, create a virtual method such that we do
         // not accidentally remove the method from the interface of this class.
         // Note that this method is added independently of whether it will actually be used. If
         // it turns out that the method is never used, it will be removed by the final round
         // of tree shaking.
-        shadowedBy = buildBridgeMethod(virtualMethod, resultingMethod);
-        deferredRenamings.recordCreationOfBridgeMethod(
-            virtualMethod.getReference(), shadowedBy.getReference());
+        bridge = shadowedBy = buildBridgeMethod(virtualMethod, resultingMethod);
         add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get());
       }
 
@@ -279,9 +261,7 @@
                                   .getMethodInfo(virtualMethod, source)
                                   .joiner())));
 
-      deferredRenamings.map(virtualMethod.getReference(), shadowedBy.getReference());
-      deferredRenamings.recordMove(
-          virtualMethod.getReference(), resultingMethod.getReference(), resultingMethod.isStatic());
+      deferredRenamings.recordSplit(virtualMethod, override, bridge, resultingMethod);
     }
 
     if (abortMerge) {
@@ -363,7 +343,12 @@
     source.getMethodCollection().clearVirtualMethods();
     source.clearInstanceFields();
     source.clearStaticFields();
-    // Step 5: Record merging.
+    // Step 5: Merge attributes.
+    if (source.isNestHost()) {
+      target.clearNestHost();
+      target.setNestMemberAttributes(source.getNestMembersClassAttributes());
+    }
+    // Step 6: Record merging.
     assert !abortMerge;
     assert GenericSignatureCorrectnessHelper.createForVerification(
             appView, GenericSignatureContextBuilder.createForSingleClass(appView, target))
@@ -535,11 +520,9 @@
     return synthesizedBridges;
   }
 
-  private void redirectSuperCallsInTarget(DexEncodedMethod oldTarget, DexEncodedMethod newTarget) {
+  private void redirectSuperCallsInTarget(DexEncodedMethod oldTarget) {
     DexMethod oldTargetReference = oldTarget.getReference();
-    DexMethod newTargetReference = newTarget.getReference();
-    InvokeType newTargetType = newTarget.isNonPrivateVirtualMethod() ? VIRTUAL : DIRECT;
-    if (source.accessFlags.isInterface()) {
+    if (source.isInterface()) {
       // If we merge a default interface method from interface I to its subtype C, then we need
       // to rewrite invocations on the form "invoke-super I.m()" to "invoke-direct C.m$I()".
       //
@@ -548,10 +531,7 @@
       // if I has a supertype J. This is due to the fact that invoke-super instructions that
       // resolve to a method on an interface never hit an implementation below that interface.
       deferredRenamings.mapVirtualMethodToDirectInType(
-          oldTargetReference,
-          prototypeChanges ->
-              new MethodLookupResult(newTargetReference, null, STATIC, prototypeChanges),
-          target.type);
+          oldTargetReference, oldTarget, target.getType());
     } else {
       // If we merge class B into class C, and class C contains an invocation super.m(), then it
       // is insufficient to rewrite "invoke-super B.m()" to "invoke-{direct,virtual} C.m$B()" (the
@@ -562,7 +542,7 @@
       //
       // We handle this by adding a mapping for [target] and all of its supertypes.
       DexProgramClass holder = target;
-      while (holder != null && holder.isProgramClass()) {
+      while (holder != null) {
         DexMethod signatureInHolder = oldTargetReference.withHolder(holder, dexItemFactory);
         // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
         boolean resolutionSucceeds =
@@ -570,10 +550,7 @@
                 || appView.appInfo().lookupSuperTarget(signatureInHolder, holder, appView) != null;
         if (resolutionSucceeds) {
           deferredRenamings.mapVirtualMethodToDirectInType(
-              signatureInHolder,
-              prototypeChanges ->
-                  new MethodLookupResult(newTargetReference, null, newTargetType, prototypeChanges),
-              target.type);
+              signatureInHolder, oldTarget, target.type);
         } else {
           break;
         }
@@ -588,17 +565,15 @@
           DexMethod signatureInType = oldTargetReference.withHolder(type, dexItemFactory);
           // Resolution would have succeeded if the method used to be in [type], or if one of
           // its super classes declared the method.
+          // TODO(b/315283244): Should not rely on lens for this. Instead precompute this before
+          //  merging any classes.
           boolean resolutionSucceededBeforeMerge =
               lensBuilder.hasMappingForSignatureInContext(holder, signatureInType)
                   || appView.appInfo().lookupSuperTarget(signatureInHolder, holder, appView)
                       != null;
           if (resolutionSucceededBeforeMerge) {
             deferredRenamings.mapVirtualMethodToDirectInType(
-                signatureInType,
-                prototypeChanges ->
-                    new MethodLookupResult(
-                        newTargetReference, null, newTargetType, prototypeChanges),
-                target.type);
+                signatureInType, oldTarget, target.type);
           }
         }
         holder =
@@ -609,7 +584,7 @@
     }
   }
 
-  private void blockRedirectionOfSuperCalls(DexMethod method) {
+  private void blockRedirectionOfSuperCalls(DexEncodedMethod method) {
     // We are merging a class B into C. The methods from B are being moved into C, and then we
     // subsequently rewrite the invoke-super instructions in C that hit a method in B, such that
     // they use an invoke-direct instruction instead. In this process, we need to avoid rewriting
@@ -642,8 +617,7 @@
         || invocationTarget.isNonStaticPrivateMethod();
     SynthesizedBridgeCode code =
         new SynthesizedBridgeCode(
-            newMethod,
-            invocationTarget.getReference(),
+            method.getReference(),
             invocationTarget.isStatic()
                 ? STATIC
                 : (invocationTarget.isNonPrivateVirtualMethod() ? VIRTUAL : DIRECT),
@@ -720,8 +694,8 @@
     int i = 0;
     for (DexEncodedField field : sourceFields) {
       DexEncodedField resultingField = renameFieldIfNeeded(field, availableFieldSignatures);
-      existingFieldNames.add(resultingField.getReference().name);
-      deferredRenamings.map(field.getReference(), resultingField.getReference());
+      existingFieldNames.add(resultingField.getName());
+      deferredRenamings.recordMove(field, resultingField);
       result[i] = resultingField;
       i++;
     }
@@ -759,8 +733,7 @@
     DexEncodedMethod result =
         method.toTypeSubstitutedMethodAsInlining(newSignature, dexItemFactory);
     result.getMutableOptimizationInfo().markForceInline();
-    deferredRenamings.map(method.getReference(), result.getReference());
-    deferredRenamings.recordMove(method.getReference(), result.getReference());
+    deferredRenamings.recordMove(method, result);
     // Renamed constructors turn into ordinary private functions. They can be private, as
     // they are only references from their direct subclass, which they were merged into.
     result.getAccessFlags().unsetConstructor();
@@ -840,12 +813,13 @@
     accessFlags.setPrivate();
   }
 
-  private static void makePublic(DexEncodedMethod method) {
+  private static void makePublicFinal(DexEncodedMethod method) {
     MethodAccessFlags accessFlags = method.getAccessFlags();
     assert !accessFlags.isAbstract();
     accessFlags.unsetPrivate();
     accessFlags.unsetProtected();
     accessFlags.setPublic();
+    accessFlags.setFinal();
   }
 
   private void makeStatic(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
index f984383..22316f3c 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
@@ -32,7 +32,7 @@
       AppView<AppInfoWithLiveness> appView, Set<DexProgramClass> classesToMerge) {
     this.appView = appView;
     this.classesToMerge = classesToMerge;
-    this.lensBuilder = new VerticalClassMergerGraphLens.Builder(appView);
+    this.lensBuilder = new VerticalClassMergerGraphLens.Builder();
   }
 
   public boolean isEmpty() {
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java b/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
index c71a51d..cfbc759 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
@@ -20,12 +20,14 @@
 public class IllegalAccessDetector extends UseRegistryWithResult<Boolean, ProgramMethod> {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy;
+  private final GraphLens codeLens;
 
   public IllegalAccessDetector(
       AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy,
       ProgramMethod context) {
     super(appViewWithClassHierarchy, context, false);
     this.appViewWithClassHierarchy = appViewWithClassHierarchy;
+    this.codeLens = context.getDefinition().getCode().getCodeLens(appViewWithClassHierarchy);
   }
 
   protected boolean checkFoundPackagePrivateAccess() {
@@ -43,7 +45,8 @@
   }
 
   private boolean checkFieldReference(DexField field) {
-    return checkRewrittenFieldReference(appViewWithClassHierarchy.graphLens().lookupField(field));
+    return checkRewrittenFieldReference(
+        appViewWithClassHierarchy.graphLens().lookupField(field, codeLens));
   }
 
   private boolean checkRewrittenFieldReference(DexField field) {
@@ -102,16 +105,18 @@
   }
 
   private boolean checkTypeReference(DexType type) {
-    return internalCheckTypeReference(type, appViewWithClassHierarchy.graphLens());
+    return internalCheckTypeReference(type, appViewWithClassHierarchy.graphLens(), codeLens);
   }
 
   private boolean checkRewrittenTypeReference(DexType type) {
-    return internalCheckTypeReference(type, GraphLens.getIdentityLens());
+    return internalCheckTypeReference(
+        type, GraphLens.getIdentityLens(), GraphLens.getIdentityLens());
   }
 
-  private boolean internalCheckTypeReference(DexType type, GraphLens graphLens) {
+  private boolean internalCheckTypeReference(
+      DexType type, GraphLens graphLens, GraphLens codeLens) {
     DexType baseType =
-        graphLens.lookupType(type.toBaseType(appViewWithClassHierarchy.dexItemFactory()));
+        graphLens.lookupType(type.toBaseType(appViewWithClassHierarchy.dexItemFactory()), codeLens);
     if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
       DexClass clazz = appViewWithClassHierarchy.definitionFor(baseType);
       if (clazz == null || !clazz.isPublic()) {
@@ -126,7 +131,7 @@
     if (appViewWithClassHierarchy.initClassLens().isFinal()) {
       // The InitClass lens is always rewritten up until the most recent graph lens, so first map
       // the class type to the most recent graph lens.
-      DexType rewrittenType = appViewWithClassHierarchy.graphLens().lookupType(clazz);
+      DexType rewrittenType = appViewWithClassHierarchy.graphLens().lookupType(clazz, codeLens);
       DexField initClassField =
           appViewWithClassHierarchy.initClassLens().getInitClassField(rewrittenType);
       checkRewrittenFieldReference(initClassField);
@@ -138,35 +143,35 @@
   @Override
   public void registerInvokeVirtual(DexMethod method) {
     MethodLookupResult lookup =
-        appViewWithClassHierarchy.graphLens().lookupInvokeVirtual(method, getContext());
+        appViewWithClassHierarchy.graphLens().lookupInvokeVirtual(method, getContext(), codeLens);
     checkRewrittenMethodReference(lookup.getReference(), OptionalBool.FALSE);
   }
 
   @Override
   public void registerInvokeDirect(DexMethod method) {
     MethodLookupResult lookup =
-        appViewWithClassHierarchy.graphLens().lookupInvokeDirect(method, getContext());
+        appViewWithClassHierarchy.graphLens().lookupInvokeDirect(method, getContext(), codeLens);
     checkRewrittenMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
   }
 
   @Override
   public void registerInvokeStatic(DexMethod method) {
     MethodLookupResult lookup =
-        appViewWithClassHierarchy.graphLens().lookupInvokeStatic(method, getContext());
+        appViewWithClassHierarchy.graphLens().lookupInvokeStatic(method, getContext(), codeLens);
     checkRewrittenMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
   }
 
   @Override
   public void registerInvokeInterface(DexMethod method) {
     MethodLookupResult lookup =
-        appViewWithClassHierarchy.graphLens().lookupInvokeInterface(method, getContext());
+        appViewWithClassHierarchy.graphLens().lookupInvokeInterface(method, getContext(), codeLens);
     checkRewrittenMethodReference(lookup.getReference(), OptionalBool.TRUE);
   }
 
   @Override
   public void registerInvokeSuper(DexMethod method) {
     MethodLookupResult lookup =
-        appViewWithClassHierarchy.graphLens().lookupInvokeSuper(method, getContext());
+        appViewWithClassHierarchy.graphLens().lookupInvokeSuper(method, getContext(), codeLens);
     checkRewrittenMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
   }
 
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/MergeMayLeadToNoSuchMethodErrorUseRegistry.java b/src/main/java/com/android/tools/r8/verticalclassmerging/MergeMayLeadToNoSuchMethodErrorUseRegistry.java
new file mode 100644
index 0000000..c69002e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/MergeMayLeadToNoSuchMethodErrorUseRegistry.java
@@ -0,0 +1,64 @@
+// 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.verticalclassmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.lens.MethodLookupResult;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+class MergeMayLeadToNoSuchMethodErrorUseRegistry
+    extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
+
+  private final AppView<AppInfoWithLiveness> appViewWithLiveness;
+  private final GraphLens graphLens;
+  private final GraphLens codeLens;
+  private final DexProgramClass source;
+
+  MergeMayLeadToNoSuchMethodErrorUseRegistry(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod context, DexProgramClass source) {
+    super(appView, context, Boolean.FALSE);
+    assert context.getHolder().getSuperType().isIdenticalTo(source.getType());
+    this.appViewWithLiveness = appView;
+    this.graphLens = appView.graphLens();
+    this.codeLens = context.getDefinition().getCode().getCodeLens(appView);
+    this.source = source;
+  }
+
+  boolean mayLeadToNoSuchMethodError() {
+    return getResult();
+  }
+
+  /**
+   * Sets the result of this registry to true if (1) it finds an invoke-super instruction that
+   * targets a (default) interface method and (2) the invoke-super instruction does not have a
+   * target when the context of the super class is used.
+   */
+  @Override
+  public void registerInvokeSuper(DexMethod method) {
+    MethodLookupResult lookupResult = graphLens.lookupInvokeSuper(method, getContext(), codeLens);
+    DexMethod rewrittenMethod = lookupResult.getReference();
+    MethodResolutionResult currentResolutionResult =
+        appViewWithLiveness.appInfo().unsafeResolveMethodDueToDexFormat(rewrittenMethod);
+    DexClassAndMethod currentSuperTarget =
+        currentResolutionResult.lookupInvokeSuperTarget(getContext(), appViewWithLiveness);
+    if (currentSuperTarget == null || !currentSuperTarget.getHolder().isInterface()) {
+      return;
+    }
+
+    MethodResolutionResult parentResolutionResult =
+        appViewWithLiveness.appInfo().resolveMethodOnClass(source, method);
+    DexClassAndMethod parentSuperTarget =
+        parentResolutionResult.lookupInvokeSuperTarget(source, appViewWithLiveness);
+    if (parentSuperTarget == null) {
+      setResult(true);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/SynthesizedBridgeCode.java b/src/main/java/com/android/tools/r8/verticalclassmerging/SynthesizedBridgeCode.java
index b51cd19..edffcd5 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/SynthesizedBridgeCode.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/SynthesizedBridgeCode.java
@@ -1,9 +1,11 @@
 package com.android.tools.r8.verticalclassmerging;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
@@ -13,13 +15,13 @@
 
   private DexMethod method;
   private DexMethod invocationTarget;
-  private InvokeType type;
+  private final InvokeType type;
   private final boolean isInterface;
+  private VerticalClassMergerGraphLens codeLens;
 
-  public SynthesizedBridgeCode(
-      DexMethod method, DexMethod invocationTarget, InvokeType type, boolean isInterface) {
+  public SynthesizedBridgeCode(DexMethod method, InvokeType type, boolean isInterface) {
     this.method = method;
-    this.invocationTarget = invocationTarget;
+    this.invocationTarget = null;
     this.type = type;
     this.isInterface = isInterface;
   }
@@ -44,8 +46,14 @@
   // expects to be applied to method signatures from *before* vertical class merging or *after*
   // vertical class merging).
   public void updateMethodSignatures(VerticalClassMergerGraphLens lens) {
-    method = lens.getNextMethodSignature(method);
-    invocationTarget = lens.getNextMethodSignature(invocationTarget);
+    codeLens = lens;
+    invocationTarget = lens.getNextImplementationMethodSignature(method);
+    method = lens.getNextBridgeMethodSignature(method);
+  }
+
+  @Override
+  public GraphLens getCodeLens(AppView<?> appView) {
+    return codeLens;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
index fc20047..45f4ca8 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.classmerging.SyntheticArgumentClass;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -24,6 +25,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 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.Timing.TimingMerger;
@@ -32,6 +34,7 @@
 import com.google.common.collect.Streams;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -166,15 +169,7 @@
             .computeStronglyConnectedComponents();
 
     // Remove singleton class hierarchies as they are not subject to vertical class merging.
-    Set<DexProgramClass> singletonComponents = Sets.newIdentityHashSet();
-    connectedComponents.removeIf(
-        connectedComponent -> {
-          if (connectedComponent.size() == 1) {
-            singletonComponents.addAll(connectedComponent);
-            return true;
-          }
-          return false;
-        });
+    connectedComponents.removeIf(connectedComponent -> connectedComponent.size() == 1);
     timing.end();
 
     // Apply class merging concurrently in disjoint class hierarchies.
@@ -185,13 +180,13 @@
     if (verticalClassMergerResult.isEmpty()) {
       return;
     }
-
+    ProfileCollectionAdditions profileCollectionAdditions =
+        ProfileCollectionAdditions.create(appView);
     VerticalClassMergerGraphLens lens =
-        new VerticalClassMergerTreeFixer(appView, immediateSubtypingInfo, verticalClassMergerResult)
-            .run(connectedComponents, singletonComponents, executorService, timing);
+        runFixup(profileCollectionAdditions, verticalClassMergerResult, executorService, timing);
     updateKeepInfoForMergedClasses(verticalClassMergerResult);
     assert verifyGraphLens(lens, verticalClassMergerResult);
-    updateArtProfiles(lens, verticalClassMergerResult);
+    updateArtProfiles(profileCollectionAdditions, lens, verticalClassMergerResult);
     appView.rewriteWithLens(lens, executorService, timing);
     updateKeepInfoForSynthesizedBridges(verticalClassMergerResult);
     appView.notifyOptimizationFinishedForTesting();
@@ -270,12 +265,37 @@
     return verticalClassMergerResult.build();
   }
 
+  private VerticalClassMergerGraphLens runFixup(
+      ProfileCollectionAdditions profileCollectionAdditions,
+      VerticalClassMergerResult verticalClassMergerResult,
+      ExecutorService executorService,
+      Timing timing)
+      throws ExecutionException {
+    DexProgramClass deterministicContext =
+        appView
+            .definitionFor(
+                ListUtils.first(
+                    ListUtils.sort(
+                        verticalClassMergerResult.getVerticallyMergedClasses().getTargets(),
+                        Comparator.naturalOrder())))
+            .asProgramClass();
+    SyntheticArgumentClass syntheticArgumentClass =
+        new SyntheticArgumentClass.Builder(appView).build(deterministicContext);
+    VerticalClassMergerGraphLens lens =
+        new VerticalClassMergerTreeFixer(
+                appView,
+                profileCollectionAdditions,
+                syntheticArgumentClass,
+                verticalClassMergerResult)
+            .run(executorService, timing);
+    return lens;
+  }
+
   private void updateArtProfiles(
+      ProfileCollectionAdditions profileCollectionAdditions,
       VerticalClassMergerGraphLens verticalClassMergerLens,
       VerticalClassMergerResult verticalClassMergerResult) {
     // Include bridges in art profiles.
-    ProfileCollectionAdditions profileCollectionAdditions =
-        ProfileCollectionAdditions.create(appView);
     if (!profileCollectionAdditions.isNop()) {
       List<SynthesizedBridgeCode> synthesizedBridges =
           verticalClassMergerResult.getSynthesizedBridges();
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
index 1d30b3c..3ccd921 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
@@ -4,14 +4,15 @@
 
 package com.android.tools.r8.verticalclassmerging;
 
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
 import com.android.tools.r8.classmerging.ClassMergerGraphLens;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
+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.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.lens.MethodLookupResult;
@@ -19,21 +20,23 @@
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.InvokeType;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 // This graph lens is instantiated during vertical class merging. The graph lens is context
@@ -62,35 +65,41 @@
 // For the invocation "invoke-virtual A.m()" in B.m2, this graph lens will return the method B.m.
 public class VerticalClassMergerGraphLens extends ClassMergerGraphLens {
 
-  public interface GraphLensLookupResultProvider {
-
-    MethodLookupResult get(RewrittenPrototypeDescription prototypeChanges);
-  }
-
   private final VerticallyMergedClasses mergedClasses;
-  private final Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
-      contextualVirtualToDirectMethodMaps;
+  private final Map<DexType, Map<DexMethod, DexMethod>> contextualSuperToImplementationInContexts;
+  private final BidirectionalOneToOneMap<DexMethod, DexMethod> extraNewMethodSignatures;
+
   private final Set<DexMethod> mergedMethods;
-  private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
-  private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges;
+  private final Set<DexMethod> staticizedMethods;
 
   private VerticalClassMergerGraphLens(
       AppView<?> appView,
       VerticallyMergedClasses mergedClasses,
       BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
-      Map<DexMethod, DexMethod> methodMap,
-      Set<DexMethod> mergedMethods,
-      Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
-          contextualVirtualToDirectMethodMaps,
+      Map<DexType, Map<DexMethod, DexMethod>> contextualSuperToImplementationInContexts,
       BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> newMethodSignatures,
-      Map<DexMethod, DexMethod> originalMethodSignaturesForBridges,
-      Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges) {
-    super(appView, fieldMap, methodMap, mergedClasses.getBidirectionalMap(), newMethodSignatures);
+      BidirectionalOneToOneMap<DexMethod, DexMethod> extraNewMethodSignatures,
+      Set<DexMethod> mergedMethods,
+      Set<DexMethod> staticizedMethods) {
+    super(
+        appView,
+        fieldMap,
+        Collections.emptyMap(),
+        mergedClasses.getBidirectionalMap(),
+        newMethodSignatures);
     this.mergedClasses = mergedClasses;
-    this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
+    this.contextualSuperToImplementationInContexts = contextualSuperToImplementationInContexts;
+    this.extraNewMethodSignatures = extraNewMethodSignatures;
     this.mergedMethods = mergedMethods;
-    this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges;
-    this.prototypeChanges = prototypeChanges;
+    this.staticizedMethods = staticizedMethods;
+  }
+
+  private boolean isMerged(DexMethod method) {
+    return mergedMethods.contains(method);
+  }
+
+  private boolean isStaticized(DexMethod method) {
+    return staticizedMethods.contains(method);
   }
 
   @Override
@@ -104,6 +113,39 @@
   }
 
   @Override
+  public DexField getNextFieldSignature(DexField previous) {
+    DexField field = super.getNextFieldSignature(previous);
+    assert field.verifyReferencedBaseTypesMatches(
+        type -> !mergedClasses.isMergeSource(type), dexItemFactory());
+    return field;
+  }
+
+  @Override
+  public DexMethod getNextMethodSignature(DexMethod previous) {
+    if (extraNewMethodSignatures.containsKey(previous)) {
+      return getNextImplementationMethodSignature(previous);
+    }
+    DexMethod method = super.getNextMethodSignature(previous);
+    assert method.verifyReferencedBaseTypesMatches(
+        type -> !mergedClasses.isMergeSource(type), dexItemFactory());
+    return method;
+  }
+
+  public DexMethod getNextBridgeMethodSignature(DexMethod previous) {
+    DexMethod method = newMethodSignatures.getRepresentativeValueOrDefault(previous, previous);
+    assert method.verifyReferencedBaseTypesMatches(
+        type -> !mergedClasses.isMergeSource(type), dexItemFactory());
+    return method;
+  }
+
+  public DexMethod getNextImplementationMethodSignature(DexMethod previous) {
+    DexMethod method = extraNewMethodSignatures.getRepresentativeValueOrDefault(previous, previous);
+    assert method.verifyReferencedBaseTypesMatches(
+        type -> !mergedClasses.isMergeSource(type), dexItemFactory());
+    return method;
+  }
+
+  @Override
   protected Iterable<DexType> internalGetOriginalTypes(DexType previous) {
     Collection<DexType> originalTypes = mergedClasses.getSourcesFor(previous);
     Iterable<DexType> currentType = IterableUtils.singleton(previous);
@@ -114,57 +156,111 @@
   }
 
   @Override
+  protected MethodLookupResult internalLookupMethod(
+      DexMethod reference,
+      DexMethod context,
+      InvokeType type,
+      GraphLens codeLens,
+      LookupMethodContinuation continuation) {
+    if (this == codeLens) {
+      MethodLookupResult lookupResult =
+          MethodLookupResult.builder(this, codeLens)
+              .setReboundReference(reference)
+              .setReference(reference)
+              .setType(type)
+              .build();
+      return continuation.lookupMethod(lookupResult);
+    }
+    return super.internalLookupMethod(reference, context, type, codeLens, continuation);
+  }
+
+  @Override
   public MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context, GraphLens codeLens) {
     assert context != null || verifyIsContextFreeForMethod(previous.getReference(), codeLens);
     assert context == null || previous.getType() != null;
-    if (previous.getType() == InvokeType.SUPER && !mergedMethods.contains(context)) {
-      Map<DexMethod, GraphLensLookupResultProvider> virtualToDirectMethodMap =
-          contextualVirtualToDirectMethodMaps.get(context.getHolderType());
-      if (virtualToDirectMethodMap != null) {
-        GraphLensLookupResultProvider result =
-            virtualToDirectMethodMap.get(previous.getReference());
-        if (result != null) {
-          // If the super class A of the enclosing class B (i.e., context.holder())
-          // has been merged into B during vertical class merging, and this invoke-super instruction
-          // was resolving to a method in A, then the target method has been changed to a direct
-          // method and moved into B, so that we need to use an invoke-direct instruction instead of
-          // invoke-super (or invoke-static, if the method was originally a default interface
-          // method).
-          return result.get(previous.getPrototypeChanges());
-        }
-      }
-    }
+    assert previous.hasReboundReference();
     MethodLookupResult lookupResult;
-    DexMethod newMethod = methodMap.apply(previous.getReference());
-    if (newMethod == null) {
-      lookupResult = previous;
-    } else {
+    DexMethod implementationReference = getImplementationTargetForInvokeSuper(previous, context);
+    if (implementationReference != null) {
       lookupResult =
-          MethodLookupResult.builder(this)
-              .setReference(newMethod)
+          MethodLookupResult.builder(this, codeLens)
+              .setReboundReference(implementationReference)
+              .setReference(implementationReference)
               .setPrototypeChanges(
-                  internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod))
-              .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
+                  internalDescribePrototypeChanges(
+                      previous.getPrototypeChanges(),
+                      previous.getReboundReference(),
+                      implementationReference))
+              .setType(
+                  isStaticized(implementationReference) ? InvokeType.STATIC : InvokeType.VIRTUAL)
+              .build();
+    } else {
+      DexMethod newReboundReference = previous.getRewrittenReboundReference(newMethodSignatures);
+      assert newReboundReference.verifyReferencedBaseTypesMatches(
+          type -> !mergedClasses.isMergeSource(type), dexItemFactory());
+      DexMethod newReference =
+          previous.getRewrittenReferenceFromRewrittenReboundReference(
+              newReboundReference, this::getNextClassType, dexItemFactory());
+      lookupResult =
+          MethodLookupResult.builder(this, codeLens)
+              .setReboundReference(newReboundReference)
+              .setReference(newReference)
+              .setType(mapInvocationType(newReference, previous.getReference(), previous.getType()))
+              .setPrototypeChanges(
+                  internalDescribePrototypeChanges(
+                      previous.getPrototypeChanges(),
+                      previous.getReboundReference(),
+                      newReboundReference))
               .build();
     }
     assert !appView.testing().enableVerticalClassMergerLensAssertion
         || Streams.stream(lookupResult.getReference().getReferencedBaseTypes(dexItemFactory()))
-            .noneMatch(type -> mergedClasses.hasBeenMergedIntoSubtype(type));
+            .noneMatch(mergedClasses::hasBeenMergedIntoSubtype);
     return lookupResult;
   }
 
+  private DexMethod getImplementationTargetForInvokeSuper(
+      MethodLookupResult previous, DexMethod context) {
+    if (previous.getType().isSuper() && !isMerged(context)) {
+      return contextualSuperToImplementationInContexts
+          .getOrDefault(context.getHolderType(), Collections.emptyMap())
+          .get(previous.getReference());
+    }
+    return null;
+  }
+
   @Override
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
-      RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
-    return prototypeChanges.combine(
-        this.prototypeChanges.getOrDefault(method, RewrittenPrototypeDescription.none()));
+      RewrittenPrototypeDescription prototypeChanges,
+      DexMethod previousMethod,
+      DexMethod newMethod) {
+    if (isStaticized(newMethod)) {
+      // The receiver has been added as an explicit argument.
+      assert newMethod.getArity() == previousMethod.getArity() + 1;
+      RewrittenPrototypeDescription isConvertedToStaticMethod =
+          RewrittenPrototypeDescription.createForArgumentsInfo(
+              ArgumentInfoCollection.builder()
+                  .setArgumentInfosSize(newMethod.getParameters().size())
+                  .setIsConvertedToStaticMethod()
+                  .build());
+      return prototypeChanges.combine(isConvertedToStaticMethod);
+    }
+    if (newMethod.getArity() > previousMethod.getArity()) {
+      assert dexItemFactory().isConstructor(previousMethod);
+      RewrittenPrototypeDescription collisionResolution =
+          RewrittenPrototypeDescription.createForExtraParameters(
+              ExtraUnusedNullParameter.computeExtraUnusedNullParameters(previousMethod, newMethod));
+      return prototypeChanges.combine(collisionResolution);
+    }
+    assert newMethod.getArity() == previousMethod.getArity();
+    return prototypeChanges;
   }
 
   @Override
   public DexMethod getPreviousMethodSignature(DexMethod method) {
     return super.getPreviousMethodSignature(
-        originalMethodSignaturesForBridges.getOrDefault(method, method));
+        extraNewMethodSignatures.getRepresentativeKeyOrDefault(method, method));
   }
 
   @Override
@@ -175,8 +271,16 @@
 
   @Override
   protected InvokeType mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, InvokeType type) {
-    return mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
+      DexMethod newMethod, DexMethod previousMethod, InvokeType type) {
+    if (isStaticized(newMethod)) {
+      return InvokeType.STATIC;
+    }
+    if (type.isInterface()
+        && mergedClasses.hasInterfaceBeenMergedIntoClass(
+            previousMethod.getHolderType(), newMethod.getHolderType())) {
+      return InvokeType.VIRTUAL;
+    }
+    return type;
   }
 
   @Override
@@ -184,7 +288,7 @@
     if (codeLens == this) {
       return true;
     }
-    return contextualVirtualToDirectMethodMaps.isEmpty()
+    return contextualSuperToImplementationInContexts.isEmpty()
         && getPrevious().isContextFreeForMethods(codeLens);
   }
 
@@ -195,7 +299,7 @@
     }
     assert getPrevious().verifyIsContextFreeForMethod(method, codeLens);
     DexMethod previous = getPrevious().lookupMethod(method, null, null, codeLens).getReference();
-    assert contextualVirtualToDirectMethodMaps.values().stream()
+    assert contextualSuperToImplementationInContexts.values().stream()
         .noneMatch(virtualToDirectMethodMap -> virtualToDirectMethodMap.containsKey(previous));
     return true;
   }
@@ -203,115 +307,91 @@
   public static class Builder
       extends BuilderBase<VerticalClassMergerGraphLens, VerticallyMergedClasses> {
 
-    private final AppView<AppInfoWithLiveness> appView;
-    private final DexItemFactory dexItemFactory;
-
-    protected final MutableBidirectionalOneToOneMap<DexField, DexField> fieldMap =
+    protected final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures =
         new BidirectionalOneToOneHashMap<>();
-    protected final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
-    private final ImmutableSet.Builder<DexMethod> mergedMethodsBuilder = ImmutableSet.builder();
-    private final Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
-        contextualVirtualToDirectMethodMaps = new IdentityHashMap<>();
+    private final Map<DexType, Map<DexMethod, DexMethod>>
+        contextualSuperToImplementationInContexts = new IdentityHashMap<>();
 
     private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod>
         newMethodSignatures = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
-    private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
-        new IdentityHashMap<>();
-    private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges =
-        new IdentityHashMap<>();
+    private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> extraNewMethodSignatures =
+        new BidirectionalOneToOneHashMap<>();
 
-    private final Map<DexProto, DexProto> cache = new IdentityHashMap<>();
-
-    Builder(AppView<AppInfoWithLiveness> appView) {
-      this.appView = appView;
-      this.dexItemFactory = appView.dexItemFactory();
-    }
+    private final Set<DexMethod> mergedMethods = Sets.newIdentityHashSet();
+    private final Set<DexMethod> staticizedMethods = Sets.newIdentityHashSet();
 
     static Builder createBuilderForFixup(VerticalClassMergerResult verticalClassMergerResult) {
-      Builder builder = verticalClassMergerResult.getLensBuilder();
-      VerticallyMergedClasses mergedClasses =
-          verticalClassMergerResult.getVerticallyMergedClasses();
-      Builder newBuilder = new Builder(builder.appView);
-      builder.fieldMap.forEach(
-          (key, value) ->
-              newBuilder.map(
-                  key, builder.getFieldSignatureAfterClassMerging(value, mergedClasses)));
-      for (Map.Entry<DexMethod, DexMethod> entry : builder.methodMap.entrySet()) {
-        newBuilder.map(
-            entry.getKey(),
-            builder.getMethodSignatureAfterClassMerging(entry.getValue(), mergedClasses));
-      }
-      for (DexMethod method : builder.mergedMethodsBuilder.build()) {
-        newBuilder.markMethodAsMerged(
-            builder.getMethodSignatureAfterClassMerging(method, mergedClasses));
-      }
-      for (Map.Entry<DexType, Map<DexMethod, GraphLensLookupResultProvider>> entry :
-          builder.contextualVirtualToDirectMethodMaps.entrySet()) {
-        DexType context = entry.getKey();
-        assert context.isIdenticalTo(builder.getTypeAfterClassMerging(context, mergedClasses));
-        for (Map.Entry<DexMethod, GraphLensLookupResultProvider> innerEntry :
-            entry.getValue().entrySet()) {
-          DexMethod from = innerEntry.getKey();
-          MethodLookupResult rewriting =
-              innerEntry.getValue().get(RewrittenPrototypeDescription.none());
-          DexMethod to =
-              builder.getMethodSignatureAfterClassMerging(rewriting.getReference(), mergedClasses);
-          newBuilder.mapVirtualMethodToDirectInType(
-              from,
-              prototypeChanges ->
-                  new MethodLookupResult(to, null, rewriting.getType(), prototypeChanges),
-              context);
-        }
-      }
-      builder.newMethodSignatures.forEachManyToOneMapping(
-          (originalMethodSignatures, renamedMethodSignature, representative) -> {
-            DexMethod methodSignatureAfterClassMerging =
-                builder.getMethodSignatureAfterClassMerging(renamedMethodSignature, mergedClasses);
-            newBuilder.newMethodSignatures.put(
-                originalMethodSignatures, methodSignatureAfterClassMerging);
-            if (originalMethodSignatures.size() > 1) {
-              newBuilder.newMethodSignatures.setRepresentative(
-                  methodSignatureAfterClassMerging, representative);
-            }
-          });
-      for (Map.Entry<DexMethod, DexMethod> entry :
-          builder.originalMethodSignaturesForBridges.entrySet()) {
-        newBuilder.recordCreationOfBridgeMethod(
-            entry.getValue(),
-            builder.getMethodSignatureAfterClassMerging(entry.getKey(), mergedClasses));
-      }
-      builder.prototypeChanges.forEach(
-          (method, prototypeChangesForMethod) ->
-              newBuilder.prototypeChanges.put(
-                  builder.getMethodSignatureAfterClassMerging(method, mergedClasses),
-                  prototypeChangesForMethod));
-      return newBuilder;
+      return verticalClassMergerResult.getLensBuilder();
     }
 
     @Override
     public void addExtraParameters(
-        DexMethod methodSignature, List<? extends ExtraParameter> extraParameters) {
-      throw new Unimplemented();
+        DexMethod from, DexMethod to, List<? extends ExtraParameter> extraParameters) {
+      // Intentionally empty.
     }
 
     @Override
     public void commitPendingUpdates() {
-      throw new Unimplemented();
+      // Intentionally empty.
     }
 
     @Override
-    public void fixupField(DexField from, DexField to) {
-      throw new Unimplemented();
+    public void fixupField(DexField oldFieldSignature, DexField newFieldSignature) {
+      DexField originalFieldSignature =
+          newFieldSignatures.getKeyOrDefault(oldFieldSignature, oldFieldSignature);
+      newFieldSignatures.put(originalFieldSignature, newFieldSignature);
     }
 
     @Override
-    public void fixupMethod(DexMethod from, DexMethod to) {
-      throw new Unimplemented();
+    public void fixupMethod(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
+      if (extraNewMethodSignatures.containsValue(oldMethodSignature)) {
+        DexMethod originalMethodSignature = extraNewMethodSignatures.getKey(oldMethodSignature);
+        extraNewMethodSignatures.put(originalMethodSignature, newMethodSignature);
+      } else {
+        Set<DexMethod> oldMethodSignatures = newMethodSignatures.getKeys(oldMethodSignature);
+        if (oldMethodSignatures.isEmpty()) {
+          newMethodSignatures.put(oldMethodSignature, newMethodSignature);
+        } else {
+          DexMethod representative = newMethodSignatures.getRepresentativeKey(oldMethodSignature);
+          newMethodSignatures.removeValue(oldMethodSignature);
+          newMethodSignatures.put(oldMethodSignatures, newMethodSignature);
+          if (representative != null) {
+            newMethodSignatures.setRepresentative(newMethodSignature, representative);
+          }
+        }
+      }
+
+      if (mergedMethods.remove(oldMethodSignature)) {
+        mergedMethods.add(newMethodSignature);
+      }
+
+      if (staticizedMethods.remove(oldMethodSignature)) {
+        staticizedMethods.add(newMethodSignature);
+      }
+    }
+
+    public void fixupContextualVirtualToDirectMethodMaps() {
+      for (Entry<DexType, Map<DexMethod, DexMethod>> entry :
+          contextualSuperToImplementationInContexts.entrySet()) {
+        for (Entry<DexMethod, DexMethod> innerEntry : entry.getValue().entrySet()) {
+          DexMethod virtualMethod = innerEntry.getValue();
+          DexMethod implementationMethod = extraNewMethodSignatures.get(virtualMethod);
+          assert implementationMethod != null;
+          innerEntry.setValue(implementationMethod);
+        }
+      }
     }
 
     @Override
     public Set<DexMethod> getOriginalMethodReferences(DexMethod method) {
-      throw new Unimplemented();
+      if (extraNewMethodSignatures.containsValue(method)) {
+        return Set.of(extraNewMethodSignatures.getKey(method));
+      }
+      Set<DexMethod> previousMethodSignatures = newMethodSignatures.getKeys(method);
+      if (!previousMethodSignatures.isEmpty()) {
+        return previousMethodSignatures;
+      }
+      return Set.of(method);
     }
 
     @Override
@@ -322,132 +402,70 @@
       return new VerticalClassMergerGraphLens(
           appView,
           mergedClasses,
-          fieldMap,
-          methodMap,
-          mergedMethodsBuilder.build(),
-          contextualVirtualToDirectMethodMaps,
+          newFieldSignatures,
+          contextualSuperToImplementationInContexts,
           newMethodSignatures,
-          originalMethodSignaturesForBridges,
-          prototypeChanges);
+          extraNewMethodSignatures,
+          mergedMethods,
+          staticizedMethods);
     }
 
-    private DexField getFieldSignatureAfterClassMerging(
-        DexField field, VerticallyMergedClasses mergedClasses) {
-      assert !field.getHolderType().isArrayType();
-
-      DexType holder = field.getHolderType();
-      DexType newHolder = mergedClasses.getMergeTargetOrDefault(holder, holder);
-
-      DexType type = field.getType();
-      DexType newType = getTypeAfterClassMerging(type, mergedClasses);
-
-      if (holder.isIdenticalTo(newHolder) && type.isIdenticalTo(newType)) {
-        return field;
-      }
-      return dexItemFactory.createField(newHolder, newType, field.getName());
-    }
-
-    private DexMethod getMethodSignatureAfterClassMerging(
-        DexMethod signature, VerticallyMergedClasses mergedClasses) {
-      assert !signature.getHolderType().isArrayType();
-
-      DexType holder = signature.getHolderType();
-      DexType newHolder = mergedClasses.getMergeTargetOrDefault(holder, holder);
-
-      DexProto proto = signature.getProto();
-      DexProto newProto =
-          dexItemFactory.applyClassMappingToProto(
-              proto, type -> getTypeAfterClassMerging(type, mergedClasses), cache);
-
-      if (holder.isIdenticalTo(newHolder) && proto.isIdenticalTo(newProto)) {
-        return signature;
-      }
-      return dexItemFactory.createMethod(newHolder, newProto, signature.getName());
-    }
-
-    private DexType getTypeAfterClassMerging(DexType type, VerticallyMergedClasses mergedClasses) {
-      if (type.isArrayType()) {
-        DexType baseType = type.toBaseType(dexItemFactory);
-        DexType newBaseType = mergedClasses.getMergeTargetOrDefault(baseType, baseType);
-        if (newBaseType.isNotIdenticalTo(baseType)) {
-          return type.replaceBaseType(newBaseType, dexItemFactory);
-        }
-        return type;
-      }
-      return mergedClasses.getMergeTargetOrDefault(type, type);
-    }
-
+    // TODO: should be removed.
     public boolean hasMappingForSignatureInContext(DexProgramClass context, DexMethod signature) {
-      Map<DexMethod, GraphLensLookupResultProvider> virtualToDirectMethodMap =
-          contextualVirtualToDirectMethodMaps.get(context.type);
-      if (virtualToDirectMethodMap != null) {
-        return virtualToDirectMethodMap.containsKey(signature);
+      return contextualSuperToImplementationInContexts
+          .getOrDefault(context.getType(), Collections.emptyMap())
+          .containsKey(signature);
+    }
+
+    public void markMethodAsMerged(DexEncodedMethod method) {
+      mergedMethods.add(method.getReference());
+    }
+
+    public void recordMove(DexEncodedField from, DexEncodedField to) {
+      newFieldSignatures.put(from.getReference(), to.getReference());
+    }
+
+    public void recordMove(DexEncodedMethod from, DexEncodedMethod to) {
+      newMethodSignatures.put(from.getReference(), to.getReference());
+    }
+
+    public void recordSplit(
+        DexEncodedMethod from,
+        DexEncodedMethod override,
+        DexEncodedMethod bridge,
+        DexEncodedMethod implementation) {
+      if (override != null) {
+        assert bridge == null;
+        newMethodSignatures.put(from.getReference(), override.getReference());
+        newMethodSignatures.put(override.getReference(), override.getReference());
+        newMethodSignatures.setRepresentative(override.getReference(), override.getReference());
+      } else {
+        assert bridge != null;
+        newMethodSignatures.put(from.getReference(), bridge.getReference());
       }
-      return false;
-    }
 
-    public boolean hasOriginalSignatureMappingFor(DexField field) {
-      return fieldMap.containsValue(field);
-    }
+      if (implementation == null) {
+        assert from.isAbstract();
+        return;
+      }
 
-    public boolean hasOriginalSignatureMappingFor(DexMethod method) {
-      return newMethodSignatures.containsValue(method)
-          || originalMethodSignaturesForBridges.containsKey(method);
-    }
+      extraNewMethodSignatures.put(from.getReference(), implementation.getReference());
+      mergedMethods.add(implementation.getReference());
 
-    public void markMethodAsMerged(DexMethod method) {
-      mergedMethodsBuilder.add(method);
-    }
-
-    public void map(DexField from, DexField to) {
-      fieldMap.put(from, to);
-    }
-
-    public Builder map(DexMethod from, DexMethod to) {
-      methodMap.put(from, to);
-      return this;
-    }
-
-    public void recordMerge(DexMethod from, DexMethod to) {
-      newMethodSignatures.put(from, to);
-      newMethodSignatures.put(to, to);
-      newMethodSignatures.setRepresentative(to, to);
-    }
-
-    public void recordMove(DexMethod from, DexMethod to) {
-      recordMove(from, to, false);
-    }
-
-    public void recordMove(DexMethod from, DexMethod to, boolean isStaticized) {
-      newMethodSignatures.put(from, to);
-      if (isStaticized) {
-        RewrittenPrototypeDescription prototypeChangesForMethod =
-            RewrittenPrototypeDescription.create(
-                ImmutableList.of(),
-                null,
-                ArgumentInfoCollection.builder()
-                    .setArgumentInfosSize(to.getParameters().size())
-                    .setIsConvertedToStaticMethod()
-                    .build());
-        prototypeChanges.put(to, prototypeChangesForMethod);
+      if (implementation.isStatic()) {
+        staticizedMethods.add(implementation.getReference());
       }
     }
 
-    public void recordCreationOfBridgeMethod(DexMethod from, DexMethod to) {
-      originalMethodSignaturesForBridges.put(to, from);
-    }
-
-    public void mapVirtualMethodToDirectInType(
-        DexMethod from, GraphLensLookupResultProvider to, DexType type) {
-      Map<DexMethod, GraphLensLookupResultProvider> virtualToDirectMethodMap =
-          contextualVirtualToDirectMethodMaps.computeIfAbsent(type, key -> new IdentityHashMap<>());
-      virtualToDirectMethodMap.put(from, to);
+    public void mapVirtualMethodToDirectInType(DexMethod from, DexEncodedMethod to, DexType type) {
+      contextualSuperToImplementationInContexts
+          .computeIfAbsent(type, ignoreKey(IdentityHashMap::new))
+          .put(from, to.getReference());
     }
 
     public void merge(VerticalClassMergerGraphLens.Builder builder) {
-      fieldMap.putAll(builder.fieldMap);
-      methodMap.putAll(builder.methodMap);
-      mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
+      newFieldSignatures.putAll(builder.newFieldSignatures);
+      mergedMethods.addAll(builder.mergedMethods);
       builder.newMethodSignatures.forEachManyToOneMapping(
           (keys, value, representative) -> {
             boolean isRemapping =
@@ -482,19 +500,13 @@
               }
             }
           });
-      prototypeChanges.putAll(builder.prototypeChanges);
-      originalMethodSignaturesForBridges.putAll(builder.originalMethodSignaturesForBridges);
-      for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
-        Map<DexMethod, GraphLensLookupResultProvider> current =
-            contextualVirtualToDirectMethodMaps.get(context);
-        Map<DexMethod, GraphLensLookupResultProvider> other =
-            builder.contextualVirtualToDirectMethodMaps.get(context);
-        if (current != null) {
-          current.putAll(other);
-        } else {
-          contextualVirtualToDirectMethodMaps.put(context, other);
-        }
-      }
+      staticizedMethods.addAll(builder.staticizedMethods);
+      extraNewMethodSignatures.putAll(builder.extraNewMethodSignatures);
+      builder.contextualSuperToImplementationInContexts.forEach(
+          (key, value) ->
+              contextualSuperToImplementationInContexts
+                  .computeIfAbsent(key, ignoreKey(IdentityHashMap::new))
+                  .putAll(value));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
index 45796d3..983077e 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
@@ -70,7 +70,8 @@
       if (!isStillMergeCandidate(sourceClass, targetClass)) {
         continue;
       }
-      if (mergeMayLeadToIllegalAccesses(sourceClass, targetClass)) {
+      if (mergeMayLeadToIllegalAccesses(sourceClass, targetClass)
+          || mergeMayLeadToNoSuchMethodError(sourceClass, targetClass)) {
         continue;
       }
       mergeCandidates.add(sourceClass);
@@ -328,10 +329,35 @@
                 return TraversalContinuation.doBreak();
               }
               return TraversalContinuation.doContinue();
-            });
+            },
+            DexEncodedMethod::hasCode);
     return result.shouldBreak();
   }
 
+  // TODO: maybe skip this check if target does not implement any interfaces (directly or
+  // indirectly)?
+  private boolean mergeMayLeadToNoSuchMethodError(DexProgramClass source, DexProgramClass target) {
+    // This only returns true when an invoke-super instruction is found that targets a default
+    // interface method.
+    if (!options.canUseDefaultAndStaticInterfaceMethods()) {
+      return false;
+    }
+    // This problem may only arise when merging (non-interface) classes into classes.
+    if (source.isInterface() || target.isInterface()) {
+      return false;
+    }
+    return target
+        .traverseProgramMethods(
+            method -> {
+              MergeMayLeadToNoSuchMethodErrorUseRegistry registry =
+                  new MergeMayLeadToNoSuchMethodErrorUseRegistry(appView, method, source);
+              method.registerCodeReferencesWithResult(registry);
+              return TraversalContinuation.breakIf(registry.mayLeadToNoSuchMethodError());
+            },
+            DexEncodedMethod::hasCode)
+        .shouldBreak();
+  }
+
   private boolean methodResolutionMayChange(DexProgramClass source, DexProgramClass target) {
     for (DexEncodedMethod virtualSourceMethod : source.virtualMethods()) {
       DexEncodedMethod directTargetMethod =
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerResult.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerResult.java
index a98e6c3..390131a 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerResult.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerResult.java
@@ -58,7 +58,7 @@
 
     Builder(AppView<AppInfoWithLiveness> appView) {
       this(
-          new VerticalClassMergerGraphLens.Builder(appView),
+          new VerticalClassMergerGraphLens.Builder(),
           new ArrayList<>(),
           VerticallyMergedClasses.builder());
     }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
index d424f66..6b9955c 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
@@ -3,104 +3,55 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.verticalclassmerging;
 
-import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.classmerging.ClassMergerTreeFixer;
+import com.android.tools.r8.classmerging.SyntheticArgumentClass;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
-import com.android.tools.r8.graph.fixup.TreeFixerBase;
-import com.android.tools.r8.shaking.AnnotationFixer;
+import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Timing;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
-class VerticalClassMergerTreeFixer extends TreeFixerBase {
+class VerticalClassMergerTreeFixer
+    extends ClassMergerTreeFixer<
+        VerticalClassMergerGraphLens.Builder,
+        VerticalClassMergerGraphLens,
+        VerticallyMergedClasses> {
 
-  private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
-  private final VerticalClassMergerGraphLens.Builder lensBuilder;
-  private final VerticallyMergedClasses mergedClasses;
   private final List<SynthesizedBridgeCode> synthesizedBridges;
 
   VerticalClassMergerTreeFixer(
       AppView<AppInfoWithLiveness> appView,
-      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+      ProfileCollectionAdditions profileCollectionAdditions,
+      SyntheticArgumentClass syntheticArgumentClass,
       VerticalClassMergerResult verticalClassMergerResult) {
-    super(appView);
-    this.immediateSubtypingInfo = immediateSubtypingInfo;
-    this.lensBuilder =
-        VerticalClassMergerGraphLens.Builder.createBuilderForFixup(verticalClassMergerResult);
-    this.mergedClasses = verticalClassMergerResult.getVerticallyMergedClasses();
+    super(
+        appView,
+        VerticalClassMergerGraphLens.Builder.createBuilderForFixup(verticalClassMergerResult),
+        verticalClassMergerResult.getVerticallyMergedClasses(),
+        profileCollectionAdditions,
+        syntheticArgumentClass);
     this.synthesizedBridges = verticalClassMergerResult.getSynthesizedBridges();
   }
 
-  VerticalClassMergerGraphLens run(
-      List<Set<DexProgramClass>> connectedComponents,
-      Set<DexProgramClass> singletonComponents,
-      ExecutorService executorService,
-      Timing timing)
+  @Override
+  public VerticalClassMergerGraphLens run(ExecutorService executorService, Timing timing)
       throws ExecutionException {
-    timing.begin("Fixup");
-    // Globally substitute merged class types in protos and holders.
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      clazz.getMethodCollection().replaceMethods(this::fixupMethod);
-      clazz.setStaticFields(fixupFields(clazz.staticFields()));
-      clazz.setInstanceFields(fixupFields(clazz.instanceFields()));
-      clazz.setPermittedSubclassAttributes(
-          fixupPermittedSubclassAttribute(clazz.getPermittedSubclassAttributes()));
-    }
-    VerticalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
+    VerticalClassMergerGraphLens lens = super.run(executorService, timing);
     for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
       synthesizedBridge.updateMethodSignatures(lens);
     }
-    new AnnotationFixer(appView, lens).run(appView.appInfo().classes(), executorService);
-    timing.end();
     return lens;
   }
 
   @Override
-  public DexType mapClassType(DexType type) {
-    while (mergedClasses.hasBeenMergedIntoSubtype(type)) {
-      type = mergedClasses.getTargetFor(type);
-    }
-    return type;
+  public void postprocess() {
+    lensBuilder.fixupContextualVirtualToDirectMethodMaps();
   }
 
   @Override
-  public void recordClassChange(DexType from, DexType to) {
-    // Fixup of classes is not used so no class type should change.
-    throw new Unreachable();
-  }
-
-  @Override
-  public void recordFieldChange(DexField from, DexField to) {
-    if (!lensBuilder.hasOriginalSignatureMappingFor(to)) {
-      lensBuilder.map(from, to);
-    }
-  }
-
-  @Override
-  public void recordMethodChange(DexMethod from, DexMethod to) {
-    if (!lensBuilder.hasOriginalSignatureMappingFor(to)) {
-      lensBuilder.map(from, to).recordMove(from, to);
-    }
-  }
-
-  @Override
-  public DexEncodedMethod recordMethodChange(DexEncodedMethod method, DexEncodedMethod newMethod) {
-    recordMethodChange(method.getReference(), newMethod.getReference());
-    if (newMethod.isNonPrivateVirtualMethod()) {
-      // Since we changed the return type or one of the parameters, this method cannot be a
-      // classpath or library method override, since we only class merge program classes.
-      assert !method.isLibraryMethodOverride().isTrue();
-      newMethod.setLibraryMethodOverride(OptionalBool.FALSE);
-    }
-    return newMethod;
+  public boolean isRunningBeforePrimaryOptimizationPass() {
+    return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java
index 394b067..0003478 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java
@@ -22,13 +22,16 @@
 public class VerticallyMergedClasses implements MergedClasses {
 
   private final BidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses;
-  private final BidirectionalManyToOneMap<DexType, DexType> mergedInterfaces;
+  private final BidirectionalManyToOneMap<DexType, DexType> mergedInterfacesToClasses;
+  private final BidirectionalManyToOneMap<DexType, DexType> mergedInterfacesToInterfaces;
 
   public VerticallyMergedClasses(
       BidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses,
-      BidirectionalManyToOneMap<DexType, DexType> mergedInterfaces) {
+      BidirectionalManyToOneMap<DexType, DexType> mergedInterfacesToClasses,
+      BidirectionalManyToOneMap<DexType, DexType> mergedInterfacesToInterfaces) {
     this.mergedClasses = mergedClasses;
-    this.mergedInterfaces = mergedInterfaces;
+    this.mergedInterfacesToClasses = mergedInterfacesToClasses;
+    this.mergedInterfacesToInterfaces = mergedInterfacesToInterfaces;
   }
 
   public static Builder builder() {
@@ -38,7 +41,7 @@
   public static VerticallyMergedClasses empty() {
     EmptyBidirectionalOneToOneMap<DexType, DexType> emptyMap =
         new EmptyBidirectionalOneToOneMap<>();
-    return new VerticallyMergedClasses(emptyMap, emptyMap);
+    return new VerticallyMergedClasses(emptyMap, emptyMap, emptyMap);
   }
 
   @Override
@@ -59,6 +62,10 @@
     return mergedClasses.keySet();
   }
 
+  public Set<DexType> getTargets() {
+    return mergedClasses.values();
+  }
+
   public Collection<DexType> getSourcesFor(DexType type) {
     return mergedClasses.getKeys(type);
   }
@@ -77,8 +84,13 @@
     return mergedClasses.containsKey(type);
   }
 
+  public boolean hasInterfaceBeenMergedIntoClass(DexType interfaceType, DexType classType) {
+    return classType.isIdenticalTo(mergedInterfacesToClasses.get(interfaceType));
+  }
+
   public boolean hasInterfaceBeenMergedIntoSubtype(DexType type) {
-    return mergedInterfaces.containsKey(type);
+    return mergedInterfacesToClasses.containsKey(type)
+        || mergedInterfacesToInterfaces.containsKey(type);
   }
 
   public boolean isEmpty() {
@@ -104,13 +116,20 @@
     private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> mergedClasses =
         BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
 
-    private final BidirectionalManyToOneHashMap<DexType, DexType> mergedInterfaces =
+    private final BidirectionalManyToOneHashMap<DexType, DexType> mergedInterfacesToClasses =
+        BidirectionalManyToOneHashMap.newIdentityHashMap();
+
+    private final BidirectionalManyToOneHashMap<DexType, DexType> mergedInterfacesToInterfaces =
         BidirectionalManyToOneHashMap.newIdentityHashMap();
 
     void add(DexProgramClass source, DexProgramClass target) {
       mergedClasses.put(source.getType(), target.getType());
       if (source.isInterface()) {
-        mergedInterfaces.put(source.getType(), target.getType());
+        if (target.isInterface()) {
+          mergedInterfacesToInterfaces.put(source.getType(), target.getType());
+        } else {
+          mergedInterfacesToClasses.put(source.getType(), target.getType());
+        }
       }
     }
 
@@ -128,11 +147,13 @@
 
     void merge(VerticallyMergedClasses.Builder other) {
       mergedClasses.putAll(other.mergedClasses);
-      mergedInterfaces.putAll(other.mergedInterfaces);
+      mergedInterfacesToClasses.putAll(other.mergedInterfacesToClasses);
+      mergedInterfacesToInterfaces.putAll(other.mergedInterfacesToInterfaces);
     }
 
     VerticallyMergedClasses build() {
-      return new VerticallyMergedClasses(mergedClasses, mergedInterfaces);
+      return new VerticallyMergedClasses(
+          mergedClasses, mergedInterfacesToClasses, mergedInterfacesToInterfaces);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 0bc2093..0dbe829 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1176,7 +1176,7 @@
   // These tests match on paths relative to the execution directory (normally the repo root).
   // Cached stdout might be from a different directory.
   private static List<String> noArtCommandCaching =
-      ImmutableList.of("068-classloader", "086-null-superTest", "087-gc-after-linkTest");
+      ImmutableList.of("068-classloader", "086-null-super", "087-gc-after-link");
 
   private static final String NO_CLASS_ACCESS_MODIFICATION_RULE =
       "-keep,allowobfuscation,allowoptimization,allowshrinking class *";
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
index e180f05..7b685b6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
@@ -71,6 +71,7 @@
 
   @NoHorizontalClassMerging
   @NeverClassInline
+  @NoVerticalClassMerging
   public static class C {
     @NeverInline
     public void foo(A a) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ConflictWasDetectedTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ConflictWasDetectedTest.java
index b1185f0..3eec75f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ConflictWasDetectedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ConflictWasDetectedTest.java
@@ -6,37 +6,42 @@
 
 import com.android.tools.r8.KeepUnusedArguments;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class ConflictWasDetectedTest extends TestBase {
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public ConflictWasDetectedTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector
+                    .assertClassesMerged(
+                        ClassWithConflictingMethod.class, OtherClassWithConflictingMethod.class)
+                    .assertNoOtherClassesMerged())
         .addVerticallyMergedClassesInspector(
-            VerticallyMergedClassesInspector::assertNoClassesMerged)
+            inspector -> inspector.assertMergedIntoSubtype(ConflictingInterface.class))
         .enableInliningAnnotations()
-        // .enableNoHorizontalClassMergingAnnotations()
+        .enableNoParameterTypeStrengtheningAnnotations()
         .enableUnusedArgumentAnnotations()
         .setMinApi(parameters)
         .compile()
@@ -53,6 +58,8 @@
       escape(impl);
     }
 
+    @NeverInline
+    @NoParameterTypeStrengthening
     private static void callMethodOnIface(ConflictingInterface iface) {
       System.out.println(iface.method());
       System.out.println(ClassWithConflictingMethod.conflict(null));
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
index 8cd641d..513561a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
@@ -4,15 +4,11 @@
 
 package com.android.tools.r8.classmerging.vertical;
 
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.codeinspector.AssertUtils;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,36 +34,31 @@
 
   @Test
   public void test() throws Exception {
-    Box<R8TestCompileResult> compileResult = new Box<>();
-    AssertUtils.assertFailsCompilationIf(
-        verifyLensLookup,
-        () ->
-            compileResult.set(
-                testForR8(parameters.getBackend())
-                    .addInnerClasses(IncorrectRewritingOfInvokeSuperTest.class)
-                    .addKeepMainRule(TestClass.class)
-                    .addOptionsModification(
-                        options -> {
-                          options.enableUnusedInterfaceRemoval = false;
-                          options.testing.enableVerticalClassMergerLensAssertion = verifyLensLookup;
-                        })
-                    .enableInliningAnnotations()
-                    .addDontObfuscate()
-                    .setMinApi(parameters)
-                    .compile()));
-
-    if (!compileResult.isSet()) {
-      assertTrue(verifyLensLookup);
-      return;
-    }
-
-    compileResult.get().run(parameters.getRuntime(), TestClass.class).assertSuccess();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(IncorrectRewritingOfInvokeSuperTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              options.enableUnusedInterfaceRemoval = false;
+              options.testing.enableVerticalClassMergerLensAssertion = verifyLensLookup;
+            })
+        .enableInliningAnnotations()
+        .addDontObfuscate()
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Caught NPE");
   }
 
   static class TestClass {
 
     public static void main(String[] args) {
-      new B() {}.m(new SubArgType());
+      B b = new B() {};
+      b.m(new SubArgType());
+      try {
+        b.m(null);
+      } catch (RuntimeException e) {
+        System.out.println("Caught NPE");
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/MethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/MethodCollisionTest.java
index ea8f326..3555fa6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/MethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/MethodCollisionTest.java
@@ -9,37 +9,30 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class MethodCollisionTest extends TestBase {
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public MethodCollisionTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        // TODO(christofferqa): Currently we do not allow merging A into B because we find a
-        //  collision. However, we are free to change the names of private methods, so we should
-        //  handle them similar to fields (i.e., we should allow merging A into B). This would also
-        //  improve the performance of the collision detector, because it would only have to
-        //  consider non-private methods.
         .addVerticallyMergedClassesInspector(
-            VerticallyMergedClassesInspector::assertNoClassesMerged)
+            inspector -> inspector.assertMergedIntoSubtype(A.class, C.class))
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 7db55ae..7254cd5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -145,7 +145,6 @@
     Set<String> preservedClassNames =
         ImmutableSet.of(
             "classmerging.ArrayTypeCollisionTest",
-            "classmerging.ArrayTypeCollisionTest$A",
             "classmerging.ArrayTypeCollisionTest$B");
     runTest(
         testForR8(parameters.getBackend())
@@ -177,8 +176,7 @@
   public void testArrayReturnTypeCollision() throws Throwable {
     String main = "classmerging.ArrayReturnTypeCollisionTest";
     Set<String> preservedClassNames =
-        ImmutableSet.of(
-            "classmerging.ArrayReturnTypeCollisionTest", "classmerging.A", "classmerging.B");
+        ImmutableSet.of("classmerging.ArrayReturnTypeCollisionTest", "classmerging.B");
 
     JasminBuilder jasminBuilder = new JasminBuilder();
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java
index d5bfade..de57198 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsSubstitutionTest.java
@@ -34,7 +34,7 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  @Test()
+  @Test
   public void test() throws Exception {
     testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
@@ -63,14 +63,22 @@
               // copied to the virtual bridge.
               MethodSubject fooMovedFromB =
                   classSubject.uniqueMethodThatMatches(
-                      method -> !method.isVirtual() && method.getOriginalName(false).equals("foo"));
+                      method ->
+                          method.isFinal()
+                              && method.isVirtual()
+                              && !method.isSynthetic()
+                              && method.getOriginalName(false).equals("foo"));
               assertThat(fooMovedFromB, isPresentAndRenamed());
               assertEquals(
                   "(Ljava/lang/Object;)Ljava/lang/Object;",
                   fooMovedFromB.getFinalSignatureAttribute());
               MethodSubject fooBridge =
                   classSubject.uniqueMethodThatMatches(
-                      method -> method.isVirtual() && method.getOriginalName(false).equals("foo"));
+                      method ->
+                          method.isFinal()
+                              && method.isVirtual()
+                              && method.isSynthetic()
+                              && method.getOriginalName(false).equals("foo"));
               assertThat(fooBridge, isPresentAndRenamed());
               assertEquals(
                   "(Ljava/lang/Object;)Ljava/lang/Object;", fooBridge.getFinalSignatureAttribute());
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
index 0a7c200..e4847bd 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.PACKAGE_NAME;
 import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.classesMatching;
 import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertNotNull;
 import static junit.framework.TestCase.assertSame;
 import static junit.framework.TestCase.assertTrue;
@@ -151,7 +152,7 @@
   }
 
   public static void assertNestAttributesCorrect(CodeInspector inspector) {
-    assertTrue(inspector.allClasses().size() > 0);
+    assertFalse(inspector.allClasses().isEmpty());
     for (FoundClassSubject classSubject : inspector.allClasses()) {
       DexClass clazz = classSubject.getDexProgramClass();
       if (clazz.isInANest()) {
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
index f0955fc..e070f46 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
@@ -9,6 +9,7 @@
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 
+import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -43,13 +44,17 @@
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
             .addProgramClasses(I.class, A.class, Main.class)
             .addProgramClassFileData(getClassWithTransformedInvoked())
-            .run(parameters.getRuntime(), Main.class);
+            .run(parameters.getRuntime(), Main.class)
+            .apply(this::inspectRunResult);
+  }
+
+  private void inspectRunResult(SingleTestRunResult<?> runResult) {
     if (parameters.isDexRuntime() && parameters.canUseDefaultAndStaticInterfaceMethods()) {
       // TODO(b/166210854): Runs really should fail, but since DEX does not have interface
       //  method references the VM will just dispatch.
-      result.assertSuccessWithOutput(EXPECTED);
+      runResult.assertSuccessWithOutput(EXPECTED);
     } else {
-      result.assertFailureWithErrorThatThrows(getExpectedException());
+      runResult.assertFailureWithErrorThatThrows(getExpectedException());
     }
   }
 
@@ -74,8 +79,11 @@
         .addKeepMainRule(Main.class)
         .setMinApi(parameters)
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/166210854): Runs but should have failed.
-        .assertSuccessWithOutput(EXPECTED);
+        .applyIf(
+            parameters.isCfRuntime(),
+            // TODO(b/166210854): Runs but should have failed.
+            runResult -> runResult.assertSuccessWithOutput(EXPECTED),
+            this::inspectRunResult);
   }
 
   private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
index e89317f..0421acd 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
@@ -73,7 +73,7 @@
 
   public static class A implements I {}
 
-  public static class B extends A {
+  public static class B extends A { // B extends Object implements I
 
     public void bar() {
       foo(); // Will be rewritten to invoke-special B.foo()
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java
index 15f1e9c..455e037 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToImmediateInterfaceTest.java
@@ -6,17 +6,13 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.codeinspector.AssertUtils;
 import java.io.IOException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -63,30 +59,17 @@
 
   @Test
   public void testR8() throws Exception {
-    Box<R8TestCompileResult> compileResult = new Box<>();
-
-    // TODO(b/313065227): Should succeed.
-    AssertUtils.assertFailsCompilationIf(
-        parameters.isCfRuntime(),
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClasses(I.class, Main.class)
-                .addProgramClassFileData(getClassWithTransformedInvoked())
-                .addKeepMainRule(Main.class)
-                .setMinApi(parameters)
-                .compile()
-                .apply(compileResult::set));
-
-    if (!compileResult.isSet()) {
-      assertTrue(parameters.isCfRuntime());
-      return;
-    }
-
-    // TODO(b/313065227): Should succeed.
-    compileResult
-        .get()
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, Main.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters)
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatThrows(NullPointerException.class);
+        // TODO(b/313065227): Should succeed.
+        .applyIf(
+            parameters.isCfRuntime(),
+            runResult -> runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            runResult -> runResult.assertFailureWithErrorThatThrows(NullPointerException.class));
   }
 
   private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
index 9d261da..2fd2c50 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
@@ -10,6 +10,8 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.KeepUnusedReturnValue;
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestParameters;
@@ -17,8 +19,6 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -26,7 +26,6 @@
 
 @RunWith(Parameterized.class)
 public class VerticalClassMergingRetraceTest extends RetraceTestBase {
-  private Set<StackTraceLine> haveSeenLines = new HashSet<>();
 
   @Parameters(name = "{0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
@@ -43,7 +42,10 @@
 
   @Override
   public void configure(R8TestBuilder builder) {
-    builder.enableInliningAnnotations();
+    builder
+        .enableInliningAnnotations()
+        .enableKeepUnusedReturnValueAnnotations()
+        .enableNeverClassInliningAnnotations();
   }
 
   @Override
@@ -126,7 +128,6 @@
     // since the synthetic bridge belongs to ResourceWrapper.foo.
     assumeTrue(compat);
     assumeTrue(parameters.isDexRuntime());
-    haveSeenLines.clear();
     runTest(
         ImmutableList.of(),
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -142,11 +143,14 @@
   // Will be merged down, and represented as:
   //     java.lang.String ...ResourceWrapper.foo() -> a
   @NeverInline
+  // TODO(b/313404813): Remove @KeepUnusedReturnValue as a workaround for a retrace failure.
+  @KeepUnusedReturnValue
   String foo() {
     throw null;
   }
 }
 
+@NeverClassInline
 class TintResources extends ResourceWrapper {}
 
 class MainApp {
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java
index b04e01b..56ea0b9 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -16,7 +17,6 @@
 import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,6 +46,7 @@
             options -> options.callSiteOptimizationOptions().disableOptimization())
         .addVerticallyMergedClassesInspector(
             inspector -> inspector.assertMergedIntoSubtype(A.class))
+        .enableNeverClassInliningAnnotations()
         .setMinApi(parameters)
         .compile()
         .inspectResidualArtProfile(this::inspect)
@@ -64,11 +65,13 @@
     assertThat(bClassSubject, isPresent());
 
     MethodSubject movedMethodSubject =
-        bClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isPrivate);
+        bClassSubject.uniqueMethodThatMatches(
+            method -> method.isBridge() && method.isSynthetic() && method.isVirtual());
     assertThat(movedMethodSubject, isPresent());
 
     MethodSubject syntheticBridgeMethodSubject =
-        bClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual);
+        bClassSubject.uniqueMethodThatMatches(
+            method -> !method.isBridge() && !method.isSynthetic() && method.isVirtual());
     assertThat(syntheticBridgeMethodSubject, isPresent());
 
     profileInspector
@@ -90,5 +93,6 @@
     }
   }
 
+  @NeverClassInline
   static class B extends A {}
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
index 9d3f2c5..0fe9536 100644
--- a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -4,8 +4,7 @@
 package com.android.tools.r8.shaking;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -142,28 +141,22 @@
             .inspector();
 
     ClassSubject superInterface1 = inspector.clazz(B112452064SuperInterface1.class);
-    if (enableUnusedInterfaceRemoval && enableVerticalClassMerging) {
-      assertThat(superInterface1, isAbsent());
-    } else {
-      assertThat(superInterface1, isPresentAndRenamed());
-    }
+    assertThat(
+        superInterface1, isAbsentIf(enableUnusedInterfaceRemoval && enableVerticalClassMerging));
+
     MethodSubject foo = superInterface1.uniqueMethodWithOriginalName("foo");
-    assertThat(foo, not(isPresent()));
+    assertThat(foo, isAbsent());
+
     ClassSubject superInterface2 = inspector.clazz(B112452064SuperInterface2.class);
-    if (enableVerticalClassMerging) {
-      assertThat(superInterface2, not(isPresent()));
-    } else {
-      assertThat(superInterface2, isPresentAndRenamed());
-    }
+    assertThat(
+        superInterface2, isAbsentIf(enableUnusedInterfaceRemoval && enableVerticalClassMerging));
+
     MethodSubject bar = superInterface2.uniqueMethodWithOriginalName("bar");
-    assertThat(bar, not(isPresent()));
+    assertThat(bar, isAbsent());
+
     ClassSubject subInterface = inspector.clazz(B112452064SubInterface.class);
-    if (enableUnusedInterfaceRemoval) {
-      assertThat(subInterface, not(isPresent()));
-    } else {
-      assertThat(subInterface, isPresent());
-      assertThat(subInterface, isPresentAndRenamed());
-    }
+    assertThat(
+        subInterface, isAbsentIf(enableUnusedInterfaceRemoval || enableVerticalClassMerging));
   }
 
   @Test