Merge commit '8b95700662d1f3c4d6df79d560ddcb436f550e9e' into dev-release
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 4fc504a..2b6eb5f 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -149,6 +149,15 @@
       }
     }
     builders {
+      name: "linux_horizontal"
+      mixins: "linux"
+      mixins: "normal"
+      recipe {
+        properties: "tool:r8"
+        properties: "horizontal_class_merging:True"
+      }
+    }
+    builders {
       name: "linux_release"
       mixins: "normal"
       mixins: "linux"
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index faf51f3..90e3efc 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -16,6 +16,11 @@
     short_name: "linux"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux_horizontal"
+    category: "R8"
+    short_name: "horizontal"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux-android-4.0.4"
     category: "R8"
     short_name: "4.0.4"
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg
index 5e37d53..e417700 100644
--- a/infra/config/global/luci-notify.cfg
+++ b/infra/config/global/luci-notify.cfg
@@ -29,6 +29,11 @@
     repository: "https://r8.googlesource.com/r8"
   }
   builders {
+    name: "linux_horizontal"
+    bucket: "ci"
+    repository: "https://r8.googlesource.com/r8"
+  }
+  builders {
     name: "linux_release"
     bucket: "ci"
     repository: "https://r8.googlesource.com/r8"
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index eb725d9..3c31451 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -27,6 +27,7 @@
   }
   triggers: "archive"
   triggers: "linux"
+  triggers: "linux_horizontal"
   triggers: "linux-android-4.0.4"
   triggers: "linux-android-4.4.4"
   triggers: "linux-android-5.1.1"
@@ -136,6 +137,19 @@
 }
 
 job {
+  id: "linux_horizontal"
+  acl_sets: "default"
+  triggering_policy: {
+    max_concurrent_invocations: 3
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux_horizontal"
+  }
+}
+
+job {
   id: "linux-android-4.0.4"
   acl_sets: "default"
   triggering_policy: {
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index a1d8b26..c0efa2c 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -34,6 +34,8 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -299,7 +301,7 @@
   }
 
   public static void main(String... args) throws Exception {
-    if (args.length != 3 && args.length != 4) {
+    if (args.length != 3 && args.length != 4 && args.length != 5) {
       System.out.println(USAGE.replace("\n", System.lineSeparator()));
       return;
     }
@@ -324,11 +326,15 @@
     builder.addLibraryFile(rtJar);
     Path r8Jar = Paths.get(args[argumentIndex++]);
     builder.addLibraryFile(r8Jar);
-    Path sampleJar = Paths.get(args[argumentIndex]);
+    Path sampleJar = Paths.get(args[argumentIndex++]);
     builder.addProgramFile(sampleJar);
+    PrintStream output = System.out;
+    if (argumentIndex < args.length) {
+      output = new PrintStream(Files.newOutputStream(Paths.get(args[argumentIndex])));
+    }
     Set<String> descriptors = new HashSet<>(getDescriptors(r8Jar));
     descriptors.removeAll(getDescriptors(sampleJar));
-    Printer printer = printKeep ? new KeepPrinter() : new DefaultPrinter();
+    Printer printer = printKeep ? new KeepPrinter(output) : new DefaultPrinter(output);
     PrintUses printUses = new PrintUses(descriptors, builder.build(), printer, allowObfuscation);
     printUses.analyze();
     printUses.print();
@@ -378,12 +384,18 @@
 
   private abstract static class Printer {
 
+    PrintStream output;
+
+    Printer(PrintStream output) {
+      this.output = output;
+    }
+
     void append(String string) {
-      System.out.print(string);
+      output.print(string);
     }
 
     void appendLine(String string) {
-      System.out.println(string);
+      output.println(string);
     }
 
     void printArguments(DexMethod method) {
@@ -472,6 +484,10 @@
 
   private static class DefaultPrinter extends Printer {
 
+    DefaultPrinter(PrintStream output) {
+      super(output);
+    }
+
     @Override
     public void printConstructorName(DexEncodedMethod encodedMethod) {
       if (encodedMethod.accessFlags.isStatic()) {
@@ -517,6 +533,10 @@
 
   private static class KeepPrinter extends Printer {
 
+    KeepPrinter(PrintStream output) {
+      super(output);
+    }
+
     @Override
     public void printTypeHeader(DexClass dexClass, boolean allowObfuscation) {
       append(allowObfuscation ? "-keep,allowobfuscation" : "-keep");
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9963a47..c98bafd 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -74,6 +74,8 @@
 import com.android.tools.r8.optimize.BridgeHoisting;
 import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
+import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
+import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
 import com.android.tools.r8.optimize.VisibilityBridgeRemover;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.repackaging.Repackaging;
@@ -532,21 +534,6 @@
           }
           timing.end();
         }
-        if (options.enableHorizontalClassMerging) {
-          timing.begin("HorizontalClassMerger");
-          HorizontalClassMerger merger =
-              new HorizontalClassMerger(
-                  appViewWithLiveness, mainDexTracingResult, classMergingEnqueuerExtension);
-          HorizontalClassMergerGraphLens lens = merger.run();
-          if (lens != null) {
-            appView.setHorizontallyMergedClasses(lens.getHorizontallyMergedClasses());
-            appView.rewriteWithLens(lens);
-          }
-          timing.end();
-        }
-
-        // Only required for class merging, clear instance to save memory.
-        classMergingEnqueuerExtension = null;
 
         if (options.enableArgumentRemoval) {
           SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
@@ -575,6 +562,23 @@
             timing.end();
           }
         }
+        if (options.enableHorizontalClassMerging) {
+          timing.begin("HorizontalClassMerger");
+          HorizontalClassMerger merger =
+              new HorizontalClassMerger(
+                  appViewWithLiveness, mainDexTracingResult, classMergingEnqueuerExtension);
+          DirectMappedDexApplication.Builder appBuilder =
+              appView.appInfo().app().asDirect().builder();
+          HorizontalClassMergerGraphLens lens = merger.run(appBuilder);
+          if (lens != null) {
+            appView.setHorizontallyMergedClasses(lens.getHorizontallyMergedClasses());
+            appView.rewriteWithLensAndApplication(lens, appBuilder.build());
+          }
+          timing.end();
+        }
+
+        // Only required for class merging, clear instance to save memory.
+        classMergingEnqueuerExtension = null;
       }
 
       // None of the optimizations above should lead to the creation of type lattice elements.
@@ -793,6 +797,22 @@
         }
       }
 
+      // Remove unneeded visibility bridges that have been inserted for member rebinding.
+      // This can only be done if we have AppInfoWithLiveness.
+      if (appView.appInfo().hasLiveness()) {
+        new VisibilityBridgeRemover(appView.withLiveness()).run();
+      } else {
+        // If we don't have AppInfoWithLiveness here, it must be because we are not shrinking. When
+        // we are not shrinking, we can't move visibility bridges. In principle, though, it would be
+        // possible to remove visibility bridges that have been synthesized by R8, but we currently
+        // do not have this information.
+        assert !options.isShrinking();
+      }
+
+      MemberRebindingIdentityLens memberRebindingLens =
+          MemberRebindingIdentityLensFactory.create(appView, executorService);
+      appView.setGraphLens(memberRebindingLens);
+
       // Perform repackaging.
       // TODO(b/165783399): Consider making repacking available without minification.
       if (options.isMinifying() && options.testing.enableExperimentalRepackaging) {
@@ -803,7 +823,13 @@
         RepackagingLens lens =
             new Repackaging(appView.withLiveness()).run(appBuilder, executorService, timing);
         if (lens != null) {
-          appView.rewriteWithLensAndApplication(lens, appBuilder.build());
+          // Specify to use the member rebinding lens as the parent lens during the rewriting. This
+          // is needed to ensure that the rebound references are available during lens lookups.
+          // TODO(b/168282032): This call-site should not have to think about the parent lens that
+          //  is used for the rewriting. Once the new member rebinding lens replaces the old member
+          //  rebinding analysis it should be possible to clean this up.
+          appView.rewriteWithLensAndApplication(
+              lens, appBuilder.build(), memberRebindingLens.getPrevious());
         }
       }
 
@@ -866,18 +892,6 @@
         return;
       }
 
-      // Remove unneeded visibility bridges that have been inserted for member rebinding.
-      // This can only be done if we have AppInfoWithLiveness.
-      if (appView.appInfo().hasLiveness()) {
-        new VisibilityBridgeRemover(appView.withLiveness()).run();
-      } else {
-        // If we don't have AppInfoWithLiveness here, it must be because we are not shrinking. When
-        // we are not shrinking, we can't move visibility bridges. In principle, though, it would be
-        // possible to remove visibility bridges that have been synthesized by R8, but we currently
-        // do not have this information.
-        assert !options.isShrinking();
-      }
-
       // Validity checks.
       assert getDirectApp(appView).verifyCodeObjectsOwners();
       assert appView.appInfo().classes().stream().allMatch(clazz -> clazz.isValid(options));
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index 5fb9822..b4b0d5a 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -202,6 +202,10 @@
     return r8Type("DexItemFactory", "graph");
   }
 
+  private String arrayDequeType() {
+    return type("ArrayDeque", ImmutableList.of("java", "util"));
+  }
+
   private String arraysType() {
     return type("Arrays", ImmutableList.of("java", "util"));
   }
@@ -464,7 +468,7 @@
             + "[] { "
             + values
             + " })",
-        arraysType() + ".asList(" + stack + ")");
+        "new " + arrayDequeType() + "<>(" + arraysType() + ".asList(" + stack + "))");
   }
 
   private String frameTypeType(FrameType frameType) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index f128f76..a8386df 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -4,9 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -42,6 +45,16 @@
     this.type = type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getAsmOpcode();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
   public Opcode getOpcode() {
     return opcode;
   }
@@ -181,4 +194,17 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forBinop();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., value1, value2 →
+    // ..., result
+    FrameType frameType = FrameType.fromNumericType(type, factory);
+    frameBuilder.popAndDiscard(frameType, frameType).push(frameType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 5757b8c..734246c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -35,6 +37,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.ARRAYLENGTH;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
@@ -57,4 +69,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forArrayLength();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., arrayref →
+    // ..., length
+    frameBuilder.popAndDiscard(factory.objectArrayType).push(factory.intType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 7993e8e..3f93558 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -4,9 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -32,6 +35,16 @@
     this.type = type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getLoadType();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
   public MemberType getType() {
     return type;
   }
@@ -102,4 +115,17 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forArrayGet();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., arrayref, index →
+    // ..., value
+    frameBuilder.popAndDiscard(factory.objectArrayType, factory.intType);
+    frameBuilder.push(FrameType.fromMemberType(type, factory));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 1c16397..bab53f5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -4,9 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -34,6 +37,16 @@
     return type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getStoreType();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
   private int getStoreType() {
     switch (type) {
       case OBJECT:
@@ -92,4 +105,18 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forArrayPut();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., arrayref, index, value →
+    // ...
+    frameBuilder
+        .popAndDiscard(FrameType.fromMemberType(type, factory))
+        .popAndDiscard(factory.objectArrayType, factory.intType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index e263d02..f4bfd7a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -36,6 +37,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.CHECKCAST;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return type.slowCompareTo(((CfCheckCast) other).type);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -76,4 +87,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forCheckCast(type, context);
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., objectref →
+    // ..., objectref
+    frameBuilder.popAndDiscard(factory.objectType).push(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index 1eeb6b4..98b4cef 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -4,9 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -39,6 +42,16 @@
     this.type = type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getAsmOpcode();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
   public Bias getBias() {
     return bias;
   }
@@ -106,4 +119,17 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forBinop();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., value1, value2 →
+    // ..., result
+    FrameType frameType = FrameType.fromNumericType(type, factory);
+    frameBuilder.popAndDiscard(frameType, frameType).push(factory.intType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index 2bb2444..adf0ba2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -31,6 +32,16 @@
     this.type = type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return CfCompareHelper.CONST_CLASS_COMPARE_ID;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return type.slowCompareTo(((CfConstClass) other).type);
+  }
+
   public DexType getType() {
     return type;
   }
@@ -99,4 +110,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forConstClass(type, context);
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., value
+    frameBuilder.push(factory.classType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index d058eca..ff490b5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -4,10 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -24,7 +26,7 @@
 
 public class CfConstMethodHandle extends CfInstruction {
 
-  private DexMethodHandle handle;
+  private final DexMethodHandle handle;
 
   public CfConstMethodHandle(DexMethodHandle handle) {
     this.handle = handle;
@@ -35,6 +37,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return CfCompareHelper.CONST_METHOD_HANDLE_COMPARE_ID;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return handle.slowCompareTo(((CfConstMethodHandle) other).handle);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -76,4 +88,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forConstMethodHandle();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., value
+    frameBuilder.push(factory.methodHandleType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index e59a64e..5b9e4aa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -4,10 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 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.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -35,6 +37,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return CfCompareHelper.CONST_METHOD_TYPE_COMPARE_ID;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return type.slowCompareTo(((CfConstMethodType) other).type);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -74,4 +86,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forConstMethodType();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., value
+    frameBuilder.push(factory.methodTypeType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index e5740e7..9a42170 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -35,6 +37,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.ACONST_NULL;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
@@ -49,4 +61,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forConstInstruction();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., value
+    frameBuilder.push(DexItemFactory.nullValueType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 642eeb9..a919748 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -5,8 +5,10 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -18,6 +20,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.Comparator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -31,6 +34,18 @@
     this.type = type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return CfCompareHelper.CONST_NUMBER_COMPARE_ID;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return Comparator.comparing(CfConstNumber::getRawValue)
+        .thenComparing(CfConstNumber::getType)
+        .compare(this, (CfConstNumber) other);
+  }
+
   public ValueType getType() {
     return type;
   }
@@ -147,4 +162,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forConstInstruction();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., value
+    frameBuilder.push(type.toPrimitiveType().toDexType(factory));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 9037875..8ff71b9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -4,9 +4,11 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -27,6 +29,16 @@
     this.string = string;
   }
 
+  @Override
+  public int getCompareToId() {
+    return CfCompareHelper.CONST_STRING_COMPARE_ID;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return string.slowCompareTo(other.asConstString().string);
+  }
+
   public DexString getString() {
     return string;
   }
@@ -79,4 +91,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forConstInstruction();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., value
+    frameBuilder.push(factory.stringType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index bdc470f..64c45fa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -5,10 +5,12 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -33,6 +35,16 @@
     this.nameComputationInfo = nameComputationInfo;
   }
 
+  @Override
+  public int getCompareToId() {
+    return CfCompareHelper.CONST_STRING_DEX_ITEM_COMPARE_ID;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return item.referenceCompareTo(((CfDexItemBasedConstString) other).item);
+  }
+
   public DexReference getItem() {
     return item;
   }
@@ -96,4 +108,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forDexItemBasedConstString(item, context);
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., value
+    frameBuilder.push(factory.stringType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 3cff4ee..ba90681 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.Comparator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -47,6 +49,18 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return opcode;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return Comparator.comparing(CfFieldInstruction::getField, DexField::slowCompareTo)
+        .thenComparing(field -> field.declaringField, DexField::slowCompareTo)
+        .compare(this, (CfFieldInstruction) other);
+  }
+
+  @Override
   public CfFieldInstruction asFieldInstruction() {
     return this;
   }
@@ -152,4 +166,37 @@
         throw new Unreachable("Unexpected opcode " + opcode);
     }
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    switch (opcode) {
+      case Opcodes.GETFIELD:
+        // ..., objectref →
+        // ..., value
+        frameBuilder.popAndDiscard(field.holder).push(field.type);
+        return;
+      case Opcodes.GETSTATIC:
+        // ..., →
+        // ..., value
+        frameBuilder.push(field.type);
+        return;
+      case Opcodes.PUTFIELD:
+        // ..., objectref, value →
+        // ...,
+        frameBuilder.popAndDiscard(field.holder, field.type);
+        return;
+      case Opcodes.PUTSTATIC:
+        // ..., value →
+        // ...
+        frameBuilder.pop(field.type);
+        return;
+      default:
+        throw new Unreachable("Unexpected opcode " + opcode);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index f392338..1770409 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -7,12 +7,16 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -20,8 +24,10 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
-import java.util.List;
+import java.util.ArrayDeque;
+import java.util.Deque;
 import java.util.Objects;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -34,8 +40,8 @@
       return new InitializedType(type);
     }
 
-    public static FrameType uninitializedNew(CfLabel label) {
-      return new UninitializedNew(label);
+    public static FrameType uninitializedNew(CfLabel label, DexType typeToInitialize) {
+      return new UninitializedNew(label, typeToInitialize);
     }
 
     public static FrameType uninitializedThis() {
@@ -46,6 +52,14 @@
       return Top.SINGLETON;
     }
 
+    public static FrameType oneWord() {
+      return OneWord.SINGLETON;
+    }
+
+    public static FrameType twoWord() {
+      return TwoWord.SINGLETON;
+    }
+
     abstract Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens);
 
     public boolean isWide() {
@@ -72,11 +86,54 @@
       return null;
     }
 
+    public DexType getUninitializedNewType() {
+      return null;
+    }
+
     public boolean isTop() {
       return false;
     }
 
+    public boolean isOneWord() {
+      return false;
+    }
+
+    public boolean isTwoWord() {
+      return false;
+    }
+
     private FrameType() {}
+
+    public static FrameType fromMemberType(MemberType memberType, DexItemFactory factory) {
+      switch (memberType) {
+        case OBJECT:
+          return FrameType.initialized(factory.objectType);
+        case BOOLEAN_OR_BYTE:
+          return FrameType.initialized(factory.intType);
+        case CHAR:
+          return FrameType.initialized(factory.intType);
+        case SHORT:
+          return FrameType.initialized(factory.intType);
+        case INT:
+          return FrameType.initialized(factory.intType);
+        case FLOAT:
+          return FrameType.initialized(factory.floatType);
+        case LONG:
+          return FrameType.initialized(factory.longType);
+        case DOUBLE:
+          return FrameType.initialized(factory.doubleType);
+        case INT_OR_FLOAT:
+          return FrameType.oneWord();
+        case LONG_OR_DOUBLE:
+          return FrameType.twoWord();
+        default:
+          throw new Unreachable("Unexpected MemberType: " + memberType);
+      }
+    }
+
+    public static FrameType fromNumericType(NumericType numericType, DexItemFactory factory) {
+      return FrameType.initialized(numericType.dexTypeFor(factory));
+    }
   }
 
   @Override
@@ -89,6 +146,18 @@
     return this;
   }
 
+  @Override
+  public int getCompareToId() {
+    return CfCompareHelper.FRAME_COMPARE_ID;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    // The frame should be determined by the code so it should for equal iff the code is equal.
+    // Thus we just require the frame to be in place.
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
   private static class InitializedType extends FrameType {
 
     private final DexType type;
@@ -100,7 +169,7 @@
 
     @Override
     public String toString() {
-      return type.toString();
+      return "Initialized(" + type.toString() + ")";
     }
 
     @Override
@@ -163,9 +232,11 @@
 
   private static class UninitializedNew extends FrameType {
     private final CfLabel label;
+    private final DexType type;
 
-    private UninitializedNew(CfLabel label) {
+    private UninitializedNew(CfLabel label, DexType type) {
       this.label = label;
+      this.type = type;
     }
 
     @Override
@@ -187,9 +258,15 @@
     public CfLabel getUninitializedLabel() {
       return label;
     }
+
+    @Override
+    public DexType getUninitializedNewType() {
+      return type;
+    }
   }
 
   private static class UninitializedThis extends FrameType {
+
     private UninitializedThis() {}
 
     @Override
@@ -208,10 +285,55 @@
     }
   }
 
-  private final Int2ReferenceSortedMap<FrameType> locals;
-  private final List<FrameType> stack;
+  private static class OneWord extends FrameType {
 
-  public CfFrame(Int2ReferenceSortedMap<FrameType> locals, List<FrameType> stack) {
+    private static final OneWord SINGLETON = new OneWord();
+
+    @Override
+    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+      throw new Unreachable("Should only be used for verification");
+    }
+
+    @Override
+    public boolean isOneWord() {
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return "oneword";
+    }
+  }
+
+  private static class TwoWord extends FrameType {
+
+    private static final TwoWord SINGLETON = new TwoWord();
+
+    @Override
+    Object getTypeOpcode(GraphLens graphLens, NamingLens namingLens) {
+      throw new Unreachable("Should only be used for verification");
+    }
+
+    @Override
+    public boolean isWide() {
+      return true;
+    }
+
+    @Override
+    public boolean isTwoWord() {
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return "twoword";
+    }
+  }
+
+  private final Int2ReferenceSortedMap<FrameType> locals;
+  private final Deque<FrameType> stack;
+
+  public CfFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) {
     assert locals.values().stream().allMatch(Objects::nonNull);
     assert stack.stream().allMatch(Objects::nonNull);
     this.locals = locals;
@@ -222,7 +344,7 @@
     return locals;
   }
 
-  public List<FrameType> getStack() {
+  public Deque<FrameType> getStack() {
     return stack;
   }
 
@@ -252,8 +374,9 @@
       return null;
     }
     Object[] stackTypes = new Object[stackCount];
-    for (int i = 0; i < stackCount; i++) {
-      stackTypes[i] = stack.get(i).getTypeOpcode(graphLens, namingLens);
+    int index = 0;
+    for (FrameType frameType : stack) {
+      stackTypes[index++] = frameType.getTypeOpcode(graphLens, namingLens);
     }
     return stackTypes;
   }
@@ -305,7 +428,6 @@
 
   @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
-    // TODO(mathiasr): Verify stack map frames before building IR.
     code.setStateFromFrame(this);
   }
 
@@ -319,4 +441,46 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return ConstraintWithTarget.ALWAYS;
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    frameBuilder.verifyFrameAndSet(this);
+  }
+
+  public CfFrame markInstantiated(FrameType uninitializedType, DexType initType) {
+    if (uninitializedType.isInitialized()) {
+      throw CfCodeStackMapValidatingException.error(
+          "Cannot instantiate already instantiated type " + uninitializedType);
+    }
+    Int2ReferenceSortedMap<FrameType> newLocals = new Int2ReferenceAVLTreeMap<>();
+    for (int var : locals.keySet()) {
+      newLocals.put(var, getInitializedFrameType(uninitializedType, locals.get(var), initType));
+    }
+    Deque<FrameType> newStack = new ArrayDeque<>();
+    for (FrameType frameType : stack) {
+      newStack.addLast(getInitializedFrameType(uninitializedType, frameType, initType));
+    }
+    return new CfFrame(newLocals, newStack);
+  }
+
+  private FrameType getInitializedFrameType(FrameType unInit, FrameType other, DexType newType) {
+    assert !unInit.isInitialized();
+    if (other.isInitialized()) {
+      return other;
+    }
+    if (unInit.isUninitializedThis() && other.isUninitializedThis()) {
+      return FrameType.initialized(newType);
+    }
+    if (unInit.isUninitializedNew()
+        && other.isUninitializedNew()
+        && unInit.getUninitializedLabel() == other.getUninitializedLabel()) {
+      return FrameType.initialized(newType);
+    }
+    return other;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
new file mode 100644
index 0000000..259ad12
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
@@ -0,0 +1,379 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.collections.ImmutableDeque;
+import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiPredicate;
+
+public class CfFrameVerificationHelper {
+
+  private final CfFrame NO_FRAME =
+      new CfFrame(
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder().build(), ImmutableDeque.of());
+
+  private final Deque<FrameType> throwStack;
+
+  private CfFrame currentFrame = NO_FRAME;
+  private final DexType context;
+  private final Map<CfLabel, CfFrame> stateMap;
+  private final BiPredicate<DexType, DexType> isJavaAssignable;
+  private final DexItemFactory factory;
+  private final List<CfTryCatch> tryCatchRanges;
+
+  private final Deque<CfTryCatch> currentCatchRanges = new ArrayDeque<>();
+  private final Set<CfLabel> tryCatchRangeLabels;
+
+  public CfFrameVerificationHelper(
+      DexType context,
+      Map<CfLabel, CfFrame> stateMap,
+      List<CfTryCatch> tryCatchRanges,
+      BiPredicate<DexType, DexType> isJavaAssignable,
+      DexItemFactory factory) {
+    this.context = context;
+    this.stateMap = stateMap;
+    this.tryCatchRanges = tryCatchRanges;
+    this.isJavaAssignable = isJavaAssignable;
+    this.factory = factory;
+    throwStack = ImmutableDeque.of(FrameType.initialized(factory.throwableType));
+    // Compute all labels that marks a start or end to catch ranges.
+    tryCatchRangeLabels = Sets.newIdentityHashSet();
+    for (CfTryCatch tryCatchRange : tryCatchRanges) {
+      tryCatchRangeLabels.add(tryCatchRange.start);
+      tryCatchRangeLabels.add(tryCatchRange.end);
+    }
+  }
+
+  public DexItemFactory factory() {
+    return factory;
+  }
+
+  public FrameType readLocal(int index, DexType expectedType) {
+    verifyFrameIsSet();
+    FrameType frameType = currentFrame.getLocals().get(index);
+    if (frameType == null) {
+      throw CfCodeStackMapValidatingException.error("No local at index " + index);
+    }
+    verifyIsAssignable(frameType, expectedType);
+    return frameType;
+  }
+
+  public void storeLocal(int index, FrameType frameType) {
+    verifyFrameIsSet();
+    currentFrame.getLocals().put(index, frameType);
+  }
+
+  public FrameType pop() {
+    verifyFrameIsSet();
+    if (currentFrame.getStack().isEmpty()) {
+      throw CfCodeStackMapValidatingException.error("Cannot pop() from an empty stack");
+    }
+    return currentFrame.getStack().removeLast();
+  }
+
+  public FrameType pop(DexType expectedType) {
+    FrameType frameType = pop();
+    verifyIsAssignable(frameType, expectedType);
+    return frameType;
+  }
+
+  public CfFrameVerificationHelper popAndDiscard(DexType... expectedTypes) {
+    verifyFrameIsSet();
+    for (int i = expectedTypes.length - 1; i >= 0; i--) {
+      pop(expectedTypes[i]);
+    }
+    return this;
+  }
+
+  public FrameType pop(FrameType expectedType) {
+    FrameType frameType = pop();
+    verifyIsAssignable(frameType, expectedType);
+    return frameType;
+  }
+
+  public CfFrameVerificationHelper popAndDiscard(FrameType... expectedTypes) {
+    verifyFrameIsSet();
+    for (int i = expectedTypes.length - 1; i >= 0; i--) {
+      pop(expectedTypes[i]);
+    }
+    return this;
+  }
+
+  public void popAndInitialize(DexType context, DexType methodHolder) {
+    verifyFrameIsSet();
+    FrameType objectRef = pop(factory.objectType);
+    CfFrame newFrame =
+        currentFrame.markInstantiated(
+            objectRef, objectRef.isUninitializedNew() ? methodHolder : context);
+    setNoFrame();
+    verifyFrameAndSet(newFrame);
+  }
+
+  public CfFrameVerificationHelper push(FrameType type) {
+    verifyFrameIsSet();
+    currentFrame.getStack().addLast(type);
+    return this;
+  }
+
+  public CfFrameVerificationHelper push(DexType type) {
+    return push(FrameType.initialized(type));
+  }
+
+  public CfFrameVerificationHelper seenLabel(CfLabel label) {
+    if (tryCatchRangeLabels.contains(label)) {
+      for (CfTryCatch tryCatchRange : tryCatchRanges) {
+        if (tryCatchRange.start == label) {
+          currentCatchRanges.add(tryCatchRange);
+          // We can have fall-through into this range requiring the current frame being
+          // assignable to the handler frame. This is handled for locals when we see a throwing
+          // instruction, but we can validate here that the stack will be a single element stack
+          // [throwable].
+          CfFrame destinationFrame = stateMap.get(tryCatchRange.start);
+          if (destinationFrame == null) {
+            throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
+          }
+          verifyStackIsAssignable(
+              destinationFrame.getStack(), throwStack, factory, isJavaAssignable);
+        }
+      }
+      currentCatchRanges.removeIf(currentRange -> currentRange.end == label);
+    }
+    return this;
+  }
+
+  private void verifyFrameIsSet() {
+    if (currentFrame == NO_FRAME) {
+      throw CfCodeStackMapValidatingException.error("Unexpected state change");
+    }
+  }
+
+  public void verifyFrameAndSet(CfFrame newFrame) {
+    if (currentFrame != NO_FRAME) {
+      verifyFrame(newFrame);
+    }
+    setFrame(newFrame);
+  }
+
+  private void setFrame(CfFrame frame) {
+    assert frame != NO_FRAME;
+    currentFrame =
+        new CfFrame(
+            new Int2ReferenceAVLTreeMap<>(frame.getLocals()), new ArrayDeque<>(frame.getStack()));
+  }
+
+  public void verifyExceptionEdges() {
+    for (CfTryCatch currentCatchRange : currentCatchRanges) {
+      for (CfLabel target : currentCatchRange.targets) {
+        CfFrame destinationFrame = stateMap.get(target);
+        if (destinationFrame == null) {
+          throw CfCodeStackMapValidatingException.error("No frame for target catch range target");
+        }
+        // We have to check all current handler targets have assignable locals and a 1-element
+        // stack assignable to throwable. It is not required that the the thrown error is
+        // handled.
+        verifyLocalsIsAssignable(
+            currentFrame.getLocals(), destinationFrame.getLocals(), factory, isJavaAssignable);
+      }
+    }
+  }
+
+  public CfFrame getFrame() {
+    return currentFrame;
+  }
+
+  public void verifyTarget(CfLabel label) {
+    verifyFrame(stateMap.get(label));
+  }
+
+  public void verifyFrame(CfFrame destinationFrame) {
+    if (destinationFrame == null) {
+      throw CfCodeStackMapValidatingException.error("No destination frame");
+    }
+    verifyFrame(destinationFrame.getLocals(), destinationFrame.getStack());
+  }
+
+  public void verifyFrame(Int2ReferenceSortedMap<FrameType> locals, Deque<FrameType> stack) {
+    verifyIsAssignable(
+        currentFrame.getLocals(),
+        currentFrame.getStack(),
+        locals,
+        stack,
+        factory,
+        isJavaAssignable);
+  }
+
+  public void setNoFrame() {
+    currentFrame = NO_FRAME;
+  }
+
+  public void clearStack() {
+    verifyFrameIsSet();
+    currentFrame.getStack().clear();
+  }
+
+  public void verifyIsAssignable(FrameType source, DexType target) {
+    if (!source.isInitialized()) {
+      if (source.isUninitializedThis() && target == context) {
+        return;
+      }
+      if (target == factory.objectType) {
+        return;
+      }
+      throw CfCodeStackMapValidatingException.error(
+          "The expected type " + source + " is not assignable to " + target.toSourceString());
+    }
+    if (!isJavaAssignable.test(source.getInitializedType(), target)) {
+      throw CfCodeStackMapValidatingException.error(
+          "The expected type " + source + " is not assignable to " + target.toSourceString());
+    }
+  }
+
+  public void verifyIsAssignable(FrameType source, FrameType target) {
+    if (!canBeAssigned(source, target, factory, isJavaAssignable)) {
+      throw CfCodeStackMapValidatingException.error(
+          "The expected type " + source + " is not assignable to " + target);
+    }
+  }
+
+  // Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.4.
+  public static void verifyIsAssignable(
+      Int2ReferenceSortedMap<FrameType> sourceLocals,
+      Deque<FrameType> sourceStack,
+      Int2ReferenceSortedMap<FrameType> destLocals,
+      Deque<FrameType> destStack,
+      DexItemFactory factory,
+      BiPredicate<DexType, DexType> isJavaAssignable) {
+    verifyLocalsIsAssignable(sourceLocals, destLocals, factory, isJavaAssignable);
+    verifyStackIsAssignable(sourceStack, destStack, factory, isJavaAssignable);
+  }
+
+  private static void verifyLocalsIsAssignable(
+      Int2ReferenceSortedMap<FrameType> sourceLocals,
+      Int2ReferenceSortedMap<FrameType> destLocals,
+      DexItemFactory factory,
+      BiPredicate<DexType, DexType> isJavaAssignable) {
+    final int localsLastKey = sourceLocals.isEmpty() ? -1 : sourceLocals.lastIntKey();
+    final int otherLocalsLastKey = destLocals.isEmpty() ? -1 : destLocals.lastIntKey();
+    if (localsLastKey < otherLocalsLastKey) {
+      throw CfCodeStackMapValidatingException.error(
+          "Source locals "
+              + MapUtils.toString(sourceLocals)
+              + " have different local indices than "
+              + MapUtils.toString(destLocals));
+    }
+    for (int i = 0; i < otherLocalsLastKey; i++) {
+      final FrameType sourceType =
+          sourceLocals.containsKey(i) ? sourceLocals.get(i) : FrameType.top();
+      final FrameType destinationType =
+          destLocals.containsKey(i) ? destLocals.get(i) : FrameType.top();
+      if (!canBeAssigned(sourceType, destinationType, factory, isJavaAssignable)) {
+        throw CfCodeStackMapValidatingException.error(
+            "Could not assign '"
+                + MapUtils.toString(sourceLocals)
+                + "' to '"
+                + MapUtils.toString(destLocals)
+                + "'. The local at index "
+                + i
+                + " with '"
+                + sourceType
+                + "' not being assignable to '"
+                + destinationType
+                + "'");
+      }
+    }
+  }
+
+  private static void verifyStackIsAssignable(
+      Deque<FrameType> sourceStack,
+      Deque<FrameType> destStack,
+      DexItemFactory factory,
+      BiPredicate<DexType, DexType> isJavaAssignable) {
+    if (sourceStack.size() != destStack.size()) {
+      throw CfCodeStackMapValidatingException.error(
+          "Source stack "
+              + Arrays.toString(sourceStack.toArray())
+              + " and destination stack "
+              + Arrays.toString(destStack.toArray())
+              + " is not the same size");
+    }
+    final Iterator<FrameType> otherIterator = destStack.iterator();
+    int i = 0;
+    for (FrameType sourceType : sourceStack) {
+      final FrameType destinationType = otherIterator.next();
+      if (!canBeAssigned(sourceType, destinationType, factory, isJavaAssignable)) {
+        throw CfCodeStackMapValidatingException.error(
+            "Could not assign '"
+                + Arrays.toString(sourceStack.toArray())
+                + "' to '"
+                + Arrays.toString(destStack.toArray())
+                + "'. The stack value at index "
+                + i
+                + " (from bottom) with '"
+                + sourceType
+                + "' not being assignable to '"
+                + destinationType
+                + "'");
+      }
+      i++;
+    }
+  }
+
+  // Based on https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.2.
+  public static boolean canBeAssigned(
+      FrameType source,
+      FrameType target,
+      DexItemFactory factory,
+      BiPredicate<DexType, DexType> isJavaAssignable) {
+    if (target.isTop()) {
+      return true;
+    }
+    if (source.isTop()) {
+      return false;
+    }
+    if (source.isWide() != target.isWide()) {
+      return false;
+    }
+    if (target.isOneWord() || target.isTwoWord()) {
+      return true;
+    }
+    if (source.isUninitializedThis() && target.isUninitializedThis()) {
+      return true;
+    }
+    if (source.isUninitializedNew() && target.isUninitializedNew()) {
+      // TODO(b/168190134): Allow for picking the offset from the target if not set.
+      DexType uninitializedNewTypeSource = source.getUninitializedNewType();
+      DexType uninitializedNewTypeTarget = target.getUninitializedNewType();
+      return uninitializedNewTypeSource == null
+          || uninitializedNewTypeTarget == null
+          || uninitializedNewTypeSource == uninitializedNewTypeTarget;
+    }
+    // TODO(b/168190267): Clean-up the lattice.
+    if (!source.isInitialized()
+        && target.isInitialized()
+        && target.getInitializedType() == factory.objectType) {
+      return true;
+    }
+    if (source.isInitialized() && target.isInitialized()) {
+      // Both are instantiated types and we resort to primitive tyoe/java type hierarchy checking.
+      return isJavaAssignable.test(source.getInitializedType(), target.getInitializedType());
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index 0cd79d4..afc44f5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -28,6 +30,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.GOTO;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return helper.compareLabels(target, ((CfGoto) other).target);
+  }
+
+  @Override
   public CfGoto asGoto() {
     return this;
   }
@@ -74,4 +86,15 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forJumpInstruction();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    frameBuilder.verifyTarget(target);
+    frameBuilder.setNoFrame();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index 31497fd..fba8f74 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -5,8 +5,10 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -35,6 +37,19 @@
     this.target = target;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getOpcode();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    CfIf otherIf = (CfIf) other;
+    assert kind == otherIf.kind;
+    assert type == otherIf.type;
+    return helper.compareLabels(target, otherIf.target);
+  }
+
   public ValueType getType() {
     return type;
   }
@@ -107,4 +122,18 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forJumpInstruction();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., value →
+    // ...
+    frameBuilder.pop(
+        type.isObject() ? factory.objectType : type.toPrimitiveType().toDexType(factory));
+    frameBuilder.verifyTarget(target);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 47cbcad..d4119ba 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -5,8 +5,10 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -35,6 +37,19 @@
     this.target = target;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getOpcode();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    CfIfCmp otherIf = (CfIfCmp) other;
+    assert kind == otherIf.kind;
+    assert type == otherIf.type;
+    return helper.compareLabels(target, otherIf.target);
+  }
+
   public Type getKind() {
     return kind;
   }
@@ -108,4 +123,19 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forJumpInstruction();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., value1, value2 →
+    // ...
+    DexType type =
+        this.type.isObject() ? factory.objectType : this.type.toPrimitiveType().toDexType(factory);
+    frameBuilder.popAndDiscard(type, type);
+    frameBuilder.verifyTarget(target);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index 4a37601..ab56f05 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -17,7 +19,9 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.Comparator;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class CfIinc extends CfInstruction {
 
@@ -30,6 +34,18 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.IINC;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return Comparator.comparingInt(CfIinc::getLocalIndex)
+        .thenComparing(CfIinc::getIncrement)
+        .compare(this, (CfIinc) other);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -65,4 +81,14 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return ConstraintWithTarget.ALWAYS;
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    frameBuilder.readLocal(var, factory.intType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index b12d10e..70d5b53 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -41,6 +42,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return CfCompareHelper.CONST_CLASS_COMPARE_ID;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return clazz.slowCompareTo(((CfInitClass) other).clazz);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -85,4 +96,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forInitClass(clazz, context);
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., →
+    // ..., value
+    frameBuilder.push(clazz);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index c34d576..d7f5ef5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -35,6 +36,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.INSTANCEOF;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return type.slowCompareTo(other.asInstanceOf().type);
+  }
+
+  @Override
   public boolean isInstanceOf() {
     return true;
   }
@@ -84,4 +95,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forInstanceOf(type, context);
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., objectref →
+    // ..., result
+    frameBuilder.popAndDiscard(factory.objectType).push(factory.intType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index da09f7b..39857bf 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -4,10 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -34,6 +36,34 @@
 
   public abstract void print(CfPrinter printer);
 
+  /**
+   * Base compare id for each instruction.
+   *
+   * <p>The id is required to be unique for each instruction class and define a order on
+   * instructions up to the instructions data payload which is ordered by {@code internalCompareTo}.
+   * Currently we represent the ID using the ASM opcode of the instruction or in case the
+   * instruction is not represented externally, some non-overlapping ID defined in {@code
+   * CfCompareHelper}.
+   */
+  public abstract int getCompareToId();
+
+  /**
+   * Compare two instructions with the same compare id.
+   *
+   * <p>The internal compare may assume to only be called on instructions that have the same
+   * "compare id". Overrides of this method can assume 'other' to be of the same type (as this is a
+   * requirement for the defintion of the "compare id").
+   *
+   * <p>If an instruction is uniquely determined by the "compare id" then the override should simply
+   * call '{@code CfCompareHelper::compareIdUniquelyDeterminesEquality}'.
+   */
+  public abstract int internalCompareTo(CfInstruction other, CfCompareHelper helper);
+
+  public final int compareTo(CfInstruction o, CfCompareHelper helper) {
+    int diff = getCompareToId() - o.getCompareToId();
+    return diff != 0 ? diff : internalCompareTo(o, helper);
+  }
+
   @Override
   public String toString() {
     CfPrinter printer = new CfPrinter();
@@ -145,6 +175,14 @@
     return false;
   }
 
+  public CfThrow asThrow() {
+    return null;
+  }
+
+  public boolean isThrow() {
+    return false;
+  }
+
   public CfDexItemBasedConstString asDexItemBasedConstString() {
     return null;
   }
@@ -158,6 +196,10 @@
     return false;
   }
 
+  public boolean isReturnVoid() {
+    return false;
+  }
+
   /** Return true if this instruction is CfIf or CfIfCmp. */
   public boolean isConditionalJump() {
     return false;
@@ -183,4 +225,11 @@
 
   public abstract ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexProgramClass context);
+
+  public abstract void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens);
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 4f21538..4256db3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -17,7 +18,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
@@ -51,6 +52,18 @@
     this.itf = itf;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getOpcode();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    CfInvoke otherInvoke = other.asInvoke();
+    int itfDiff = Boolean.compare(itf, otherInvoke.itf);
+    return itfDiff != 0 ? itfDiff : method.slowCompareTo(otherInvoke.method);
+  }
+
   public DexMethod getMethod() {
     return method;
   }
@@ -82,9 +95,9 @@
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    GraphLensLookupResult lookup =
+    MethodLookupResult lookup =
         graphLens.lookupMethod(method, context.getReference(), getInvokeType(context));
-    DexMethod rewrittenMethod = lookup.getMethod();
+    DexMethod rewrittenMethod = lookup.getReference();
     String owner = namingLens.lookupInternalName(rewrittenMethod.holder);
     String name = namingLens.lookupName(rewrittenMethod).toString();
     String desc = rewrittenMethod.proto.toDescriptorString(namingLens);
@@ -273,8 +286,8 @@
       case Opcodes.INVOKESTATIC:
         {
           // Static invokes may have changed as a result of horizontal class merging.
-          GraphLensLookupResult lookup = graphLens.lookupMethod(target, null, Type.STATIC);
-          target = lookup.getMethod();
+          MethodLookupResult lookup = graphLens.lookupMethod(target, null, Type.STATIC);
+          target = lookup.getReference();
           type = lookup.getType();
         }
         break;
@@ -292,8 +305,8 @@
           }
 
           // Virtual invokes may have changed to interface invokes as a result of member rebinding.
-          GraphLensLookupResult lookup = graphLens.lookupMethod(target, null, type);
-          target = lookup.getMethod();
+          MethodLookupResult lookup = graphLens.lookupMethod(target, null, type);
+          target = lookup.getReference();
           type = lookup.getType();
         }
         break;
@@ -305,6 +318,29 @@
     return inliningConstraints.forInvoke(target, type, context);
   }
 
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., objectref, [arg1, [arg2 ...]] →
+    // ... [ returnType ]
+    // OR, for static method calls:
+    // ..., [arg1, [arg2 ...]] →
+    // ...
+    frameBuilder.popAndDiscard(this.method.proto.parameters.values);
+    if (opcode == Opcodes.INVOKESPECIAL && method.isInstanceInitializer(factory)) {
+      frameBuilder.popAndInitialize(context, method.holder);
+    } else if (opcode != Opcodes.INVOKESTATIC) {
+      frameBuilder.pop(method.holder);
+    }
+    if (this.method.proto.returnType != factory.voidType) {
+      frameBuilder.push(this.method.proto.returnType);
+    }
+  }
+
   private static boolean noNeedToUseGraphLens(
       DexMethod method, Invoke.Type type, GraphLens graphLens) {
     assert graphLens.lookupMethod(method, null, type).getType() == type;
@@ -338,9 +374,8 @@
   }
 
   private DexEncodedMethod lookupMethodOnHolder(AppView<?> appView, DexMethod method) {
-    GraphLensLookupResult lookupResult =
-        appView.graphLens().lookupMethod(method, method, Type.DIRECT);
-    DexMethod rewrittenMethod = lookupResult.getMethod();
+    MethodLookupResult lookupResult = appView.graphLens().lookupMethod(method, method, Type.DIRECT);
+    DexMethod rewrittenMethod = lookupResult.getReference();
     // Directly lookup the program type for holder. This bypasses lookup order as well as looks
     // directly on the application data, which bypasses and indirection or validation.
     DexProgramClass clazz = appView.appInfo().unsafeDirectProgramTypeLookup(rewrittenMethod.holder);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index a455305..b151132 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -29,6 +30,7 @@
 import java.util.List;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
 public class CfInvokeDynamic extends CfInstruction {
@@ -40,6 +42,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.INVOKEDYNAMIC;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return callSite.compareTo(((CfInvokeDynamic) other).callSite);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -130,4 +142,19 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forInvokeCustom();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., [arg1, [arg2 ...]] →
+    // ...
+    frameBuilder.popAndDiscard(callSite.methodProto.parameters.values);
+    if (callSite.methodProto.returnType != factory.voidType) {
+      frameBuilder.push(callSite.methodProto.returnType);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
index 47ee09b..75f1a0f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -5,8 +5,11 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.CfCodeStackMapValidatingException;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -33,6 +36,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    throw error();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    throw error();
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -60,6 +73,18 @@
     throw error();
   }
 
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // JSR/RET instructions cannot be verified since we have not type-checking way for addresses
+    // on the stack/locals. We have to abandon.
+    throw CfCodeStackMapValidatingException.error("Unexpected JSR/RET instruction");
+  }
+
   public int getLocal() {
     return local;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index b27fd00..af69cbb 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -31,6 +33,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return CfCompareHelper.LABEL_COMPARE_ID;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return helper.compareLabels(this, other.asLabel());
+  }
+
+  @Override
   public CfLabel asLabel() {
     return this;
   }
@@ -72,4 +84,14 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return ConstraintWithTarget.ALWAYS;
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // This is a no-op.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 17e0ecd..591b608 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -5,8 +5,10 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -32,6 +34,16 @@
     this.type = type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getLoadType();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return Integer.compare(var, other.asLoad().var);
+  }
+
   private int getLoadType() {
     switch (type) {
       case OBJECT:
@@ -101,4 +113,19 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forLoad();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., objectref
+    frameBuilder.push(
+        frameBuilder.readLocal(
+            getLocalIndex(),
+            type.isObject() ? factory.objectType : type.toPrimitiveType().toDexType(factory)));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index 19a3bfe..3f487f8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -4,9 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -44,6 +47,16 @@
     this.type = type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getAsmOpcode();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
   public NumericType getType() {
     return type;
   }
@@ -153,4 +166,27 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forBinop();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., value1, value2 →
+    // ..., result
+    FrameType value1Type = FrameType.fromNumericType(type, factory);
+    FrameType value2Type;
+    switch (opcode) {
+      case And:
+      case Or:
+      case Xor:
+        value2Type = value1Type;
+        break;
+      default:
+        value2Type = FrameType.initialized(factory.intType);
+    }
+    frameBuilder.popAndDiscard(value1Type, value2Type).push(value1Type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index 24b580b..8515ece 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -4,8 +4,11 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -34,6 +37,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return getAsmOpcode();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -42,7 +55,11 @@
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    visitor.visitInsn(type == Type.ENTER ? Opcodes.MONITORENTER : Opcodes.MONITOREXIT);
+    visitor.visitInsn(getAsmOpcode());
+  }
+
+  private int getAsmOpcode() {
+    return type == Type.ENTER ? Opcodes.MONITORENTER : Opcodes.MONITOREXIT;
   }
 
   @Override
@@ -66,4 +83,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forMonitor();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., objectref →
+    // ...
+    frameBuilder.pop(FrameType.initialized(factory.objectType));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 33f4244..b00a3fe 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -20,7 +21,9 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.Comparator;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class CfMultiANewArray extends CfInstruction {
 
@@ -41,6 +44,18 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.MULTIANEWARRAY;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return Comparator.comparingInt(CfMultiANewArray::getDimensions)
+        .thenComparing(CfMultiANewArray::getType, DexType::slowCompareTo)
+        .compare(this, ((CfMultiANewArray) other));
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -81,4 +96,19 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forInvokeMultiNewArray(type, context);
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., count1, [count2, ...] →
+    // ..., arrayref
+    for (int i = 0; i < dimensions; i++) {
+      frameBuilder.pop(factory.intType);
+    }
+    frameBuilder.push(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index 88274b3..37c37f4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -4,9 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -35,6 +38,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return getAsmOpcode();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -95,4 +108,17 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forUnop();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., value →
+    // ..., result
+    FrameType frameType = FrameType.fromNumericType(type, factory);
+    frameBuilder.popAndDiscard(frameType).push(frameType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index 83b7e63..b4939b6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -35,6 +37,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.NEW;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return type.slowCompareTo(((CfNew) other).type);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -72,4 +84,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forNewInstance(type, context);
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., objectref
+    frameBuilder.push(FrameType.uninitializedNew(new CfLabel(), type));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 3c9a746..41f2029 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -38,6 +39,16 @@
     return type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return type.isPrimitiveArrayType() ? Opcodes.NEWARRAY : Opcodes.ANEWARRAY;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return type.slowCompareTo(((CfNewArray) other).type);
+  }
+
   private int getPrimitiveTypeCode() {
     switch (type.descriptor.content[1]) {
       case 'Z':
@@ -123,4 +134,17 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forNewArrayEmpty(type, context);
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., count →
+    // ..., arrayref
+    assert type.isArrayType();
+    frameBuilder.popAndDiscard(factory.intType).push(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index e2b6044..5d6205a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -22,6 +24,16 @@
 public class CfNop extends CfInstruction {
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.NOP;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -53,4 +65,14 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return ConstraintWithTarget.ALWAYS;
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // This is an actual Nop.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index ce3ab3b..6c82b1d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -4,9 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -36,6 +39,16 @@
     this.to = to;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getAsmOpcode();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
   public NumericType getFromType() {
     return from;
   }
@@ -53,7 +66,7 @@
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    visitor.visitInsn(this.getAsmOpcode());
+    visitor.visitInsn(getAsmOpcode());
   }
 
   @Override
@@ -166,4 +179,18 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forUnop();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., value →
+    // ..., result
+    frameBuilder
+        .popAndDiscard(FrameType.fromNumericType(from, factory))
+        .push(FrameType.fromNumericType(to, factory));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index e30be70..fe22ae9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -30,6 +32,18 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return CfCompareHelper.POSITION_COMPARE_ID;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    CfPosition otherPosition = (CfPosition) other;
+    int lineDiff = position.line - otherPosition.position.line;
+    return lineDiff != 0 ? lineDiff : helper.compareLabels(label, otherPosition.label);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -81,4 +95,14 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return ConstraintWithTarget.ALWAYS;
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // This is a no-op.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index c5198d1..e1cebfd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -5,8 +5,10 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -34,6 +36,16 @@
     return type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getOpcode();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
   private int getOpcode() {
     switch (type) {
       case INT:
@@ -89,4 +101,16 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forReturn();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    assert returnType != null;
+    frameBuilder.popAndDiscard(returnType);
+    frameBuilder.setNoFrame();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index 8f7bb37..5a7e864 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -27,6 +29,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.RETURN;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -49,6 +61,11 @@
   }
 
   @Override
+  public boolean isReturnVoid() {
+    return true;
+  }
+
+  @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     builder.addReturn();
   }
@@ -58,4 +75,14 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forReturn();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    frameBuilder.setNoFrame();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index a7a39b6..205808f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -4,10 +4,13 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -79,6 +82,16 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return opcode.opcode;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -329,4 +342,167 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return ConstraintWithTarget.ALWAYS;
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+
+    switch (opcode) {
+      case Pop:
+        // ..., value →
+        // ...
+        frameBuilder.pop(FrameType.oneWord());
+        return;
+      case Pop2:
+        // ..., value2, value1 →
+        // ...
+        // or, for double and long:
+        // ..., value →
+        // ...
+        final FrameType pop = frameBuilder.pop();
+        if (!pop.isWide()) {
+          frameBuilder.verifyIsAssignable(pop, FrameType.oneWord());
+          frameBuilder.pop(FrameType.oneWord());
+        }
+        return;
+      case Dup:
+        {
+          // ..., value →
+          // ..., value, value
+          FrameType topValue = frameBuilder.pop(FrameType.oneWord());
+          frameBuilder.push(topValue).push(topValue);
+          return;
+        }
+      case DupX1:
+        {
+          // ..., value2, value1 →
+          // ..., value1, value2, value1
+          FrameType value1 = frameBuilder.pop(FrameType.oneWord());
+          FrameType value2 = frameBuilder.pop(FrameType.oneWord());
+          frameBuilder.push(value1).push(value2).push(value1);
+          return;
+        }
+      case DupX2:
+        {
+          // ..., value3, value2, value1 →
+          // ..., value1, value3, value2, value1
+          // or, if value2 is double or long:
+          // ..., value2, value1 →
+          // ..., value1, value2, value1
+          FrameType value1 = frameBuilder.pop(FrameType.oneWord());
+          FrameType value2 = frameBuilder.pop();
+          if (!value2.isWide()) {
+            frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+            FrameType value3 = frameBuilder.pop(FrameType.oneWord());
+            frameBuilder.push(value1).push(value3);
+          } else {
+            frameBuilder.push(value1);
+          }
+          frameBuilder.push(value2).push(value1);
+          return;
+        }
+      case Dup2:
+        {
+          // ..., value2, value1 →
+          // ..., value2, value1, value2, value1
+          // or, for value1 being long or double:
+          // ..., value →
+          // ..., value, value
+          FrameType value1 = frameBuilder.pop();
+          if (!value1.isWide()) {
+            frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
+            FrameType value2 = frameBuilder.pop(FrameType.oneWord());
+            frameBuilder.push(value2).push(value1).push(value2);
+          } else {
+            frameBuilder.push(value1);
+          }
+          frameBuilder.push(value1);
+          return;
+        }
+      case Dup2X1:
+        {
+          // ..., value3, value2, value1 →
+          // ..., value2, value1, value3, value2, value1
+          // or, for value1 being a long or double:
+          // ..., value2, value1 →
+          // ..., value1, value2, value1
+          FrameType value1 = frameBuilder.pop();
+          FrameType value2 = frameBuilder.pop(FrameType.oneWord());
+          if (!value1.isWide()) {
+            frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
+            FrameType value3 = frameBuilder.pop(FrameType.oneWord());
+            frameBuilder.push(value2).push(value1).push(value3);
+          } else {
+            frameBuilder.push(value1);
+          }
+          frameBuilder.push(value2);
+          frameBuilder.push(value1);
+          return;
+        }
+      case Dup2X2:
+        {
+          // (1)
+          // ..., value4, value3, value2, value1 →
+          // ..., value2, value1, value4, value3, value2, value1
+          // (2) OR, if value1 is long or double
+          // ..., value3, value2, value1 →
+          // ..., value1, value3, value2, value1
+          // (3) OR if value3 is long or double
+          // ..., value3, value2, value1 →
+          // ..., value2, value1, value3, value2, value1
+          // (4) OR, where value1 and value2 is double or long:
+          // ..., value2, value1 →
+          // ..., value1, value2, value1
+          FrameType value1 = frameBuilder.pop();
+          FrameType value2 = frameBuilder.pop();
+          if (!value1.isWide()) {
+            FrameType value3 = frameBuilder.pop();
+            if (!value3.isWide()) {
+              // (1)
+              frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
+              frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+              frameBuilder.verifyIsAssignable(value3, FrameType.oneWord());
+              FrameType value4 = frameBuilder.pop(FrameType.oneWord());
+              frameBuilder
+                  .push(value2)
+                  .push(value1)
+                  .push(value4)
+                  .push(value3)
+                  .push(value2)
+                  .push(value1);
+            } else {
+              // (3)
+              frameBuilder.verifyIsAssignable(value1, FrameType.oneWord());
+              frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+              frameBuilder.push(value2).push(value1).push(value3).push(value2).push(value1);
+            }
+          } else if (!value2.isWide()) {
+            // (2)
+            frameBuilder.verifyIsAssignable(value2, FrameType.oneWord());
+            FrameType value3 = frameBuilder.pop(FrameType.oneWord());
+            frameBuilder.push(value1).push(value3).push(value2).push(value1);
+          } else {
+            // (4)
+            assert value2.isWide();
+            frameBuilder.push(value1).push(value2).push(value1);
+          }
+          return;
+        }
+      case Swap:
+        {
+          // ..., value2, value1 →
+          // ..., value1, value2
+          FrameType value1 = frameBuilder.pop(FrameType.oneWord());
+          FrameType value2 = frameBuilder.pop(FrameType.oneWord());
+          frameBuilder.push(value1).push(value2);
+          return;
+        }
+      default:
+        throw new Unreachable("Invalid opcode for CfStackInstruction");
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index d46d3a3..1856c84 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -4,9 +4,12 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -32,6 +35,16 @@
     this.type = type;
   }
 
+  @Override
+  public int getCompareToId() {
+    return getStoreType();
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return Integer.compare(var, other.asStore().var);
+  }
+
   private int getStoreType() {
     switch (type) {
       case OBJECT:
@@ -100,4 +113,42 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forStore();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., ref →
+    // ...
+    FrameType pop = frameBuilder.pop();
+    switch (type) {
+      case OBJECT:
+        frameBuilder.verifyIsAssignable(pop, factory.objectType);
+        frameBuilder.storeLocal(var, pop);
+        return;
+      case INT:
+        frameBuilder.verifyIsAssignable(pop, factory.intType);
+        frameBuilder.storeLocal(var, FrameType.initialized(factory.intType));
+        return;
+      case FLOAT:
+        frameBuilder.verifyIsAssignable(pop, factory.floatType);
+        frameBuilder.storeLocal(var, FrameType.initialized(factory.floatType));
+        return;
+      case LONG:
+        frameBuilder.verifyIsAssignable(pop, factory.longType);
+        frameBuilder.storeLocal(var, FrameType.initialized(factory.longType));
+        frameBuilder.storeLocal(var + 1, FrameType.initialized(factory.longType));
+        return;
+      case DOUBLE:
+        frameBuilder.verifyIsAssignable(pop, factory.doubleType);
+        frameBuilder.storeLocal(var, FrameType.initialized(factory.doubleType));
+        frameBuilder.storeLocal(var + 1, FrameType.initialized(factory.doubleType));
+        return;
+      default:
+        throw new Unreachable("Unexpected type " + type);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index edc5244..62effd7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -3,9 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import static com.android.tools.r8.utils.ComparatorUtils.listComparator;
+
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -17,11 +21,14 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.ComparatorUtils;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.Comparator;
 import java.util.List;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class CfSwitch extends CfInstruction {
 
@@ -41,6 +48,20 @@
     assert kind != Kind.TABLE || keys.length == 1;
   }
 
+  @Override
+  public int getCompareToId() {
+    return kind == Kind.LOOKUP ? Opcodes.LOOKUPSWITCH : Opcodes.TABLESWITCH;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    assert kind == ((CfSwitch) other).kind;
+    return Comparator.comparing(CfSwitch::getDefaultTarget, helper::compareLabels)
+        .thenComparing(insn -> insn.keys, ComparatorUtils::compareIntArray)
+        .thenComparing(CfSwitch::getSwitchTargets, listComparator(helper::compareLabels))
+        .compare(this, (CfSwitch) other);
+  }
+
   public Kind getKind() {
     return kind;
   }
@@ -117,4 +138,20 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forJumpInstruction();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., index/key →
+    // ...
+    frameBuilder.pop(factory.intType);
+    frameBuilder.verifyTarget(defaultTarget);
+    for (CfLabel target : targets) {
+      frameBuilder.verifyTarget(target);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index aaa7280..b8b31b1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -28,6 +30,26 @@
   }
 
   @Override
+  public int getCompareToId() {
+    return Opcodes.ATHROW;
+  }
+
+  @Override
+  public int internalCompareTo(CfInstruction other, CfCompareHelper helper) {
+    return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
+  @Override
+  public boolean isThrow() {
+    return true;
+  }
+
+  @Override
+  public CfThrow asThrow() {
+    return this;
+  }
+
+  @Override
   public void write(
       ProgramMethod context,
       DexItemFactory dexItemFactory,
@@ -60,4 +82,18 @@
       InliningConstraints inliningConstraints, DexProgramClass context) {
     return inliningConstraints.forJumpInstruction();
   }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ..., objectref →
+    // objectref
+    frameBuilder.pop(factory.throwableType);
+    // The exception edges are verified in CfCode since this is a throwing instruction.
+    frameBuilder.setNoFrame();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
index 5c1aa30..8e9752b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
@@ -3,11 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.code;
 
+import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 
 public class CfTryCatch {
@@ -43,4 +46,12 @@
     }
     return new CfTryCatch(start, end, guards, targets);
   }
+
+  public int compareTo(CfTryCatch other, CfCompareHelper helper) {
+    return Comparator.comparing((CfTryCatch c) -> c.start, helper::compareLabels)
+        .thenComparing(c -> c.end, helper::compareLabels)
+        .thenComparing(c -> c.guards, ComparatorUtils.listComparator(DexType::slowCompareTo))
+        .thenComparing(c -> c.targets, ComparatorUtils.listComparator(helper::compareLabels))
+        .compare(this, other);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/CheckCast.java b/src/main/java/com/android/tools/r8/code/CheckCast.java
index 6ca2dc5..06edf90 100644
--- a/src/main/java/com/android/tools/r8/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -6,11 +6,13 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public class CheckCast extends Format21c<DexType> {
 
@@ -42,6 +44,11 @@
   }
 
   @Override
+  int internalCompareBBBB(Format21c<?> other) {
+    return BBBB.slowCompareTo((DexType) other.BBBB);
+  }
+
+  @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems,
       ProgramMethod context,
@@ -52,6 +59,18 @@
   }
 
   @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    writeFirst(AA, dest);
+    write16BitReference(rewritten, dest, mapping);
+  }
+
+  @Override
   public CheckCast asCheckCast() {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index ec8a13a..3c6d03e 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -6,11 +6,13 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public class ConstClass extends Format21c<DexType> {
 
@@ -27,6 +29,11 @@
   }
 
   @Override
+  int internalCompareBBBB(Format21c<?> other) {
+    return BBBB.slowCompareTo((DexType) other.BBBB);
+  }
+
+  @Override
   public String getName() {
     return NAME;
   }
@@ -52,6 +59,18 @@
   }
 
   @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    writeFirst(AA, dest);
+    write16BitReference(rewritten, dest, mapping);
+  }
+
+  @Override
   public void registerUse(UseRegistry registry) {
     registry.registerConstClass(getType());
   }
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
index 75fc2dc..d69329a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodHandle.java
@@ -51,6 +51,11 @@
   }
 
   @Override
+  int internalCompareBBBB(Format21c<?> other) {
+    return BBBB.slowCompareTo((DexMethodHandle) other.BBBB);
+  }
+
+  @Override
   public String toString(ClassNameMapper naming) {
     return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
index 4767025..ba6e89f 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
@@ -50,6 +50,11 @@
   }
 
   @Override
+  int internalCompareBBBB(Format21c<?> other) {
+    return BBBB.slowCompareTo((DexProto) other.BBBB);
+  }
+
+  @Override
   public String toString(ClassNameMapper naming) {
     return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
diff --git a/src/main/java/com/android/tools/r8/code/ConstString.java b/src/main/java/com/android/tools/r8/code/ConstString.java
index 8bccec9..0b44f8a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/code/ConstString.java
@@ -34,6 +34,11 @@
   }
 
   @Override
+  int internalCompareBBBB(Format21c<?> other) {
+    return BBBB.slowCompareTo((DexString) other.BBBB);
+  }
+
+  @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems,
       ProgramMethod context,
@@ -88,7 +93,8 @@
     if (index != (index & 0xffff)) {
       throw new InternalCompilerError("String-index overflow.");
     }
-    super.write(dest, context, graphLens, mapping, rewriter);
+    writeFirst(AA, dest);
+    write16BitReference(BBBB, dest, mapping);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/DexCompareHelper.java b/src/main/java/com/android/tools/r8/code/DexCompareHelper.java
new file mode 100644
index 0000000..0587c08
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/DexCompareHelper.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.code;
+
+public class DexCompareHelper {
+
+  // Integer constants to ensure that there is a well order for all DEX instructions including
+  // virtual instructions represented in our internal encoding.
+  static final int INIT_CLASS_COMPARE_ID;
+  static final int DEX_ITEM_CONST_STRING_COMPARE_ID;
+
+  private static int HIGHEST_DEX_OPCODE = 0xFF;
+
+  static {
+    int lastId = HIGHEST_DEX_OPCODE;
+    INIT_CLASS_COMPARE_ID = ++lastId;
+    DEX_ITEM_CONST_STRING_COMPARE_ID = ++lastId;
+  }
+
+  // Helper to signal that the concrete instruction is uniquely determined by its ID/opcode.
+  public static int compareIdUniquelyDeterminesEquality(
+      Instruction instruction1, Instruction instruction2) {
+    assert instruction1.getClass() == instruction2.getClass();
+    assert instruction1.getCompareToId() == instruction2.getCompareToId();
+    assert instruction1.toString().equals(instruction2.toString());
+    return 0;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/DexInitClass.java b/src/main/java/com/android/tools/r8/code/DexInitClass.java
index 52cd9e1..2d9cda9 100644
--- a/src/main/java/com/android/tools/r8/code/DexInitClass.java
+++ b/src/main/java/com/android/tools/r8/code/DexInitClass.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
+import java.util.Comparator;
 
 public class DexInitClass extends Base2Format {
 
@@ -71,6 +72,11 @@
     throw new Unreachable();
   }
 
+  @Override
+  int getCompareToId() {
+    return DexCompareHelper.INIT_CLASS_COMPARE_ID;
+  }
+
   private int getOpcode(DexField field) {
     FieldMemberType type = FieldMemberType.fromDexType(field.type);
     switch (type) {
@@ -121,12 +127,10 @@
   }
 
   @Override
-  public boolean equals(Object other) {
-    if (other == null || getClass() != other.getClass()) {
-      return false;
-    }
-    DexInitClass initClass = (DexInitClass) other;
-    return dest == initClass.dest && clazz == initClass.clazz;
+  final int internalCompareTo(Instruction other) {
+    return Comparator.comparingInt((DexInitClass i) -> i.dest)
+        .thenComparing(i -> i.clazz, DexType::slowCompareTo)
+        .compare(this, (DexInitClass) other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
index c50bba8..18bdfd3 100644
--- a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
@@ -63,6 +63,16 @@
   }
 
   @Override
+  int getCompareToId() {
+    return DexCompareHelper.DEX_ITEM_CONST_STRING_COMPARE_ID;
+  }
+
+  @Override
+  int internalCompareBBBB(Format21c<?> other) {
+    return BBBB.referenceCompareTo(((DexItemBasedConstString) other).BBBB);
+  }
+
+  @Override
   public DexItemBasedConstString asDexItemBasedConstString() {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java b/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
index 20d412f..52d3e07 100644
--- a/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
+++ b/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
@@ -9,9 +9,11 @@
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
 import java.util.Arrays;
+import java.util.Comparator;
 
 public class FillArrayDataPayload extends Nop {
 
@@ -60,13 +62,11 @@
   }
 
   @Override
-  public boolean equals(Object other) {
-    if (!super.equals(other)) {
-      return false;
-    }
-    FillArrayDataPayload that = (FillArrayDataPayload) other;
-    return size == that.size && element_width == that.element_width
-        && Arrays.equals(data, that.data);
+  final int internalCompareTo(Instruction other) {
+    return Comparator.comparingInt((FillArrayDataPayload i) -> i.element_width)
+        .thenComparingLong(i -> i.size)
+        .thenComparing(i -> i.data, ComparatorUtils::compareShortArray)
+        .compare(this, (FillArrayDataPayload) other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArray.java b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
index 5198876..b432eec 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArray.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArray.java
@@ -43,6 +43,11 @@
   }
 
   @Override
+  int internalCompareBBBB(Format35c<?> other) {
+    return BBBB.slowCompareTo((DexType) other.BBBB);
+  }
+
+  @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems,
       ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
index de651f5..13dc9a5 100644
--- a/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
+++ b/src/main/java/com/android/tools/r8/code/FilledNewArrayRange.java
@@ -43,6 +43,11 @@
   }
 
   @Override
+  int internalCompareBBBB(Format3rc<?> other) {
+    return BBBB.slowCompareTo((DexType) other.BBBB);
+  }
+
+  @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems,
       ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/code/Format10t.java b/src/main/java/com/android/tools/r8/code/Format10t.java
index d34ecb2..528ef92 100644
--- a/src/main/java/com/android/tools/r8/code/Format10t.java
+++ b/src/main/java/com/android/tools/r8/code/Format10t.java
@@ -43,11 +43,8 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
-    return ((Format10t) other).AA == AA;
+  final int internalCompareTo(Instruction other) {
+    return Byte.compare(AA, ((Format10t) other).AA);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format10x.java b/src/main/java/com/android/tools/r8/code/Format10x.java
index 6ffc866..bc8feed 100644
--- a/src/main/java/com/android/tools/r8/code/Format10x.java
+++ b/src/main/java/com/android/tools/r8/code/Format10x.java
@@ -33,16 +33,6 @@
   }
 
   @Override
-  public int hashCode() {
-    return getClass().hashCode();
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    return (other != null) && (this.getClass() == other.getClass());
-  }
-
-  @Override
   public String toString(ClassNameMapper naming) {
     return formatString(null);
   }
diff --git a/src/main/java/com/android/tools/r8/code/Format11n.java b/src/main/java/com/android/tools/r8/code/Format11n.java
index 933beba..61d845e 100644
--- a/src/main/java/com/android/tools/r8/code/Format11n.java
+++ b/src/main/java/com/android/tools/r8/code/Format11n.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 abstract class Format11n extends Base1Format {
@@ -52,12 +53,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format11n o = (Format11n) other;
-    return o.A == A && o.B == B;
+    return ComparatorUtils.compareInts(A, o.A, B, o.B);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format11x.java b/src/main/java/com/android/tools/r8/code/Format11x.java
index 5d627bd..4509fbd 100644
--- a/src/main/java/com/android/tools/r8/code/Format11x.java
+++ b/src/main/java/com/android/tools/r8/code/Format11x.java
@@ -43,11 +43,8 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
-    return ((Format11x) other).AA == AA;
+  final int internalCompareTo(Instruction other) {
+    return Short.compare(AA, ((Format11x) other).AA);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format12x.java b/src/main/java/com/android/tools/r8/code/Format12x.java
index 36284b9..db7ca5a 100644
--- a/src/main/java/com/android/tools/r8/code/Format12x.java
+++ b/src/main/java/com/android/tools/r8/code/Format12x.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 abstract class Format12x extends Base1Format {
@@ -46,14 +47,12 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format12x o = (Format12x) other;
-    return o.A == A && o.B == B;
+    return ComparatorUtils.compareInts(A, o.A, B, o.B);
   }
 
+
   @Override
   public String toString(ClassNameMapper naming) {
     return formatString("v" + A + ", v" + B);
diff --git a/src/main/java/com/android/tools/r8/code/Format20t.java b/src/main/java/com/android/tools/r8/code/Format20t.java
index 42d172a..8f34250 100644
--- a/src/main/java/com/android/tools/r8/code/Format20t.java
+++ b/src/main/java/com/android/tools/r8/code/Format20t.java
@@ -43,11 +43,8 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
-    return ((Format20t) other).AAAA == AAAA;
+  final int internalCompareTo(Instruction other) {
+    return Short.compare(AAAA, ((Format20t) other).AAAA);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format21c.java b/src/main/java/com/android/tools/r8/code/Format21c.java
index 9118072..074b79a 100644
--- a/src/main/java/com/android/tools/r8/code/Format21c.java
+++ b/src/main/java/com/android/tools/r8/code/Format21c.java
@@ -4,13 +4,8 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.IndexedDexItem;
-import com.android.tools.r8.graph.ObjectToOffsetMapping;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
-import java.nio.ShortBuffer;
 import java.util.function.BiPredicate;
 
 abstract class Format21c<T extends IndexedDexItem> extends Base2Format {
@@ -32,30 +27,19 @@
   }
 
   @Override
-  public void write(
-      ShortBuffer dest,
-      ProgramMethod context,
-      GraphLens graphLens,
-      ObjectToOffsetMapping mapping,
-      LensCodeRewriterUtils rewriter) {
-    writeFirst(AA, dest);
-    write16BitReference(BBBB, dest, mapping);
-  }
-
-  @Override
   public final int hashCode() {
     return ((BBBB.hashCode() << 8) | AA) ^ getClass().hashCode();
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format21c<?> o = (Format21c<?>) other;
-    return o.AA == AA && o.BBBB.equals(BBBB);
+    int aaDiff = Short.compare(AA, o.AA);
+    return aaDiff != 0 ? aaDiff : internalCompareBBBB(o);
   }
 
+  abstract int internalCompareBBBB(Format21c<?> other);
+
   @Override
   public String toString(ClassNameMapper naming) {
     return formatString(
diff --git a/src/main/java/com/android/tools/r8/code/Format21h.java b/src/main/java/com/android/tools/r8/code/Format21h.java
index 848990d..537a020 100644
--- a/src/main/java/com/android/tools/r8/code/Format21h.java
+++ b/src/main/java/com/android/tools/r8/code/Format21h.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 abstract class Format21h extends Base2Format {
@@ -47,12 +48,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format21h o = (Format21h) other;
-    return o.AA == AA && o.BBBB == BBBB;
+    return ComparatorUtils.compareInts(AA, o.AA, BBBB, o.BBBB);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format21s.java b/src/main/java/com/android/tools/r8/code/Format21s.java
index 31c4bc9..3f4e0fc 100644
--- a/src/main/java/com/android/tools/r8/code/Format21s.java
+++ b/src/main/java/com/android/tools/r8/code/Format21s.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
 
@@ -49,12 +50,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format21s o = (Format21s) other;
-    return o.AA == AA && o.BBBB == BBBB;
+    return ComparatorUtils.compareInts(AA, o.AA, BBBB, o.BBBB);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format21t.java b/src/main/java/com/android/tools/r8/code/Format21t.java
index 5e52bac..e580a1f 100644
--- a/src/main/java/com/android/tools/r8/code/Format21t.java
+++ b/src/main/java/com/android/tools/r8/code/Format21t.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 public abstract class Format21t extends Base2Format {
@@ -51,12 +52,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format21t o = (Format21t) other;
-    return o.AA == AA && o.BBBB == BBBB;
+    return ComparatorUtils.compareInts(AA, o.AA, BBBB, o.BBBB);
   }
 
   public abstract Type getType();
diff --git a/src/main/java/com/android/tools/r8/code/Format22b.java b/src/main/java/com/android/tools/r8/code/Format22b.java
index 48955dc..5bbd0da 100644
--- a/src/main/java/com/android/tools/r8/code/Format22b.java
+++ b/src/main/java/com/android/tools/r8/code/Format22b.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
 
@@ -53,12 +54,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format22b o = (Format22b) other;
-    return o.AA == AA && o.BB == BB && o.CC == CC;
+    return ComparatorUtils.compareInts(AA, o.AA, BB, o.BB, CC, o.CC);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format22c.java b/src/main/java/com/android/tools/r8/code/Format22c.java
index 0ff7841..bd16c8f 100644
--- a/src/main/java/com/android/tools/r8/code/Format22c.java
+++ b/src/main/java/com/android/tools/r8/code/Format22c.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.util.function.BiPredicate;
 
 public abstract class Format22c<T extends DexReference> extends Base2Format {
@@ -37,12 +38,10 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
-    Format22c<?> o = (Format22c<?>) other;
-    return o.A == A && o.B == B && o.CCCC.equals(CCCC);
+  final int internalCompareTo(Instruction other) {
+    Format22c<? extends DexReference> o = (Format22c<? extends DexReference>) other;
+    int diff = ComparatorUtils.compareInts(A, o.A, B, o.B);
+    return diff != 0 ? diff : CCCC.referenceCompareTo(o.CCCC);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format22s.java b/src/main/java/com/android/tools/r8/code/Format22s.java
index 1b9aeb4..8492eae 100644
--- a/src/main/java/com/android/tools/r8/code/Format22s.java
+++ b/src/main/java/com/android/tools/r8/code/Format22s.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
 
@@ -53,12 +54,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format22s o = (Format22s) other;
-    return o.A == A && o.B == B && o.CCCC == CCCC;
+    return ComparatorUtils.compareInts(A, o.A, B, o.B, CCCC, o.CCCC);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format22t.java b/src/main/java/com/android/tools/r8/code/Format22t.java
index 4e75d23..5c64988 100644
--- a/src/main/java/com/android/tools/r8/code/Format22t.java
+++ b/src/main/java/com/android/tools/r8/code/Format22t.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 public abstract class Format22t extends Base2Format {
@@ -55,12 +56,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format22t o = (Format22t) other;
-    return o.A == A && o.B == B && o.CCCC == CCCC;
+    return ComparatorUtils.compareInts(A, o.A, B, o.B, CCCC, o.CCCC);
   }
 
   public abstract Type getType();
diff --git a/src/main/java/com/android/tools/r8/code/Format22x.java b/src/main/java/com/android/tools/r8/code/Format22x.java
index 901af1e..626e7f9 100644
--- a/src/main/java/com/android/tools/r8/code/Format22x.java
+++ b/src/main/java/com/android/tools/r8/code/Format22x.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 abstract class Format22x extends Base2Format {
@@ -48,14 +49,12 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format22x o = (Format22x) other;
-    return o.AA == AA && o.BBBB == BBBB;
+    return ComparatorUtils.compareInts(AA, o.AA, BBBB, o.BBBB);
   }
 
+
   @Override
   public String toString(ClassNameMapper naming) {
     return formatString("v" + AA + ", v" + (int)BBBB);
diff --git a/src/main/java/com/android/tools/r8/code/Format23x.java b/src/main/java/com/android/tools/r8/code/Format23x.java
index e9d030b..a87dddf 100644
--- a/src/main/java/com/android/tools/r8/code/Format23x.java
+++ b/src/main/java/com/android/tools/r8/code/Format23x.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 abstract class Format23x extends Base2Format {
@@ -52,12 +53,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format23x o = (Format23x) other;
-    return o.AA == AA && o.BB == BB && o.CC == CC;
+    return ComparatorUtils.compareInts(AA, o.AA, BB, o.BB, CC, o.CC);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format30t.java b/src/main/java/com/android/tools/r8/code/Format30t.java
index dcf5064..81dcf86 100644
--- a/src/main/java/com/android/tools/r8/code/Format30t.java
+++ b/src/main/java/com/android/tools/r8/code/Format30t.java
@@ -42,11 +42,8 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
-    return ((Format30t) other).AAAAAAAA == AAAAAAAA;
+  final int internalCompareTo(Instruction other) {
+    return Integer.compare(AAAAAAAA, ((Format30t) other).AAAAAAAA);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format31c.java b/src/main/java/com/android/tools/r8/code/Format31c.java
index 023aeac..a1ae51e 100644
--- a/src/main/java/com/android/tools/r8/code/Format31c.java
+++ b/src/main/java/com/android/tools/r8/code/Format31c.java
@@ -51,12 +51,10 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format31c o = (Format31c) other;
-    return o.AA == AA && o.BBBBBBBB.equals(BBBBBBBB);
+    int diff = Short.compare(AA, o.AA);
+    return diff != 0 ? diff : BBBBBBBB.slowCompareTo(o.BBBBBBBB);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format31i.java b/src/main/java/com/android/tools/r8/code/Format31i.java
index 843319b..3d0d27e 100644
--- a/src/main/java/com/android/tools/r8/code/Format31i.java
+++ b/src/main/java/com/android/tools/r8/code/Format31i.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 abstract class Format31i extends Base3Format {
@@ -47,12 +48,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format31i o = (Format31i) other;
-    return o.AA == AA && o.BBBBBBBB == BBBBBBBB;
+    return ComparatorUtils.compareInts(AA, o.AA, BBBBBBBB, o.BBBBBBBB);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format31t.java b/src/main/java/com/android/tools/r8/code/Format31t.java
index 9c7fdfd..8e36d79 100644
--- a/src/main/java/com/android/tools/r8/code/Format31t.java
+++ b/src/main/java/com/android/tools/r8/code/Format31t.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 public abstract class Format31t extends Base3Format {
@@ -62,12 +63,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format31t o = (Format31t) other;
-    return o.AA == AA && o.BBBBBBBB == BBBBBBBB;
+    return ComparatorUtils.compareInts(AA, o.AA, BBBBBBBB, o.BBBBBBBB);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format32x.java b/src/main/java/com/android/tools/r8/code/Format32x.java
index 34ecf4f..a5360db 100644
--- a/src/main/java/com/android/tools/r8/code/Format32x.java
+++ b/src/main/java/com/android/tools/r8/code/Format32x.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 abstract class Format32x extends Base3Format {
@@ -50,12 +51,9 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format32x o = (Format32x) other;
-    return o.AAAA == AAAA && o.BBBB == BBBB;
+    return ComparatorUtils.compareInts(AAAA, o.AAAA, BBBB, o.BBBB);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Format35c.java b/src/main/java/com/android/tools/r8/code/Format35c.java
index 8c2cb22..8e446db 100644
--- a/src/main/java/com/android/tools/r8/code/Format35c.java
+++ b/src/main/java/com/android/tools/r8/code/Format35c.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.util.function.BiPredicate;
 
 public abstract class Format35c<T extends IndexedDexItem> extends Base3Format {
@@ -55,15 +56,21 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
-    Format35c o = (Format35c) other;
-    return o.A == A && o.C == C && o.D == D && o.E == E && o.F == F && o.G == G
-        && o.BBBB.equals(BBBB);
+  final int internalCompareTo(Instruction other) {
+    Format35c<?> o = (Format35c<?>) other;
+    int diff =
+        ComparatorUtils.compareInts(
+            A, o.A,
+            C, o.C,
+            D, o.D,
+            E, o.E,
+            F, o.F,
+            G, o.G);
+    return diff != 0 ? diff : internalCompareBBBB(o);
   }
 
+  abstract int internalCompareBBBB(Format35c<?> other);
+
   private void appendRegisterArguments(StringBuilder builder, String separator) {
     builder.append("{ ");
     int[] values = new int[]{C, D, E, F, G};
diff --git a/src/main/java/com/android/tools/r8/code/Format3rc.java b/src/main/java/com/android/tools/r8/code/Format3rc.java
index 98e78cc..9102ecb 100644
--- a/src/main/java/com/android/tools/r8/code/Format3rc.java
+++ b/src/main/java/com/android/tools/r8/code/Format3rc.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.util.function.BiPredicate;
 
 public abstract class Format3rc<T extends IndexedDexItem> extends Base3Format {
@@ -40,14 +41,14 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format3rc<?> o = (Format3rc<?>) other;
-    return o.AA == AA && o.CCCC == CCCC && o.BBBB.equals(BBBB);
+    int diff = ComparatorUtils.compareInts(AA, o.AA, CCCC, o.CCCC);
+    return diff != 0 ? diff : internalCompareBBBB(o);
   }
 
+  abstract int internalCompareBBBB(Format3rc<?> other);
+
   private void appendRegisterRange(StringBuilder builder) {
     int firstRegister = CCCC;
     builder.append("{ ");
diff --git a/src/main/java/com/android/tools/r8/code/Format45cc.java b/src/main/java/com/android/tools/r8/code/Format45cc.java
index f6820c8..aa62780 100644
--- a/src/main/java/com/android/tools/r8/code/Format45cc.java
+++ b/src/main/java/com/android/tools/r8/code/Format45cc.java
@@ -9,13 +9,14 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.nio.ShortBuffer;
 
 /** Format45cc for instructions of size 4, with 5 registers and 2 constant pool index. */
@@ -76,19 +77,21 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
+  final int internalCompareTo(Instruction other) {
     Format45cc o = (Format45cc) other;
-    return o.A == A
-        && o.C == C
-        && o.D == D
-        && o.E == E
-        && o.F == F
-        && o.G == G
-        && o.BBBB.equals(BBBB)
-        && o.HHHH.equals(HHHH);
+    int diff =
+        ComparatorUtils.compareInts(
+            A, o.A,
+            C, o.C,
+            D, o.D,
+            E, o.E,
+            F, o.F,
+            G, o.G);
+    if (diff != 0) {
+      return diff;
+    }
+    int bDiff = BBBB.slowCompareTo(o.BBBB);
+    return bDiff != 0 ? bDiff : HHHH.slowCompareTo(o.HHHH);
   }
 
   @Override
@@ -97,10 +100,10 @@
       ProgramMethod context,
       GraphLens graphLens,
       LensCodeRewriterUtils rewriter) {
-    GraphLensLookupResult lookup =
+    MethodLookupResult lookup =
         graphLens.lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC);
     assert lookup.getType() == Type.POLYMORPHIC;
-    lookup.getMethod().collectIndexedItems(indexedItems);
+    lookup.getReference().collectIndexedItems(indexedItems);
 
     DexProto rewrittenProto = rewriter.rewriteProto(getProto());
     rewrittenProto.collectIndexedItems(indexedItems);
@@ -113,11 +116,11 @@
       GraphLens graphLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    GraphLensLookupResult lookup =
+    MethodLookupResult lookup =
         graphLens.lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC);
     assert lookup.getType() == Type.POLYMORPHIC;
     writeFirst(A, G, dest);
-    write16BitReference(lookup.getMethod(), dest, mapping);
+    write16BitReference(lookup.getReference(), dest, mapping);
     write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
 
     DexProto rewrittenProto = rewriter.rewriteProto(getProto());
diff --git a/src/main/java/com/android/tools/r8/code/Format4rcc.java b/src/main/java/com/android/tools/r8/code/Format4rcc.java
index 64bbc1e..690b230 100644
--- a/src/main/java/com/android/tools/r8/code/Format4rcc.java
+++ b/src/main/java/com/android/tools/r8/code/Format4rcc.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
+import java.util.Comparator;
 import java.util.function.BiPredicate;
 
 /** Format4rcc for instructions of size 4, with a range of registers and 2 constant pool index. */
@@ -51,11 +52,11 @@
       GraphLens graphLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    GraphLensLookupResult lookup =
+    MethodLookupResult lookup =
         graphLens.lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC);
     assert lookup.getType() == Type.POLYMORPHIC;
     writeFirst(AA, dest);
-    write16BitReference(lookup.getMethod(), dest, mapping);
+    write16BitReference(lookup.getReference(), dest, mapping);
     write16BitValue(CCCC, dest);
 
     DexProto rewrittenProto = rewriter.rewriteProto(getProto());
@@ -69,12 +70,12 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || (this.getClass() != other.getClass())) {
-      return false;
-    }
-    Format4rcc o = (Format4rcc) other;
-    return o.AA == AA && o.CCCC == CCCC && o.BBBB.equals(BBBB) && o.HHHH.equals(HHHH);
+  final int internalCompareTo(Instruction other) {
+    return Comparator.comparingInt((Format4rcc i) -> i.AA)
+        .thenComparingInt(i -> i.CCCC)
+        .thenComparing(i -> i.BBBB, DexMethod::slowCompareTo)
+        .thenComparing(i -> i.HHHH, DexProto::slowCompareTo)
+        .compare(this, (Format4rcc) other);
   }
 
   @Override
@@ -113,10 +114,10 @@
       ProgramMethod context,
       GraphLens graphLens,
       LensCodeRewriterUtils rewriter) {
-    GraphLensLookupResult lookup =
+    MethodLookupResult lookup =
         graphLens.lookupMethod(getMethod(), context.getReference(), Type.POLYMORPHIC);
     assert lookup.getType() == Type.POLYMORPHIC;
-    lookup.getMethod().collectIndexedItems(indexedItems);
+    lookup.getReference().collectIndexedItems(indexedItems);
 
     DexProto rewrittenProto = rewriter.rewriteProto(getProto());
     rewrittenProto.collectIndexedItems(indexedItems);
diff --git a/src/main/java/com/android/tools/r8/code/Format51l.java b/src/main/java/com/android/tools/r8/code/Format51l.java
index face456..6c583c4 100644
--- a/src/main/java/com/android/tools/r8/code/Format51l.java
+++ b/src/main/java/com/android/tools/r8/code/Format51l.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import java.nio.ShortBuffer;
+import java.util.Comparator;
 
 abstract class Format51l extends Base5Format {
 
@@ -47,12 +48,10 @@
   }
 
   @Override
-  public final boolean equals(Object other) {
-    if (other == null || this.getClass() != other.getClass()) {
-      return false;
-    }
-    Format51l o = (Format51l) other;
-    return o.AA == AA && o.BBBBBBBBBBBBBBBB == BBBBBBBBBBBBBBBB;
+  final int internalCompareTo(Instruction other) {
+    return Comparator.comparingInt((Format51l i) -> i.AA)
+        .thenComparingLong(i -> i.BBBBBBBBBBBBBBBB)
+        .compare(this, (Format51l) other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 766638c..4ff81dc 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -21,7 +21,7 @@
 import java.nio.ShortBuffer;
 import java.util.function.BiPredicate;
 
-public abstract class Instruction {
+public abstract class Instruction implements Comparable<Instruction> {
   public static final Instruction[] EMPTY_ARRAY = {};
 
   public final static int[] NO_TARGETS = null;
@@ -282,11 +282,29 @@
   }
 
   @Override
-  public abstract boolean equals(Object obj);
+  public final boolean equals(Object other) {
+    return other instanceof Instruction && compareTo((Instruction) other) == 0;
+  }
 
   @Override
   public abstract int hashCode();
 
+  int getCompareToId() {
+    return getOpcode();
+  }
+
+  // Abstract compare-to called only if the opcode/compare-id of the instruction matches.
+  abstract int internalCompareTo(Instruction other);
+
+  @Override
+  public final int compareTo(Instruction other) {
+    if (this == other) {
+      return 0;
+    }
+    int opcodeDiff = Integer.compare(getCompareToId(), other.getCompareToId());
+    return opcodeDiff != 0 ? opcodeDiff : internalCompareTo(other);
+  }
+
   public abstract String getName();
 
   public abstract String getSmaliName();
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
index 1355f13..fb6660a 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeCustom.java
@@ -44,6 +44,11 @@
   }
 
   @Override
+  int internalCompareBBBB(Format35c<?> other) {
+    return BBBB.compareTo((DexCallSite) other.BBBB);
+  }
+
+  @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems,
       ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java b/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
index 2d09e4f..71e8399 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeCustomRange.java
@@ -44,6 +44,11 @@
   }
 
   @Override
+  int internalCompareBBBB(Format3rc<?> other) {
+    return BBBB.compareTo((DexCallSite) other.BBBB);
+  }
+
+  @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems,
       ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/code/InvokeMethod.java
index c8539f3..de46296 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeMethod.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke;
@@ -30,7 +30,7 @@
       GraphLens graphLens,
       LensCodeRewriterUtils rewriter) {
     DexMethod rewritten =
-        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType()).getMethod();
+        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType()).getReference();
     rewritten.collectIndexedItems(indexedItems);
   }
 
@@ -42,16 +42,21 @@
   public abstract Invoke.Type getInvokeType();
 
   @Override
+  int internalCompareBBBB(Format35c<?> other) {
+    return BBBB.slowCompareTo((DexMethod) other.BBBB);
+  }
+
+  @Override
   public void write(
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    GraphLensLookupResult lookup =
+    MethodLookupResult lookup =
         graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType());
     writeFirst(A, G, dest, lookup.getType().getDexOpcode());
-    write16BitReference(lookup.getMethod(), dest, mapping);
+    write16BitReference(lookup.getReference(), dest, mapping);
     write16BitValue(combineBytes(makeByte(F, E), makeByte(D, C)), dest);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java b/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java
index 4e4d705..102d793 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeMethodRange.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -30,7 +30,7 @@
       GraphLens graphLens,
       LensCodeRewriterUtils rewriter) {
     DexMethod rewritten =
-        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType()).getMethod();
+        graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType()).getReference();
     rewritten.collectIndexedItems(indexedItems);
   }
 
@@ -42,16 +42,21 @@
   public abstract Type getInvokeType();
 
   @Override
+  int internalCompareBBBB(Format3rc<?> other) {
+    return BBBB.slowCompareTo((DexMethod) other.BBBB);
+  }
+
+  @Override
   public void write(
       ShortBuffer dest,
       ProgramMethod context,
       GraphLens graphLens,
       ObjectToOffsetMapping mapping,
       LensCodeRewriterUtils rewriter) {
-    GraphLensLookupResult lookup =
+    MethodLookupResult lookup =
         graphLens.lookupMethod(getMethod(), context.getReference(), getInvokeType());
     writeFirst(AA, dest, lookup.getType().getDexOpcodeRange());
-    write16BitReference(lookup.getMethod(), dest, mapping);
+    write16BitReference(lookup.getReference(), dest, mapping);
     write16BitValue(CCCC, dest);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/NewInstance.java b/src/main/java/com/android/tools/r8/code/NewInstance.java
index da58744..d3d25c6 100644
--- a/src/main/java/com/android/tools/r8/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/code/NewInstance.java
@@ -6,11 +6,13 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 public class NewInstance extends Format21c<DexType> {
 
@@ -42,6 +44,11 @@
   }
 
   @Override
+  int internalCompareBBBB(Format21c<?> other) {
+    return BBBB.slowCompareTo((DexType) other.BBBB);
+  }
+
+  @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems,
       ProgramMethod context,
@@ -52,6 +59,18 @@
   }
 
   @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexType rewritten = graphLens.lookupType(getType());
+    writeFirst(AA, dest);
+    write16BitReference(rewritten, dest, mapping);
+  }
+
+  @Override
   public void registerUse(UseRegistry registry) {
     registry.registerNewInstance(getType());
   }
diff --git a/src/main/java/com/android/tools/r8/code/Nop.java b/src/main/java/com/android/tools/r8/code/Nop.java
index 748d7ca..ae56ec8 100644
--- a/src/main/java/com/android/tools/r8/code/Nop.java
+++ b/src/main/java/com/android/tools/r8/code/Nop.java
@@ -31,9 +31,16 @@
     }
   }
 
+  // Notice that this must be overridden by the "Nop" subtypes!
+  @Override
+  int internalCompareTo(Instruction other) {
+    return DexCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
+  // Notice that this must be overridden by the "Nop" subtypes!
   @Override
   public int hashCode() {
-    return NAME.hashCode() * 7 + super.hashCode();
+    return NAME.hashCode() * 7;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java b/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
index c7caf43..4ea070c 100644
--- a/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
@@ -8,9 +8,11 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
 import java.util.Arrays;
+import java.util.Comparator;
 
 public class PackedSwitchPayload extends SwitchPayload {
 
@@ -56,12 +58,11 @@
   }
 
   @Override
-  public boolean equals(Object other) {
-    if (!super.equals(other)) {
-      return false;
-    }
-    PackedSwitchPayload that = (PackedSwitchPayload) other;
-    return size == that.size && first_key == that.first_key && Arrays.equals(targets, that.targets);
+  final int internalCompareTo(Instruction other) {
+    return Comparator.comparingInt((PackedSwitchPayload i) -> i.size)
+        .thenComparingInt(i -> first_key)
+        .thenComparing(i -> i.targets, ComparatorUtils::compareIntArray)
+        .compare(this, (PackedSwitchPayload) other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/ReturnVoid.java b/src/main/java/com/android/tools/r8/code/ReturnVoid.java
index 328e032..68cfb51 100644
--- a/src/main/java/com/android/tools/r8/code/ReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/code/ReturnVoid.java
@@ -33,6 +33,16 @@
   }
 
   @Override
+  final int internalCompareTo(Instruction other) {
+    return DexCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+  }
+
+  @Override
+  public int hashCode() {
+    return NAME.hashCode();
+  }
+
+  @Override
   public int[] getTargets() {
     return EXIT_TARGET;
   }
diff --git a/src/main/java/com/android/tools/r8/code/SgetOrSput.java b/src/main/java/com/android/tools/r8/code/SgetOrSput.java
index 1f77122..48112fc 100644
--- a/src/main/java/com/android/tools/r8/code/SgetOrSput.java
+++ b/src/main/java/com/android/tools/r8/code/SgetOrSput.java
@@ -6,8 +6,10 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
 
 abstract class SgetOrSput extends Format21c<DexField> {
 
@@ -30,7 +32,24 @@
   }
 
   @Override
+  public void write(
+      ShortBuffer dest,
+      ProgramMethod context,
+      GraphLens graphLens,
+      ObjectToOffsetMapping mapping,
+      LensCodeRewriterUtils rewriter) {
+    DexField rewritten = graphLens.lookupField(getField());
+    writeFirst(AA, dest);
+    write16BitReference(rewritten, dest, mapping);
+  }
+
+  @Override
   public final DexField getField() {
     return BBBB;
   }
+
+  @Override
+  int internalCompareBBBB(Format21c<?> other) {
+    return BBBB.slowCompareTo((DexField) other.BBBB);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java b/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
index b73779b..1a1b1e9 100644
--- a/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
@@ -8,9 +8,11 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.ShortBuffer;
 import java.util.Arrays;
+import java.util.Comparator;
 
 public class SparseSwitchPayload extends SwitchPayload {
 
@@ -62,13 +64,11 @@
   }
 
   @Override
-  public boolean equals(Object other) {
-    if (!super.equals(other)) {
-      return false;
-    }
-    SparseSwitchPayload that = (SparseSwitchPayload) other;
-    return size == that.size && Arrays.equals(keys, that.keys) && Arrays
-        .equals(targets, that.targets);
+  final int internalCompareTo(Instruction other) {
+    return Comparator.comparingInt((SparseSwitchPayload i) -> i.size)
+        .thenComparing(i -> i.keys, ComparatorUtils::compareIntArray)
+        .thenComparing(i -> i.targets, ComparatorUtils::compareIntArray)
+        .compare(this, (SparseSwitchPayload) other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index a416e51..978a7e7 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -859,7 +859,7 @@
     int insnsSize = dexReader.getUint();
     short[] code = new short[insnsSize];
     Try[] tries = new Try[triesSize];
-    DexCode.TryHandler[] handlers = null;
+    TryHandler[] handlers = new TryHandler[0];
 
     if (insnsSize != 0) {
       for (int i = 0; i < insnsSize; i++) {
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index d9103a5..bb5706d 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -408,7 +408,7 @@
     }
     result += insnSize * 2;
     result += code.tries.length * 8;
-    if ((code.handlers != null) && (code.handlers.length > 0)) {
+    if (code.handlers.length > 0) {
       result = alignSize(4, result);
       result += LebUtils.sizeAsUleb128(code.handlers.length);
       for (TryHandler handler : code.handlers) {
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
index e05af85..128ea1d 100644
--- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -194,9 +194,6 @@
 
   private TryHandler[] rewriteHandlerOffsets() {
     DexCode code = method.getCode().asDexCode();
-    if (code.handlers == null) {
-      return null;
-    }
     TryHandler[] result = new TryHandler[code.handlers.length];
     for (int i = 0; i < code.handlers.length; i++) {
       TryHandler handler = code.handlers[i];
@@ -545,21 +542,19 @@
       tryRangeStartAndEndTargets.put(start.getOffset(), start);
       tryRangeStartAndEndTargets.put(end.getOffset(), end);
     }
-    if (code.handlers != null) {
-      for (TryHandler handler : code.handlers) {
-        List<Instruction> targets = new ArrayList<>();
-        if (handler.catchAllAddr != NO_HANDLER) {
-          Instruction target = offsetToInstruction.get(handler.catchAllAddr);
-          assert target != null;
-          targets.add(target);
-        }
-        for (TypeAddrPair pair : handler.pairs) {
-          Instruction target = offsetToInstruction.get(pair.addr);
-          assert target != null;
-          targets.add(target);
-        }
-        handlerTargets.put(handler, targets);
+    for (TryHandler handler : code.handlers) {
+      List<Instruction> targets = new ArrayList<>();
+      if (handler.catchAllAddr != NO_HANDLER) {
+        Instruction target = offsetToInstruction.get(handler.catchAllAddr);
+        assert target != null;
+        targets.add(target);
       }
+      for (TypeAddrPair pair : handler.pairs) {
+        Instruction target = offsetToInstruction.get(pair.addr);
+        assert target != null;
+        targets.add(target);
+      }
+      handlerTargets.put(handler, targets);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 2e0ab82..e4f0a0c 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -76,7 +76,6 @@
   private static final int MAX_PREFILL_ENTRIES = MAX_ENTRIES - 5000;
 
   private final int id;
-  private final GraphLens graphLens;
   private final VirtualFileIndexedItemCollection indexedItems;
   private final IndexedItemTransaction transaction;
   private final FeatureSplit featureSplit;
@@ -121,7 +120,6 @@
       DexProgramClass primaryClass,
       FeatureSplit featureSplit) {
     this.id = id;
-    this.graphLens = graphLens;
     this.indexedItems = new VirtualFileIndexedItemCollection(graphLens, initClassLens, namingLens);
     this.transaction =
         new IndexedItemTransaction(indexedItems, appView, graphLens, initClassLens, namingLens);
@@ -637,12 +635,12 @@
 
     @Override
     public boolean addField(DexField field) {
-      return fields.add(graphLens.lookupField(field));
+      return fields.add(field);
     }
 
     @Override
     public boolean addMethod(DexMethod method) {
-      return methods.add(graphLens.lookupMethod(method));
+      return methods.add(method);
     }
 
     @Override
@@ -657,9 +655,8 @@
 
     @Override
     public boolean addType(DexType type) {
-      DexType rewritten = graphLens.lookupType(type);
-      assert SyntheticItems.verifyNotInternalSynthetic(rewritten);
-      return types.add(rewritten);
+      assert SyntheticItems.verifyNotInternalSynthetic(type);
+      return types.add(type);
     }
 
     @Override
@@ -761,12 +758,12 @@
 
     @Override
     public boolean addField(DexField field) {
-      return maybeInsert(base.graphLens.lookupField(field), fields, base.fields);
+      return maybeInsert(field, fields, base.fields);
     }
 
     @Override
     public boolean addMethod(DexMethod method) {
-      return maybeInsert(base.graphLens.lookupMethod(method), methods, base.methods);
+      return maybeInsert(method, methods, base.methods);
     }
 
     @Override
@@ -781,9 +778,8 @@
 
     @Override
     public boolean addType(DexType type) {
-      DexType rewritten = base.graphLens.lookupType(type);
-      assert SyntheticItems.verifyNotInternalSynthetic(rewritten);
-      return maybeInsert(rewritten, types, base.types);
+      assert SyntheticItems.verifyNotInternalSynthetic(type);
+      return maybeInsert(type, types, base.types);
     }
 
     @Override
@@ -808,19 +804,18 @@
 
     @Override
     public DexString getRenamedDescriptor(DexType type) {
-      return namingLens.lookupDescriptor(base.graphLens.lookupType(type));
+      return namingLens.lookupDescriptor(type);
     }
 
     @Override
     public DexString getRenamedName(DexMethod method) {
-      DexMethod mappedMethod = base.graphLens.lookupMethod(method);
-      assert namingLens.verifyRenamingConsistentWithResolution(mappedMethod);
-      return namingLens.lookupName(mappedMethod);
+      assert namingLens.verifyRenamingConsistentWithResolution(method);
+      return namingLens.lookupName(method);
     }
 
     @Override
     public DexString getRenamedName(DexField field) {
-      return namingLens.lookupName(base.graphLens.lookupField(field));
+      return namingLens.lookupName(field);
     }
 
     int getNumberOfMethods() {
diff --git a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
index e078248..4615f1f 100644
--- a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
+++ b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
@@ -182,7 +182,7 @@
       }
     }
 
-    Map<DexField, ProgramMethodSet> getAccessesWithContexts() {
+    public Map<DexField, ProgramMethodSet> getAccessesWithContexts() {
       return accessesWithContexts;
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 8792d0c..4bbcead 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -233,11 +233,15 @@
   }
 
   private boolean wasSet(int flag) {
-    return (originalFlags & flag) != 0;
+    return isSet(originalFlags, flag);
   }
 
   protected boolean isSet(int flag) {
-    return (modifiedFlags & flag) != 0;
+    return isSet(modifiedFlags, flag);
+  }
+
+  public static boolean isSet(int flag, int flags) {
+    return (flags & flag) != 0;
   }
 
   protected void set(int flag) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 6da4e08..d2d137b 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collection;
+import java.util.function.Consumer;
 
 public class AppInfo implements DexDefinitionSupplier {
 
@@ -125,6 +126,12 @@
     return app.classesWithDeterministicOrder();
   }
 
+  public void forEachMethod(Consumer<ProgramMethod> consumer) {
+    for (DexProgramClass clazz : classes()) {
+      clazz.forEachProgramMethod(consumer);
+    }
+  }
+
   @Override
   public DexClass definitionFor(DexType type) {
     return definitionForWithoutExistenceAssert(type);
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 f89f265..f4407b4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
 import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.graph.classmerging.MergedClasses;
@@ -214,7 +214,7 @@
   }
 
   public GraphLens clearCodeRewritings() {
-    return graphLens = graphLens.withCodeRewritingsApplied();
+    return graphLens = graphLens.withCodeRewritingsApplied(dexItemFactory());
   }
 
   public AppServices appServices() {
@@ -520,36 +520,54 @@
     return !cfByteCodePassThrough.isEmpty();
   }
 
-  public void rewriteWithLens(NestedGraphLens lens) {
+  public void rewriteWithLens(NonIdentityGraphLens lens) {
     if (lens != null) {
-      rewriteWithLens(lens, appInfo().app().asDirect(), withLiveness());
+      rewriteWithLens(lens, appInfo().app().asDirect(), withLiveness(), lens.getPrevious());
     }
   }
 
-  public void rewriteWithApplication(DirectMappedDexApplication application) {
-    assert application != null;
-    rewriteWithLens(null, application, withLiveness());
-  }
-
   public void rewriteWithLensAndApplication(
-      NestedGraphLens lens, DirectMappedDexApplication application) {
+      NonIdentityGraphLens lens, DirectMappedDexApplication application) {
+    rewriteWithLensAndApplication(lens, application, lens.getPrevious());
+  }
+
+  public void rewriteWithLensAndApplication(
+      NonIdentityGraphLens lens, DirectMappedDexApplication application, GraphLens appliedLens) {
     assert lens != null;
     assert application != null;
-    rewriteWithLens(lens, application, withLiveness());
+    rewriteWithLens(lens, application, withLiveness(), appliedLens);
   }
 
   private static void rewriteWithLens(
-      NestedGraphLens lens,
+      NonIdentityGraphLens lens,
       DirectMappedDexApplication application,
-      AppView<AppInfoWithLiveness> appView) {
-    if (lens != null) {
-      boolean changed = appView.setGraphLens(lens);
-      assert changed;
-      assert application.verifyWithLens(lens);
+      AppView<AppInfoWithLiveness> appView,
+      GraphLens appliedLens) {
+    if (lens == null) {
+      return;
     }
-    appView.setAppInfo(appView.appInfo().rewrittenWithLens(application, lens));
-    if (appView.hasInitClassLens()) {
-      appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens));
+
+    boolean changed = appView.setGraphLens(lens);
+    assert changed;
+    assert application.verifyWithLens(lens);
+
+    // The application has already been rewritten with the given applied lens. Therefore, we
+    // temporarily replace that lens with the identity lens to avoid the overhead of traversing
+    // the entire lens chain upon each lookup during the rewriting.
+    NonIdentityGraphLens temporaryRootLens = lens;
+    while (temporaryRootLens.getPrevious() != appliedLens) {
+      GraphLens previousLens = temporaryRootLens.getPrevious();
+      assert previousLens.isNonIdentityLens();
+      temporaryRootLens = previousLens.asNonIdentityLens();
     }
+
+    temporaryRootLens.withAlternativeParentLens(
+        GraphLens.getIdentityLens(),
+        () -> {
+          appView.setAppInfo(appView.appInfo().rewrittenWithLens(application, lens));
+          if (appView.hasInitClassLens()) {
+            appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens));
+          }
+        });
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index c9e4d4d..ae54120 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -4,12 +4,16 @@
 
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * A graph lens that will not lead to any code rewritings in the {@link
@@ -18,11 +22,10 @@
  *
  * <p>The mappings from the original program to the generated program are kept, though.
  */
-public class AppliedGraphLens extends GraphLens {
+public final class AppliedGraphLens extends NonIdentityGraphLens {
 
-  private final AppView<?> appView;
-
-  private final BiMap<DexType, DexType> originalTypeNames = HashBiMap.create();
+  private final BidirectionalManyToOneMap<DexType, DexType> renamedTypeNames =
+      new BidirectionalManyToOneMap<>();
   private final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
   private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
 
@@ -32,21 +35,10 @@
   private final Map<DexMethod, DexMethod> extraOriginalMethodSignatures = new IdentityHashMap<>();
 
   public AppliedGraphLens(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    this.appView = appView;
-
+    super(appView.dexItemFactory(), GraphLens.getIdentityLens());
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       // Record original type names.
-      {
-        DexType type = clazz.type;
-        if (appView.verticallyMergedClasses() != null
-            && !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
-          DexType original = appView.graphLens().getOriginalType(type);
-          if (original != type) {
-            DexType existing = originalTypeNames.forcePut(type, original);
-            assert existing == null;
-          }
-        }
-      }
+      recordOriginalTypeNames(clazz, appView);
 
       // Record original field signatures.
       for (DexEncodedField encodedField : clazz.fields()) {
@@ -82,9 +74,41 @@
     MapUtils.removeIdentityMappings(extraOriginalMethodSignatures);
   }
 
+  private void recordOriginalTypeNames(
+      DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    DexType type = clazz.getType();
+    VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
+    if (verticallyMergedClasses != null && verticallyMergedClasses.hasBeenMergedIntoSubtype(type)) {
+      return;
+    }
+
+    DexType original = appView.graphLens().getOriginalType(type);
+    if (verticallyMergedClasses != null) {
+      List<DexType> sources = verticallyMergedClasses.getSourcesFor(type);
+      if (!sources.isEmpty()) {
+        renamedTypeNames.put(original, type);
+        sources.forEach(source -> renamedTypeNames.put(source, type));
+        return;
+      }
+    }
+
+    if (original != type) {
+      renamedTypeNames.put(original, type);
+    }
+  }
+
+  @Override
+  public boolean isAppliedLens() {
+    return true;
+  }
+
   @Override
   public DexType getOriginalType(DexType type) {
-    return originalTypeNames.getOrDefault(type, type);
+    Set<DexType> originalTypeNames = renamedTypeNames.getKeys(type);
+    if (!originalTypeNames.isEmpty()) {
+      return originalTypeNames.iterator().next();
+    }
+    return type;
   }
 
   @Override
@@ -113,27 +137,29 @@
   }
 
   @Override
-  public DexType lookupType(DexType type) {
-    if (appView.verticallyMergedClasses() != null
-        && appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
-      return lookupType(appView.verticallyMergedClasses().getTargetFor(type));
-    }
-    return originalTypeNames.inverse().getOrDefault(type, type);
-  }
-
-  @Override
-  public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Invoke.Type type) {
-    return GraphLens.getIdentityLens().lookupMethod(method, context, type);
-  }
-
-  @Override
   public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
     return GraphLens.getIdentityLens().lookupPrototypeChangesForMethodDefinition(method);
   }
 
   @Override
-  public DexField lookupField(DexField field) {
-    return field;
+  public DexType internalDescribeLookupClassType(DexType previous) {
+    return renamedTypeNames.getOrDefault(previous, previous);
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    return previous;
+  }
+
+  @Override
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    return previous;
+  }
+
+  @Override
+  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    return method;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 491178a..bd6c452 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -85,6 +85,32 @@
       for (DexType value : clazz.interfaces.values) {
         ps.println("# Implements: '" + value.toSourceString() + "'");
       }
+      if (!clazz.getInnerClasses().isEmpty()) {
+        ps.println("# InnerClasses:");
+        for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
+          ps.println(
+              "#  Outer: "
+                  + (innerClassAttribute.getOuter() != null
+                      ? innerClassAttribute.getOuter().toSourceString()
+                      : "-")
+                  + ", inner: "
+                  + innerClassAttribute.getInner().toSourceString()
+                  + ", inner name: "
+                  + innerClassAttribute.getInnerName()
+                  + ", access: "
+                  + Integer.toHexString(innerClassAttribute.getAccess()));
+        }
+      }
+      EnclosingMethodAttribute enclosingMethodAttribute = clazz.getEnclosingMethodAttribute();
+      if (enclosingMethodAttribute != null) {
+        ps.println("# EnclosingMethod:");
+        if (enclosingMethodAttribute.getEnclosingClass() != null) {
+          ps.println("#  Class: " + enclosingMethodAttribute.getEnclosingClass().toSourceString());
+        } else {
+          ps.println(
+              "#  Method: " + enclosingMethodAttribute.getEnclosingMethod().toSourceString());
+        }
+      }
     }
     ps.println();
   }
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 b74a480..b6355dc 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,11 +5,15 @@
 
 import static com.android.tools.r8.graph.DexCode.FAKE_THIS_PREFIX;
 import static com.android.tools.r8.graph.DexCode.FAKE_THIS_SUFFIX;
+import static com.android.tools.r8.ir.conversion.CfSourceCode.canThrowHelper;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
 import static org.objectweb.asm.Opcodes.V1_5;
 import static org.objectweb.asm.Opcodes.V1_6;
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfFrameVerificationHelper;
 import com.android.tools.r8.cf.code.CfIinc;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -19,7 +23,11 @@
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -35,17 +43,26 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.base.Strings;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.function.BiPredicate;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
-public class CfCode extends Code {
+public class CfCode extends Code implements Comparable<CfCode> {
 
   public static class LocalVariableInfo {
 
@@ -87,6 +104,14 @@
       return end;
     }
 
+    public int compareTo(LocalVariableInfo other, CfCompareHelper helper) {
+      return Comparator.comparingInt(LocalVariableInfo::getIndex)
+          .thenComparing(LocalVariableInfo::getStart, helper::compareLabels)
+          .thenComparing(LocalVariableInfo::getEnd, helper::compareLabels)
+          .thenComparing(LocalVariableInfo::getLocal)
+          .compare(this, other);
+    }
+
     @Override
     public String toString() {
       return "" + index + " => " + local;
@@ -181,6 +206,41 @@
     return this;
   }
 
+  @Override
+  public int compareTo(CfCode o) {
+    // Fast path by checking sizes.
+    int sizeDiff =
+        Comparator.comparingInt((CfCode c) -> c.instructions.size())
+            .thenComparingInt(c -> c.tryCatchRanges.size())
+            .thenComparingInt(c -> localVariables.size())
+            .compare(this, o);
+    if (sizeDiff != 0) {
+      return sizeDiff;
+    }
+    // In the slow case, compute label maps and compare collections in full.
+    Reference2IntMap<CfLabel> labels1 = getLabelOrdering(instructions);
+    Reference2IntMap<CfLabel> labels2 = getLabelOrdering(o.instructions);
+    int labelDiff = labels1.size() - labels2.size();
+    if (labelDiff != 0) {
+      return labelDiff;
+    }
+    CfCompareHelper helper = new CfCompareHelper(labels1, labels2);
+    return Comparator.comparing((CfCode c) -> c.instructions, helper.instructionComparator())
+        .thenComparing(c -> c.tryCatchRanges, helper.tryCatchRangesComparator())
+        .thenComparing(c -> c.localVariables, helper.localVariablesComparator())
+        .compare(this, o);
+  }
+
+  private static Reference2IntMap<CfLabel> getLabelOrdering(List<CfInstruction> instructions) {
+    Reference2IntMap<CfLabel> ordering = new Reference2IntOpenHashMap<>();
+    for (CfInstruction instruction : instructions) {
+      if (instruction.isLabel()) {
+        ordering.put(instruction.asLabel(), ordering.size());
+      }
+    }
+    return ordering;
+  }
+
   private boolean shouldAddParameterNames(DexEncodedMethod method, AppView<?> appView) {
     // Don't add parameter information if the code already has full debug information.
     // Note: This fast path can cause a method to loose its parameter info, if the debug info turned
@@ -207,6 +267,7 @@
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
+    assert verifyFrames(method.getDefinition(), appView, null, false);
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     GraphLens graphLens = appView.graphLens();
     InitClassLens initClassLens = appView.initClassLens();
@@ -299,6 +360,7 @@
 
   @Override
   public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    // TODO(b/164396438): Assert that we can validate frames.
     return internalBuildPossiblyWithLocals(method, method, appView, null, null, origin, null);
   }
 
@@ -313,6 +375,7 @@
       MethodProcessor methodProcessor) {
     assert valueNumberGenerator != null;
     assert callerPosition != null;
+    // TODO(b/164396438): Assert that we can validate frames.
     return internalBuildPossiblyWithLocals(
         context, method, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
   }
@@ -526,7 +589,6 @@
       // IR processing.
       inliningConstraints.disallowStaticInterfaceMethodCalls();
     }
-
     // Model a synchronized method as having a monitor instruction.
     ConstraintWithTarget constraint =
         method.getDefinition().isSynchronized()
@@ -579,4 +641,249 @@
         new LocalVariableInfo(
             thisLocalInfo.index, debugLocalInfo, thisLocalInfo.start, thisLocalInfo.end));
   }
+
+  public boolean verifyFrames(
+      DexEncodedMethod method, AppView<?> appView, Origin origin, boolean applyProtoTypeChanges) {
+    if (!appView.options().testing.readInputStackMaps
+        || appView.options().testing.disableStackMapVerification) {
+      return true;
+    }
+    if (method.hasClassFileVersion() && method.getClassFileVersion() <= V1_6) {
+      return true;
+    }
+    if (!method.isInstanceInitializer()
+        && appView
+            .graphLens()
+            .getOriginalMethodSignature(method.method)
+            .isInstanceInitializer(appView.dexItemFactory())) {
+      // We cannot verify instance initializers if they are moved.
+      return true;
+    }
+    // Build a map from labels to frames.
+    Map<CfLabel, CfFrame> stateMap = new IdentityHashMap<>();
+    List<CfLabel> labels = new ArrayList<>();
+    boolean requireStackMapFrame = !tryCatchRanges.isEmpty();
+    for (CfInstruction instruction : instructions) {
+      if (instruction.isFrame()) {
+        CfFrame frame = instruction.asFrame();
+        if (!labels.isEmpty()) {
+          for (CfLabel label : labels) {
+            if (stateMap.containsKey(label)) {
+              return reportStackMapError(
+                  CfCodeStackMapValidatingException.multipleFramesForLabel(
+                      origin,
+                      appView.graphLens().getOriginalMethodSignature(method.method),
+                      appView),
+                  appView);
+            }
+            stateMap.put(label, frame);
+          }
+        } else if (instruction != instructions.get(0)) {
+          // From b/168212806, it is possible that the first instruction is a frame.
+          return reportStackMapError(
+              CfCodeStackMapValidatingException.unexpectedStackMapFrame(
+                  origin, appView.graphLens().getOriginalMethodSignature(method.method), appView),
+              appView);
+        }
+      }
+      // We are trying to map a frame to a label, but we can have positions in between, so skip
+      // those.
+      if (instruction.isPosition()) {
+        continue;
+      } else if (instruction.isLabel()) {
+        labels.add(instruction.asLabel());
+      } else {
+        labels.clear();
+      }
+      if (!requireStackMapFrame) {
+        requireStackMapFrame = instruction.isJump() && !finalAndExitInstruction(instruction);
+      }
+    }
+    // If there are no frames but we have seen a jump instruction, we cannot verify the stack map.
+    if (requireStackMapFrame && stateMap.isEmpty()) {
+      return reportStackMapError(
+          CfCodeStackMapValidatingException.noFramesForMethodWithJumps(
+              origin, appView.graphLens().getOriginalMethodSignature(method.method), appView),
+          appView);
+    }
+    DexType context = appView.graphLens().lookupType(method.holder());
+    DexType returnType = appView.graphLens().lookupType(method.method.getReturnType());
+    RewrittenPrototypeDescription rewrittenDescription = RewrittenPrototypeDescription.none();
+    if (applyProtoTypeChanges) {
+      rewrittenDescription =
+          appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.method);
+      if (!rewrittenDescription.isEmpty()
+          && rewrittenDescription.getRewrittenReturnInfo() != null) {
+        returnType = rewrittenDescription.getRewrittenReturnInfo().getOldType();
+      }
+    }
+    CfFrameVerificationHelper builder =
+        new CfFrameVerificationHelper(
+            context,
+            stateMap,
+            tryCatchRanges,
+            isAssignablePredicate(appView),
+            appView.dexItemFactory());
+    if (stateMap.containsKey(null)) {
+      assert !shouldComputeInitialFrame();
+      builder.verifyFrameAndSet(stateMap.get(null));
+    } else if (shouldComputeInitialFrame()) {
+      builder.verifyFrameAndSet(
+          new CfFrame(
+              computeInitialLocals(context, method, rewrittenDescription), new ArrayDeque<>()));
+    }
+    for (int i = 0; i < instructions.size(); i++) {
+      CfInstruction instruction = instructions.get(i);
+      try {
+        // Check the exceptional edge prior to evaluating the instruction. The local state is stable
+        // at this point as store operations are not throwing and the current stack does not
+        // affect the exceptional transfer (the exception edge is always a singleton stack).
+        if (canThrowHelper(instruction, appView.options().isGeneratingClassFiles())) {
+          assert !instruction.isStore();
+          builder.verifyExceptionEdges();
+        }
+        instruction.evaluate(
+            builder, context, returnType, appView.dexItemFactory(), appView.initClassLens());
+      } catch (CfCodeStackMapValidatingException ex) {
+        return reportStackMapError(
+            CfCodeStackMapValidatingException.toDiagnostics(
+                origin,
+                appView.graphLens().getOriginalMethodSignature(method.method),
+                i,
+                instruction,
+                ex.getMessage(),
+                appView),
+            appView);
+      }
+    }
+    return true;
+  }
+
+  private boolean reportStackMapError(CfCodeDiagnostics diagnostics, AppView<?> appView) {
+    // Stack maps was required from version V1_6 (50), but the JVM gave a grace-period and only
+    // started enforcing stack maps from 51 in JVM 8. As a consequence, we have different android
+    // libraries that has V1_7 code but has no stack maps. To not fail on compilations we only
+    // report a warning.
+    appView.options().reporter.warning(diagnostics);
+    return false;
+  }
+
+  private boolean finalAndExitInstruction(CfInstruction instruction) {
+    boolean isReturnOrThrow = instruction.isThrow() || instruction.isReturn();
+    if (!isReturnOrThrow) {
+      return false;
+    }
+    for (int i = instructions.size() - 1; i >= 0; i--) {
+      CfInstruction instr = instructions.get(i);
+      if (instr == instruction) {
+        return true;
+      }
+      if (instr.isPosition() || instr.isLabel()) {
+        continue;
+      }
+      return false;
+    }
+    throw new Unreachable("Instruction " + instruction + " should be in instructions");
+  }
+
+  private boolean shouldComputeInitialFrame() {
+    for (CfInstruction instruction : instructions) {
+      if (instruction.isFrame()) {
+        return false;
+      } else if (!instruction.isLabel() && !instruction.isPosition()) {
+        return true;
+      }
+    }
+    // We should never see a method with only labels and positions.
+    assert false;
+    return true;
+  }
+
+  private Int2ReferenceSortedMap<FrameType> computeInitialLocals(
+      DexType context, DexEncodedMethod method, RewrittenPrototypeDescription protoTypeChanges) {
+    int accessFlags =
+        protoTypeChanges.isEmpty()
+            ? method.accessFlags.modifiedFlags
+            : method.accessFlags.originalFlags;
+    Int2ReferenceSortedMap<FrameType> initialLocals = new Int2ReferenceAVLTreeMap<>();
+    int index = 0;
+    if (method.isInstanceInitializer()) {
+      initialLocals.put(index++, FrameType.uninitializedThis());
+    } else if (!MethodAccessFlags.isSet(ACC_STATIC, accessFlags)) {
+      initialLocals.put(index++, FrameType.initialized(context));
+    }
+    ArgumentInfoCollection argumentsInfo = protoTypeChanges.getArgumentInfoCollection();
+    DexType[] parameters = method.method.proto.parameters.values;
+    int originalNumberOfArguments =
+        parameters.length
+            + argumentsInfo.numberOfRemovedArguments()
+            + initialLocals.size()
+            - protoTypeChanges.numberOfExtraParameters();
+    int argumentIndex = index;
+    int usedArgumentIndex = 0;
+    while (argumentIndex < originalNumberOfArguments) {
+      ArgumentInfo argumentInfo = argumentsInfo.getArgumentInfo(argumentIndex++);
+      DexType localType;
+      if (argumentInfo.isRemovedArgumentInfo()) {
+        localType = argumentInfo.asRemovedArgumentInfo().getType();
+      } else {
+        if (argumentInfo.isRewrittenTypeInfo()) {
+          assert parameters[usedArgumentIndex] == argumentInfo.asRewrittenTypeInfo().getNewType();
+          localType = argumentInfo.asRewrittenTypeInfo().getOldType();
+        } else {
+          localType = parameters[usedArgumentIndex];
+        }
+        usedArgumentIndex++;
+      }
+      FrameType frameType = FrameType.initialized(localType);
+      initialLocals.put(index++, frameType);
+      if (localType.isWideType()) {
+        initialLocals.put(index++, frameType);
+      }
+    }
+    return initialLocals;
+  }
+
+  private BiPredicate<DexType, DexType> isAssignablePredicate(AppView<?> appView) {
+    return (source, target) -> isAssignable(source, target, appView);
+  }
+
+  // Rules found at https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.2
+  private boolean isAssignable(DexType source, DexType target, AppView<?> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
+    source = byteCharShortOrBooleanToInt(source, factory);
+    target = byteCharShortOrBooleanToInt(target, factory);
+    if (source == target) {
+      return true;
+    }
+    if (source.isPrimitiveType() || target.isPrimitiveType()) {
+      return false;
+    }
+    // Both are now references - everything is assignable to object.
+    if (target == factory.objectType) {
+      return true;
+    }
+    // isAssignable(null, class(_, _)).
+    // isAssignable(null, arrayOf(_)).
+    if (source == DexItemFactory.nullValueType) {
+      return true;
+    }
+    if (target.isArrayType() != target.isArrayType()) {
+      return false;
+    }
+    if (target.isArrayType()) {
+      return isAssignable(
+          target.toArrayElementType(factory), target.toArrayElementType(factory), appView);
+    }
+    // TODO(b/166570659): Do a sub-type check that allows for missing classes in hierarchy.
+    return MemberType.fromDexType(source) == MemberType.fromDexType(target);
+  }
+
+  private DexType byteCharShortOrBooleanToInt(DexType type, DexItemFactory factory) {
+    // byte, char, short and boolean has verification type int.
+    if (type.isByteType() || type.isCharType() || type.isShortType() || type.isBooleanType()) {
+      return factory.intType;
+    }
+    return type;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java b/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java
new file mode 100644
index 0000000..764a812
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/CfCodeDiagnostics.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.position.Position;
+
+public class CfCodeDiagnostics implements Diagnostic {
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return methodPosition;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return diagnosticMessage;
+  }
+
+  private final Origin origin;
+  private final MethodPosition methodPosition;
+  private final String diagnosticMessage;
+
+  CfCodeDiagnostics(Origin origin, DexMethod method, String diagnosticMessage) {
+    this.origin = origin;
+    this.methodPosition = new MethodPosition(method.asMethodReference());
+    this.diagnosticMessage = diagnosticMessage;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
new file mode 100644
index 0000000..6c54a1a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/CfCodeStackMapValidatingException.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.origin.Origin;
+
+public class CfCodeStackMapValidatingException extends RuntimeException {
+
+  private CfCodeStackMapValidatingException(String message) {
+    super(message);
+  }
+
+  public static CfCodeStackMapValidatingException error(String messsage) {
+    return new CfCodeStackMapValidatingException(messsage);
+  }
+
+  public static CfCodeDiagnostics unexpectedStackMapFrame(
+      Origin origin, DexMethod method, AppView<?> appView) {
+    StringBuilder sb = new StringBuilder("Unexpected stack map frame without target");
+    if (appView.enableWholeProgramOptimizations()) {
+      sb.append(" In later version of R8, the method may be assumed not reachable.");
+    }
+    return new CfCodeDiagnostics(origin, method, sb.toString());
+  }
+
+  public static CfCodeDiagnostics multipleFramesForLabel(
+      Origin origin, DexMethod method, AppView<?> appView) {
+    StringBuilder sb = new StringBuilder("Multiple frames for label");
+    if (appView.enableWholeProgramOptimizations()) {
+      sb.append(" In later version of R8, the method may be assumed not reachable.");
+    }
+    return new CfCodeDiagnostics(origin, method, sb.toString());
+  }
+
+  public static CfCodeDiagnostics noFramesForMethodWithJumps(
+      Origin origin, DexMethod method, AppView<?> appView) {
+    StringBuilder sb =
+        new StringBuilder("Expected stack map table for method with non-linear control flow.");
+    if (appView.enableWholeProgramOptimizations()) {
+      sb.append(" In later version of R8, the method may be assumed not reachable.");
+    }
+    return new CfCodeDiagnostics(origin, method, sb.toString());
+  }
+
+  public static CfCodeDiagnostics toDiagnostics(
+      Origin origin,
+      DexMethod method,
+      int instructionIndex,
+      CfInstruction instruction,
+      String detailMessage,
+      AppView<?> appView) {
+    StringBuilder sb =
+        new StringBuilder("Invalid stack map table at ")
+            .append(instructionIndex)
+            .append(": ")
+            .append(instruction)
+            .append(", error: ")
+            .append(detailMessage)
+            .append(".");
+    if (appView.enableWholeProgramOptimizations()) {
+      sb.append(" In later version of R8, the method may be assumed not reachable.");
+    }
+    return new CfCodeDiagnostics(origin, method, sb.toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
new file mode 100644
index 0000000..b05814e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.utils.ComparatorUtils;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import java.util.Comparator;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class CfCompareHelper {
+
+  // Integer constants to ensure that there is a well order for all CF instructions including
+  // virtual instructions represented in our internal encoding.
+  public static final int CONST_CLASS_COMPARE_ID;
+  public static final int CONST_STRING_COMPARE_ID;
+  public static final int CONST_STRING_DEX_ITEM_COMPARE_ID;
+  public static final int CONST_NUMBER_COMPARE_ID;
+  public static final int CONST_METHOD_TYPE_COMPARE_ID;
+  public static final int CONST_METHOD_HANDLE_COMPARE_ID;
+  public static final int FRAME_COMPARE_ID;
+  public static final int INIT_CLASS_COMPARE_ID;
+  public static final int LABEL_COMPARE_ID;
+  public static final int POSITION_COMPARE_ID;
+
+  static {
+    int lastId = Opcodes.IFNONNULL;
+    CONST_CLASS_COMPARE_ID = ++lastId;
+    CONST_STRING_COMPARE_ID = ++lastId;
+    CONST_STRING_DEX_ITEM_COMPARE_ID = ++lastId;
+    CONST_NUMBER_COMPARE_ID = ++lastId;
+    CONST_METHOD_TYPE_COMPARE_ID = ++lastId;
+    CONST_METHOD_HANDLE_COMPARE_ID = ++lastId;
+    FRAME_COMPARE_ID = ++lastId;
+    INIT_CLASS_COMPARE_ID = ++lastId;
+    LABEL_COMPARE_ID = ++lastId;
+    POSITION_COMPARE_ID = ++lastId;
+  }
+
+  // Helper to signal that the concrete instruction is uniquely determined by its ID/opcode.
+  public static int compareIdUniquelyDeterminesEquality(
+      CfInstruction instruction1, CfInstruction instruction2) {
+    assert instruction1.getClass() == instruction2.getClass();
+    assert instruction1.getCompareToId() == instruction2.getCompareToId();
+    assert instruction1.toString().equals(instruction2.toString());
+    return 0;
+  }
+
+  private final Reference2IntMap<CfLabel> labels1;
+  private final Reference2IntMap<CfLabel> labels2;
+
+  public CfCompareHelper(Reference2IntMap<CfLabel> labels1, Reference2IntMap<CfLabel> labels2) {
+    this.labels1 = labels1;
+    this.labels2 = labels2;
+  }
+
+  public int compareLabels(CfLabel label1, CfLabel label2) {
+    return labels1.getInt(label1) - labels2.getInt(label2);
+  }
+
+  public Comparator<List<CfInstruction>> instructionComparator() {
+    return ComparatorUtils.listComparator((x, y) -> x.compareTo(y, this));
+  }
+
+  public Comparator<List<CfTryCatch>> tryCatchRangesComparator() {
+    return ComparatorUtils.listComparator((x, y) -> x.compareTo(y, this));
+  }
+
+  public Comparator<List<CfCode.LocalVariableInfo>> localVariablesComparator() {
+    return ComparatorUtils.listComparator((x, y) -> x.compareTo(y, this));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 4dcc108..36f43ba 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -64,8 +64,7 @@
   }
 
   public static ClassAccessFlags fromDexAccessFlags(int access) {
-    // Assume that the SUPER flag should be set (behaviour for Java versions > 1.1).
-    return new ClassAccessFlags((access & DEX_FLAGS) | Constants.ACC_SUPER);
+    return new ClassAccessFlags(access & DEX_FLAGS);
   }
 
   public static ClassAccessFlags fromCfAccessFlags(int access) {
@@ -84,6 +83,10 @@
 
   @Override
   public int getAsCfAccessFlags() {
+    assert !isInterface() || isAbstract();
+    assert !isInterface() || !isSuper();
+    assert !isInterface() || !isFinal();
+    assert !isInterface() || !isEnum();
     return materialize();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java b/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java
index 0761cac..3bb9931 100644
--- a/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java
@@ -9,8 +9,9 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.util.Comparator;
 
-public class DebugLocalInfo {
+public class DebugLocalInfo implements Comparable<DebugLocalInfo> {
 
   public enum PrintLevel {
     NONE,
@@ -30,6 +31,14 @@
     this.signature = signature;
   }
 
+  @Override
+  public int compareTo(DebugLocalInfo other) {
+    return Comparator.comparing((DebugLocalInfo info) -> info.name, DexString::slowCompareTo)
+        .thenComparing(info -> info.type, DexType::slowCompareTo)
+        .thenComparing(info -> info.signature, Comparator.nullsFirst(DexString::slowCompareTo))
+        .compare(this, other);
+  }
+
   public static boolean localsInfoMapsEqual(
       Int2ReferenceMap<DebugLocalInfo> set0, Int2ReferenceMap<DebugLocalInfo> set1) {
     if (set0 == null) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index e3bf2d8..cde4638 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -562,7 +562,11 @@
   }
 
   public DexType getType() {
-    return this.type;
+    return type;
+  }
+
+  public DexType getSuperType() {
+    return superType;
   }
 
   public boolean hasClassInitializer() {
@@ -817,6 +821,11 @@
     return null;
   }
 
+  public void forEachNestMember(Consumer<DexType> consumer) {
+    assert isNestHost();
+    getNestMembersClassAttributes().forEach(member -> consumer.accept(member.getNestMember()));
+  }
+
   public NestHostClassAttribute getNestHostClassAttribute() {
     return nestHost;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
index 7b11467..288b336 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
@@ -4,21 +4,11 @@
 
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.origin.Origin;
-
-public class DexClassAndField {
-
-  private final DexClass holder;
-  private final DexEncodedField field;
+public class DexClassAndField extends DexClassAndMember<DexEncodedField, DexField> {
 
   DexClassAndField(DexClass holder, DexEncodedField field) {
-    assert holder != null;
-    assert field != null;
-    assert holder.type == field.holder();
+    super(holder, field);
     assert holder.isProgramClass() == (this instanceof ProgramField);
-    this.holder = holder;
-    this.field = field;
   }
 
   public static DexClassAndField create(DexClass holder, DexEncodedField field) {
@@ -29,26 +19,6 @@
     }
   }
 
-  public DexClass getHolder() {
-    return holder;
-  }
-
-  public DexType getHolderType() {
-    return holder.type;
-  }
-
-  public DexEncodedField getDefinition() {
-    return field;
-  }
-
-  public DexField getReference() {
-    return field.field;
-  }
-
-  public Origin getOrigin() {
-    return holder.origin;
-  }
-
   public boolean isProgramField() {
     return false;
   }
@@ -56,23 +26,4 @@
   public ProgramField asProgramField() {
     return null;
   }
-
-  public String toSourceString() {
-    return getReference().toSourceString();
-  }
-
-  @Override
-  public String toString() {
-    return toSourceString();
-  }
-
-  @Override
-  public boolean equals(Object object) {
-    throw new Unreachable("Unsupported attempt at comparing Class and DexClassAndField");
-  }
-
-  @Override
-  public int hashCode() {
-    throw new Unreachable("Unsupported attempt at computing the hashcode of DexClassAndField");
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
new file mode 100644
index 0000000..0bc5348
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+
+public abstract class DexClassAndMember<
+    D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
+
+  private final DexClass holder;
+  private final D definition;
+
+  public DexClassAndMember(DexClass holder, D definition) {
+    assert holder != null;
+    assert definition != null;
+    assert holder.type == definition.holder();
+    this.holder = holder;
+    this.definition = definition;
+  }
+
+  public DexType getContextType() {
+    return getHolderType();
+  }
+
+  public DexClass getHolder() {
+    return holder;
+  }
+
+  public DexType getHolderType() {
+    return holder.type;
+  }
+
+  public D getDefinition() {
+    return definition;
+  }
+
+  public R getReference() {
+    return definition.toReference();
+  }
+
+  public Origin getOrigin() {
+    return holder.origin;
+  }
+
+  public String toSourceString() {
+    return getReference().toSourceString();
+  }
+
+  @Override
+  public String toString() {
+    return toSourceString();
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    throw new Unreachable("Unsupported attempt at comparing Class and DexClassAndMember");
+  }
+
+  @Override
+  public int hashCode() {
+    throw new Unreachable("Unsupported attempt at computing the hash code of DexClassAndMember");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index c6c2f07..87d1bb9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -4,21 +4,12 @@
 
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.origin.Origin;
-
-public class DexClassAndMethod implements LookupTarget {
-
-  private final DexClass holder;
-  private final DexEncodedMethod method;
+public class DexClassAndMethod extends DexClassAndMember<DexEncodedMethod, DexMethod>
+    implements LookupTarget {
 
   DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
-    assert holder != null;
-    assert method != null;
-    assert holder.type == method.holder();
+    super(holder, method);
     assert holder.isProgramClass() == (this instanceof ProgramMethod);
-    this.holder = holder;
-    this.method = method;
   }
 
   public static DexClassAndMethod create(DexClass holder, DexEncodedMethod method) {
@@ -33,16 +24,6 @@
   }
 
   @Override
-  public boolean equals(Object object) {
-    throw new Unreachable("Unsupported attempt at comparing Class and DexClassAndMethod");
-  }
-
-  @Override
-  public int hashCode() {
-    throw new Unreachable("Unsupported attempt at computing the hashcode of DexClassAndMethod");
-  }
-
-  @Override
   public boolean isMethodTarget() {
     return true;
   }
@@ -52,26 +33,6 @@
     return this;
   }
 
-  public DexClass getHolder() {
-    return holder;
-  }
-
-  public DexType getHolderType() {
-    return holder.type;
-  }
-
-  public DexEncodedMethod getDefinition() {
-    return method;
-  }
-
-  public DexMethod getReference() {
-    return method.method;
-  }
-
-  public Origin getOrigin() {
-    return holder.origin;
-  }
-
   public boolean isClasspathMethod() {
     return false;
   }
@@ -87,13 +48,4 @@
   public ProgramMethod asProgramMethod() {
     return null;
   }
-
-  public String toSourceString() {
-    return method.method.toSourceString();
-  }
-
-  @Override
-  public String toString() {
-    return toSourceString();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index b8a1e8f..ce47f1e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.utils.ComparatorUtils.arrayComparator;
+
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.SwitchPayload;
@@ -20,11 +22,13 @@
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Strings;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -32,7 +36,7 @@
 import java.util.Set;
 
 // DexCode corresponds to code item in dalvik/dex-format.html
-public class DexCode extends Code {
+public class DexCode extends Code implements Comparable<DexCode> {
 
   static final String FAKE_THIS_PREFIX = "_";
   static final String FAKE_THIS_SUFFIX = "this";
@@ -63,6 +67,9 @@
     this.tries = tries;
     this.handlers = handlers;
     this.debugInfo = debugInfo;
+    assert tries != null;
+    assert handlers != null;
+    assert instructions != null;
     hashCode();  // Cache the hash code eagerly.
   }
 
@@ -159,11 +166,6 @@
     return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
   }
 
-  public int codeSizeInBytes() {
-    Instruction last = instructions[instructions.length - 1];
-    return last.getOffset() + last.getSize();
-  }
-
   @Override
   public int computeHashCode() {
     return incomingRegisterSize * 2
@@ -176,37 +178,26 @@
   }
 
   @Override
-  public boolean computeEquals(Object other) {
-    if (other instanceof DexCode) {
-      DexCode o = (DexCode) other;
-      if (incomingRegisterSize != o.incomingRegisterSize) {
-        return false;
-      }
-      if (registerSize != o.registerSize) {
-        return false;
-      }
-      if (outgoingRegisterSize != o.outgoingRegisterSize) {
-        return false;
-      }
-      if (debugInfo == null) {
-        if (o.debugInfo != null) {
-          return false;
-        }
-      } else {
-        if (!debugInfo.equals(o.debugInfo)) {
-          return false;
-        }
-      }
-      if (!Arrays.equals(tries, o.tries)) {
-        return false;
-      }
-      if (!Arrays.equals(handlers, o.handlers)) {
-        return false;
-      }
-      // Save the most expensive operation to last.
-      return Arrays.equals(instructions, o.instructions);
+  public int compareTo(DexCode other) {
+    if (this == other) {
+      return 0;
     }
-    return false;
+    int diff =
+        Comparator.comparingInt((DexCode c) -> c.incomingRegisterSize)
+            .thenComparingInt(c -> c.registerSize)
+            .thenComparingInt(c -> c.outgoingRegisterSize)
+            .thenComparing(c -> c.tries, arrayComparator())
+            .thenComparing(c -> c.handlers, arrayComparator())
+            .thenComparing(c -> c.debugInfo, Comparator.nullsFirst(DexDebugInfo::compareTo))
+            .thenComparing((DexCode c) -> c.instructions, arrayComparator())
+            .compare(this, other);
+    assert (diff == 0) == (0 == toString().compareTo(other.toString()));
+    return diff;
+  }
+
+  @Override
+  public boolean computeEquals(Object other) {
+    return other instanceof DexCode && compareTo((DexCode) other) == 0;
   }
 
   @Override
@@ -259,11 +250,9 @@
     for (Instruction insn : instructions) {
       insn.registerUse(registry);
     }
-    if (handlers != null) {
-      for (TryHandler handler : handlers) {
-        for (TypeAddrPair pair : handler.pairs) {
-          registry.registerTypeReference(pair.type);
-        }
+    for (TryHandler handler : handlers) {
+      for (TypeAddrPair pair : handler.pairs) {
+        registry.registerTypeReference(pair.type);
       }
     }
   }
@@ -330,14 +319,12 @@
         builder.append(atry.toString());
         builder.append('\n');
       }
-      if (handlers != null) {
-        builder.append("Handlers (numbers are offsets)\n");
-        for (int handlerIndex = 0; handlerIndex < handlers.length; handlerIndex++) {
-          TryHandler handler = handlers[handlerIndex];
-          builder.append("  ").append(handlerIndex).append(": ");
-          builder.append(handler.toString());
-          builder.append('\n');
-        }
+      builder.append("Handlers (numbers are offsets)\n");
+      for (int handlerIndex = 0; handlerIndex < handlers.length; handlerIndex++) {
+        TryHandler handler = handlers[handlerIndex];
+        builder.append("  ").append(handlerIndex).append(": ");
+        builder.append(handler.toString());
+        builder.append('\n');
       }
     }
     return builder.toString();
@@ -393,13 +380,11 @@
         builder.append(atry.toString());
         builder.append('\n');
       }
-      if (handlers != null) {
         builder.append("Handlers (numbers are offsets)\n");
         for (TryHandler handler : handlers) {
           builder.append(handler.toString());
           builder.append('\n');
         }
-      }
     }
     return builder.toString();
   }
@@ -422,11 +407,9 @@
     if (debugInfo != null) {
       getDebugInfoForWriting().collectIndexedItems(indexedItems, graphLens);
     }
-    if (handlers != null) {
       for (TryHandler handler : handlers) {
         handler.collectIndexedItems(indexedItems, graphLens);
       }
-    }
   }
 
   public DexDebugInfoForWriting getDebugInfoForWriting() {
@@ -460,7 +443,12 @@
     }
   }
 
-  public static class Try extends DexItem {
+  public int codeSizeInBytes() {
+    Instruction last = instructions[instructions.length - 1];
+    return last.getOffset() + last.getSize();
+  }
+
+  public static class Try extends DexItem implements Comparable<Try> {
 
     public static final int NO_INDEX = -1;
 
@@ -487,20 +475,18 @@
 
     @Override
     public boolean equals(Object other) {
+      return other instanceof Try && compareTo((Try) other) == 0;
+    }
+
+    @Override
+    public int compareTo(Try other) {
       if (this == other) {
-        return true;
+        return 0;
       }
-      if (other instanceof Try) {
-        Try o = (Try) other;
-        if (startAddress != o.startAddress) {
-          return false;
-        }
-        if (instructionCount != o.instructionCount) {
-          return false;
-        }
-        return handlerIndex == o.handlerIndex;
-      }
-      return false;
+      return ComparatorUtils.compareInts(
+          startAddress, other.startAddress,
+          instructionCount, other.instructionCount,
+          handlerIndex, other.handlerIndex);
     }
 
     @Override
@@ -521,7 +507,7 @@
 
   }
 
-  public static class TryHandler extends DexItem {
+  public static class TryHandler extends DexItem implements Comparable<TryHandler> {
 
     public static final int NO_HANDLER = -1;
 
@@ -540,17 +526,17 @@
 
     @Override
     public boolean equals(Object other) {
+      return other instanceof TryHandler && compareTo((TryHandler) other) == 0;
+    }
+
+    @Override
+    public int compareTo(TryHandler other) {
       if (this == other) {
-        return true;
+        return 0;
       }
-      if (other instanceof TryHandler) {
-        TryHandler o = (TryHandler) other;
-        if (catchAllAddr != o.catchAllAddr) {
-          return false;
-        }
-        return Arrays.equals(pairs, o.pairs);
-      }
-      return false;
+      return Comparator.comparingInt((TryHandler h) -> h.catchAllAddr)
+          .thenComparing(h -> h.pairs, arrayComparator())
+          .compare(this, other);
     }
 
     public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) {
@@ -585,7 +571,7 @@
       return builder.toString();
     }
 
-    public static class TypeAddrPair extends DexItem {
+    public static class TypeAddrPair extends DexItem implements Comparable<TypeAddrPair> {
 
       public final DexType type;
       public final /* offset */ int addr;
@@ -613,14 +599,17 @@
 
       @Override
       public boolean equals(Object other) {
+        return other instanceof TypeAddrPair && compareTo((TypeAddrPair) other) == 0;
+      }
+
+      @Override
+      public int compareTo(TypeAddrPair other) {
         if (this == other) {
-          return true;
+          return 0;
         }
-        if (other instanceof TypeAddrPair) {
-          TypeAddrPair o = (TypeAddrPair) other;
-          return type.equals(o.type) && addr == o.addr;
-        }
-        return false;
+        return Comparator.comparingInt((TypeAddrPair p) -> p.addr)
+            .thenComparing(p -> p.type, DexType::slowCompareTo)
+            .compare(this, other);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 0e59082..903686d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -8,9 +8,14 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.ir.code.Position;
+import java.util.Comparator;
 import java.util.Objects;
 
-abstract public class DexDebugEvent extends DexItem {
+public abstract class DexDebugEvent extends DexItem implements Comparable<DexDebugEvent> {
+
+  // Compare ID(s) for virtual debug events.
+  private static final int DBG_SET_INLINE_FRAME_COMPARE_ID = Constants.DBG_LAST_SPECIAL + 1;
+
   public static final DexDebugEvent[] EMPTY_ARRAY = {};
 
   public void collectIndexedItems(IndexedItemCollection collection, GraphLens graphLens) {
@@ -30,7 +35,22 @@
   abstract public int hashCode();
 
   @Override
-  abstract public boolean equals(Object other);
+  public final boolean equals(Object other) {
+    return other instanceof DexDebugEvent && compareTo((DexDebugEvent) other) == 0;
+  }
+
+  abstract int getCompareToId();
+
+  abstract int internalCompareTo(DexDebugEvent other);
+
+  @Override
+  public final int compareTo(DexDebugEvent other) {
+    if (this == other) {
+      return 0;
+    }
+    int diff = Integer.compare(getCompareToId(), other.getCompareToId());
+    return diff != 0 ? diff : internalCompareTo(other);
+  }
 
   public abstract void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping);
 
@@ -77,9 +97,13 @@
     }
 
     @Override
-    public boolean equals(Object other) {
-      return (other instanceof AdvancePC)
-          && (delta == ((AdvancePC) other).delta);
+    int getCompareToId() {
+      return Constants.DBG_ADVANCE_PC;
+    }
+
+    @Override
+    int internalCompareTo(DexDebugEvent other) {
+      return Integer.compare(delta, ((AdvancePC) other).delta);
     }
   }
 
@@ -110,8 +134,14 @@
     }
 
     @Override
-    public boolean equals(Object other) {
-      return other instanceof SetPrologueEnd;
+    int getCompareToId() {
+      return Constants.DBG_SET_PROLOGUE_END;
+    }
+
+    @Override
+    int internalCompareTo(DexDebugEvent other) {
+      assert other instanceof SetPrologueEnd;
+      return 0;
     }
   }
 
@@ -142,8 +172,14 @@
     }
 
     @Override
-    public boolean equals(Object other) {
-      return other instanceof SetEpilogueBegin;
+    int getCompareToId() {
+      return Constants.DBG_SET_EPILOGUE_BEGIN;
+    }
+
+    @Override
+    int internalCompareTo(DexDebugEvent other) {
+      assert other instanceof SetEpilogueBegin;
+      return 0;
     }
   }
 
@@ -178,9 +214,13 @@
     }
 
     @Override
-    public boolean equals(Object other) {
-      return (other instanceof AdvanceLine)
-          && (delta == ((AdvanceLine) other).delta);
+    int getCompareToId() {
+      return Constants.DBG_ADVANCE_LINE;
+    }
+
+    @Override
+    int internalCompareTo(DexDebugEvent other) {
+      return Integer.compare(delta, ((AdvanceLine) other).delta);
     }
   }
 
@@ -253,21 +293,17 @@
     }
 
     @Override
-    public boolean equals(Object other) {
-      if (!(other instanceof StartLocal)) {
-        return false;
-      }
-      StartLocal o = (StartLocal) other;
-      if (registerNum != o.registerNum) {
-        return false;
-      }
-      if (!Objects.equals(name, o.name)) {
-        return false;
-      }
-      if (!Objects.equals(type, o.type)) {
-        return false;
-      }
-      return Objects.equals(signature, o.signature);
+    int getCompareToId() {
+      return Constants.DBG_START_LOCAL;
+    }
+
+    @Override
+    int internalCompareTo(DexDebugEvent other) {
+      return Comparator.comparingInt((StartLocal e) -> e.registerNum)
+          .thenComparing(e -> e.name, DexString::slowCompareTo)
+          .thenComparing(e -> e.type, DexType::slowCompareTo)
+          .thenComparing(e -> e.signature, Comparator.nullsFirst(DexString::slowCompareTo))
+          .compare(this, (StartLocal) other);
     }
   }
 
@@ -302,9 +338,13 @@
     }
 
     @Override
-    public boolean equals(Object other) {
-      return (other instanceof EndLocal)
-          && (registerNum == ((EndLocal) other).registerNum);
+    int getCompareToId() {
+      return Constants.DBG_END_LOCAL;
+    }
+
+    @Override
+    int internalCompareTo(DexDebugEvent other) {
+      return Integer.compare(registerNum, ((EndLocal) other).registerNum);
     }
   }
 
@@ -339,9 +379,13 @@
     }
 
     @Override
-    public boolean equals(Object other) {
-      return (other instanceof RestartLocal)
-          && (registerNum == ((RestartLocal) other).registerNum);
+    int getCompareToId() {
+      return Constants.DBG_RESTART_LOCAL;
+    }
+
+    @Override
+    int internalCompareTo(DexDebugEvent other) {
+      return Integer.compare(registerNum, ((RestartLocal) other).registerNum);
     }
   }
 
@@ -381,9 +425,13 @@
     }
 
     @Override
-    public boolean equals(Object other) {
-      return (other instanceof SetFile)
-          && fileName.equals(((SetFile) other).fileName);
+    int getCompareToId() {
+      return Constants.DBG_SET_FILE;
+    }
+
+    @Override
+    int internalCompareTo(DexDebugEvent other) {
+      return fileName.slowCompareTo(((SetFile) other).fileName);
     }
   }
 
@@ -419,12 +467,15 @@
     }
 
     @Override
-    public boolean equals(Object other) {
-      if (!(other instanceof SetInlineFrame)) {
-        return false;
-      }
-      SetInlineFrame o = (SetInlineFrame) other;
-      return callee == o.callee && Objects.equals(caller, o.caller);
+    int getCompareToId() {
+      return DBG_SET_INLINE_FRAME_COMPARE_ID;
+    }
+
+    @Override
+    int internalCompareTo(DexDebugEvent other) {
+      return Comparator.comparing((SetInlineFrame e) -> e.callee, DexMethod::slowCompareTo)
+          .thenComparing(e -> e.caller, Comparator.nullsFirst(Position::compareTo))
+          .compare(this, (SetInlineFrame) other);
     }
 
     @Override
@@ -484,9 +535,13 @@
     }
 
     @Override
-    public boolean equals(Object other) {
-      return (other instanceof Default)
-          && (value == ((Default) other).value);
+    int getCompareToId() {
+      return Constants.DBG_FIRST_SPECIAL;
+    }
+
+    @Override
+    int internalCompareTo(DexDebugEvent other) {
+      return Integer.compare(value, ((Default) other).value);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index ed4f2c5..5e44a57 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -5,10 +5,12 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.utils.ComparatorUtils;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
 
-public class DexDebugInfo extends CachedHashValueDexItem {
+public class DexDebugInfo extends CachedHashValueDexItem implements Comparable<DexDebugInfo> {
 
   public final int startLine;
   public final DexString[] parameters;
@@ -40,18 +42,21 @@
   }
 
   @Override
-  public boolean computeEquals(Object other) {
-    if (other instanceof DexDebugInfo) {
-      DexDebugInfo o = (DexDebugInfo) other;
-      if (startLine != o.startLine) {
-        return false;
-      }
-      if (!Arrays.equals(parameters, o.parameters)) {
-        return false;
-      }
-      return Arrays.equals(events, o.events);
+  public final boolean computeEquals(Object other) {
+    return other instanceof DexDebugInfo && compareTo((DexDebugInfo) other) == 0;
+  }
+
+  @Override
+  public final int compareTo(DexDebugInfo other) {
+    if (this == other) {
+      return 0;
     }
-    return false;
+    return Comparator.comparingInt((DexDebugInfo i) -> i.startLine)
+        .thenComparing(
+            i -> i.parameters,
+            ComparatorUtils.arrayComparator(Comparator.nullsFirst(DexString::slowCompareTo)))
+        .thenComparing(i -> i.events, ComparatorUtils.arrayComparator())
+        .compare(this, other);
   }
 
   public void collectIndexedItems(IndexedItemCollection indexedItems, GraphLens graphLens) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index e480799..cb4eb4e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.utils.ArrayUtils;
 import java.util.Arrays;
+import java.util.function.Consumer;
 import java.util.function.Function;
 
 public class DexEncodedAnnotation extends DexItem {
@@ -30,6 +31,12 @@
     }
   }
 
+  public void forEachElement(Consumer<DexAnnotationElement> consumer) {
+    for (DexAnnotationElement element : elements) {
+      consumer.accept(element);
+    }
+  }
+
   public DexAnnotationElement getElement(int i) {
     return elements[i];
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c40383d..4936ceb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -298,11 +298,21 @@
   public int syntheticCompareTo(DexEncodedMethod other) {
     assert annotations().isEmpty();
     assert parameterAnnotationsList.isEmpty();
-    return Comparator.comparing(DexEncodedMethod::proto, DexProto::slowCompareTo)
-        .thenComparingInt(m -> m.accessFlags.getAsCfAccessFlags())
-        // TODO(b/158159959): Implement structural compareTo on code.
-        .thenComparing(m -> m.getCode().toString())
-        .compare(this, other);
+    Comparator<DexEncodedMethod> comparator =
+        Comparator.comparing(DexEncodedMethod::proto, DexProto::slowCompareTo)
+            .thenComparingInt(m -> m.accessFlags.getAsCfAccessFlags());
+    if (code.isCfCode() && other.getCode().isCfCode()) {
+      comparator = comparator.thenComparing(m -> m.getCode().asCfCode());
+    } else if (code.isDexCode() && other.getCode().isDexCode()) {
+      comparator = comparator.thenComparing(m -> m.getCode().asDexCode());
+    } else {
+      throw new Unreachable(
+          "Unexpected attempt to compare incompatible synthetic objects: "
+              + code
+              + " and "
+              + other.getCode());
+    }
+    return comparator.compare(this, other);
   }
 
   public DexType getHolderType() {
@@ -765,6 +775,10 @@
     return builder.toString();
   }
 
+  public ParameterAnnotationsList getParameterAnnotations() {
+    return parameterAnnotationsList;
+  }
+
   public void clearParameterAnnotations() {
     parameterAnnotationsList = ParameterAnnotationsList.empty();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index a58ec39..fcb79fe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -23,6 +23,14 @@
     }
   }
 
+  public DexType getHolderType() {
+    return holder;
+  }
+
+  public DexType getType() {
+    return type;
+  }
+
   @Override
   public DexEncodedField lookupOnClass(DexClass clazz) {
     return clazz != null ? clazz.lookupField(this) : null;
@@ -149,4 +157,8 @@
   public String toSourceString() {
     return type.toSourceString() + " " + holder.toSourceString() + "." + name.toSourceString();
   }
+
+  public DexField withHolder(DexType holder, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createField(holder, type, name);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 26f114f..b3e11f5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -29,6 +29,10 @@
     }
   }
 
+  public DexType getHolderType() {
+    return holder;
+  }
+
   public DexString getName() {
     return name;
   }
@@ -71,6 +75,10 @@
     return clazz != null ? clazz.lookupMember(this) : null;
   }
 
+  public ProgramMethod lookupOnProgramClass(DexProgramClass clazz) {
+    return clazz != null ? clazz.lookupProgramMethod(this) : null;
+  }
+
   @Override
   public String toString() {
     return "Method " + holder + "." + name + " " + proto.toString();
@@ -232,7 +240,11 @@
         && proto == dexItemFactory.deserializeLambdaMethodProto;
   }
 
-  public boolean isInstanceInitializer(DexDefinitionSupplier definitions) {
-    return definitions.dexItemFactory().isConstructor(this);
+  public boolean isInstanceInitializer(DexItemFactory factory) {
+    return factory.isConstructor(this);
+  }
+
+  public DexMethod withHolder(DexType holder, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createMethod(holder, proto, name);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index f0efe20..ca7a264 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -29,7 +29,8 @@
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
-public class DexProgramClass extends DexClass implements Supplier<DexProgramClass> {
+public class DexProgramClass extends DexClass
+    implements ProgramDefinition, Supplier<DexProgramClass> {
 
   @FunctionalInterface
   public interface ChecksumSupplier {
@@ -491,6 +492,16 @@
     return this;
   }
 
+  @Override
+  public DexType getContextType() {
+    return getType();
+  }
+
+  @Override
+  public DexProgramClass getDefinition() {
+    return this;
+  }
+
   public void setInitialClassFileVersion(int initialClassFileVersion) {
     assert this.initialClassFileVersion == -1 && initialClassFileVersion > 0;
     this.initialClassFileVersion = initialClassFileVersion;
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 09985c3..a0cacd9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.naming.NamingLens;
 import com.google.common.hash.Hasher;
+import java.util.function.Consumer;
 
 public class DexProto extends IndexedDexItem implements PresortedComparable<DexProto> {
 
@@ -21,6 +22,11 @@
     this.parameters = parameters;
   }
 
+  public void forEachType(Consumer<DexType> consumer) {
+    consumer.accept(returnType);
+    parameters.forEach(consumer);
+  }
+
   public DexType getParameter(int index) {
     return parameters.values[index];
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index b296b43..c60efe5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -8,9 +8,7 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 
-/**
- * A common interface for {@link DexType}, {@link DexField}, and {@link DexMethod}.
- */
+/** A common interface for {@link DexType}, {@link DexField}, and {@link DexMethod}. */
 public abstract class DexReference extends IndexedDexItem {
 
   public abstract <T> T apply(
@@ -62,4 +60,30 @@
   public DexMethod asDexMethod() {
     return null;
   }
+
+  private int referenceTypeOrder() {
+    if (isDexType()) {
+      return 1;
+    }
+    if (isDexField()) {
+      return 2;
+    }
+    assert isDexMethod();
+    return 3;
+  }
+
+  public int referenceCompareTo(DexReference o) {
+    int typeDiff = referenceTypeOrder() - o.referenceTypeOrder();
+    if (typeDiff != 0) {
+      return typeDiff;
+    }
+    if (isDexType()) {
+      return asDexType().slowCompareTo(o.asDexType());
+    }
+    if (isDexField()) {
+      return asDexField().slowCompareTo(o.asDexField());
+    }
+    assert isDexMethod();
+    return asDexMethod().slowCompareTo(o.asDexMethod());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index bc7e26f..500da1e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -15,6 +15,7 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
@@ -251,6 +252,12 @@
     return descriptor.content[0] == 'D';
   }
 
+  public boolean isNullValueType() {
+    boolean isNullValueType = descriptor.content[0] == 'N';
+    assert !isNullValueType || this == DexItemFactory.nullValueType;
+    return isNullValueType;
+  }
+
   public boolean isArrayType() {
     char firstChar = (char) descriptor.content[0];
     return firstChar == '[';
@@ -313,6 +320,7 @@
     // Any entry that is removed from here must be added to OLD_SYNTHESIZED_NAMES to ensure that
     // newer releases can be used to merge previous builds.
     return name.contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX) // Shared among enums.
+        || name.contains(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX)
         || name.contains(LAMBDA_CLASS_NAME_PREFIX) // Could collide.
         || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
         || name.contains(DISPATCH_CLASS_NAME_SUFFIX) // Shared on reference.
@@ -407,13 +415,19 @@
   public DexType replacePackage(String newPackageDescriptor, DexItemFactory dexItemFactory) {
     assert isClassType();
     String descriptorString = toDescriptorString();
-    int lastPackageSeparator = descriptorString.lastIndexOf('/');
-    String newDescriptorString = "L" + newPackageDescriptor + "/";
-    if (lastPackageSeparator >= 0) {
-      newDescriptorString += descriptorString.substring(lastPackageSeparator + 1);
-    } else {
-      newDescriptorString += descriptorString.substring(1);
+    String newDescriptorString = "L";
+    if (!newPackageDescriptor.isEmpty()) {
+      newDescriptorString += newPackageDescriptor + "/";
     }
+    newDescriptorString += DescriptorUtils.getSimpleClassNameFromDescriptor(descriptorString) + ";";
+    return dexItemFactory.createType(newDescriptorString);
+  }
+
+  public DexType addSuffix(String suffix, DexItemFactory dexItemFactory) {
+    assert isClassType();
+    String descriptorString = toDescriptorString();
+    int endIndex = descriptorString.length() - 1;
+    String newDescriptorString = descriptorString.substring(0, endIndex) + suffix + ";";
     return dexItemFactory.createType(newDescriptorString);
   }
 
@@ -426,7 +440,7 @@
   }
 
   public DexType toArrayElementType(DexItemFactory dexItemFactory) {
-    assert this.isArrayType();
+    assert isArrayType();
     DexString newDesc =
         dexItemFactory.createString(
             descriptor.size - 1,
@@ -458,6 +472,17 @@
     return getPackageOrName(false);
   }
 
+  public String getSimpleName() {
+    assert isClassType();
+    return DescriptorUtils.getSimpleClassNameFromDescriptor(toDescriptorString());
+  }
+
+  public DexType withSimpleName(String newSimpleName, DexItemFactory dexItemFactory) {
+    assert isClassType();
+    return dexItemFactory.createType(
+        DescriptorUtils.replaceSimpleClassNameInDescriptor(toDescriptorString(), newSimpleName));
+  }
+
   /** Get the fully qualified name using '/' in place of '.', aka the "internal type name" in ASM */
   public String getInternalName() {
     assert isClassType() || isArrayType();
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index c61de91..ae25597 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.ArrayUtils;
 import java.util.Arrays;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 public class DexTypeList extends DexItem {
@@ -34,6 +35,12 @@
     return ArrayUtils.contains(values, type);
   }
 
+  public void forEach(Consumer<DexType> consumer) {
+    for (DexType value : values) {
+      consumer.accept(value);
+    }
+  }
+
   @Override
   public int hashCode() {
     return Arrays.hashCode(values);
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 2fe8c3f..273c69a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.EncodedValueUtils;
 import java.util.Arrays;
+import java.util.function.Consumer;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Type;
 
@@ -1381,6 +1382,12 @@
       this.values = values;
     }
 
+    public void forEachElement(Consumer<DexValue> consumer) {
+      for (DexValue value : values) {
+        consumer.accept(value);
+      }
+    }
+
     public DexValue[] getValues() {
       return values;
     }
@@ -1474,6 +1481,10 @@
       this.value = value;
     }
 
+    public DexEncodedAnnotation getValue() {
+      return value;
+    }
+
     @Override
     public DexValueKind getValueKind() {
       return DexValueKind.ANNOTATION;
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index c82fcf0..88869df 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -9,11 +9,20 @@
 import java.util.Map;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 public class FieldAccessInfoCollectionImpl
     implements FieldAccessInfoCollection<FieldAccessInfoImpl> {
 
-  private Map<DexField, FieldAccessInfoImpl> infos = new IdentityHashMap<>();
+  private final Map<DexField, FieldAccessInfoImpl> infos;
+
+  public FieldAccessInfoCollectionImpl() {
+    this(new IdentityHashMap<>());
+  }
+
+  public FieldAccessInfoCollectionImpl(Map<DexField, FieldAccessInfoImpl> infos) {
+    this.infos = infos;
+  }
 
   @Override
   public void destroyAccessContexts() {
@@ -25,6 +34,11 @@
     infos.values().forEach(FieldAccessInfoImpl::flattenAccessContexts);
   }
 
+  public FieldAccessInfoImpl computeIfAbsent(
+      DexField field, Function<DexField, FieldAccessInfoImpl> fn) {
+    return infos.computeIfAbsent(field, fn);
+  }
+
   @Override
   public boolean contains(DexField field) {
     return infos.containsKey(field);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 023e423..9dae761 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -71,6 +71,14 @@
     return field;
   }
 
+  public AbstractAccessContexts getReadsWithContexts() {
+    return readsWithContexts;
+  }
+
+  public void setReadsWithContexts(AbstractAccessContexts readsWithContexts) {
+    this.readsWithContexts = readsWithContexts;
+  }
+
   @Override
   public int getNumberOfReadContexts() {
     return readsWithContexts.getNumberOfAccessContexts();
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 14a4e95..7bfa6cb 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -3,11 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.horizontalclassmerging.ClassMerger;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
 import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
@@ -21,7 +27,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Supplier;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * A GraphLens implements a virtual view on top of the graph, used to delay global rewrites until
@@ -41,6 +47,89 @@
  */
 public abstract class GraphLens {
 
+  abstract static class MemberLookupResult<R extends DexMember<?, R>> {
+
+    private final R reference;
+    private final R reboundReference;
+
+    private MemberLookupResult(R reference, R reboundReference) {
+      this.reference = reference;
+      this.reboundReference = reboundReference;
+    }
+
+    public R getReference() {
+      return reference;
+    }
+
+    public R getRewrittenReference(Map<R, R> rewritings) {
+      return rewritings.getOrDefault(reference, reference);
+    }
+
+    public boolean hasReboundReference() {
+      return reboundReference != null;
+    }
+
+    public R getReboundReference() {
+      return reboundReference;
+    }
+
+    public R getRewrittenReboundReference(Map<R, R> rewritings) {
+      return rewritings.getOrDefault(reboundReference, reboundReference);
+    }
+
+    abstract static class Builder<R extends DexMember<?, R>, Self extends Builder<R, Self>> {
+
+      R reference;
+      R reboundReference;
+
+      public Self setReference(R reference) {
+        this.reference = reference;
+        return self();
+      }
+
+      public Self setReboundReference(R reboundReference) {
+        this.reboundReference = reboundReference;
+        return self();
+      }
+
+      public abstract Self self();
+    }
+  }
+
+  /**
+   * Intermediate result of a field lookup that stores the actual non-rebound reference and the
+   * rebound reference that points to the definition of the field.
+   */
+  public static class FieldLookupResult extends MemberLookupResult<DexField> {
+
+    private FieldLookupResult(DexField reference, DexField reboundReference) {
+      super(reference, reboundReference);
+    }
+
+    public static Builder builder(GraphLens lens) {
+      return new Builder(lens);
+    }
+
+    public static class Builder extends MemberLookupResult.Builder<DexField, Builder> {
+
+      private GraphLens lens;
+
+      private Builder(GraphLens lens) {
+        this.lens = lens;
+      }
+
+      @Override
+      public Builder self() {
+        return this;
+      }
+
+      public FieldLookupResult build() {
+        // TODO(b/168282032): All non-identity graph lenses should set the rebound reference.
+        return new FieldLookupResult(reference, reboundReference);
+      }
+    }
+  }
+
   /**
    * Result of a method lookup in a GraphLens.
    *
@@ -48,21 +137,23 @@
    * prototype changes that have been made to the target method and the corresponding required
    * changes to the invoke arguments.
    */
-  public static class GraphLensLookupResult {
+  public static class MethodLookupResult extends MemberLookupResult<DexMethod> {
 
-    private final DexMethod method;
     private final Type type;
     private final RewrittenPrototypeDescription prototypeChanges;
 
-    public GraphLensLookupResult(
-        DexMethod method, Type type, RewrittenPrototypeDescription prototypeChanges) {
-      this.method = method;
+    public MethodLookupResult(
+        DexMethod reference,
+        DexMethod reboundReference,
+        Type type,
+        RewrittenPrototypeDescription prototypeChanges) {
+      super(reference, reboundReference);
       this.type = type;
       this.prototypeChanges = prototypeChanges;
     }
 
-    public DexMethod getMethod() {
-      return method;
+    public static Builder builder(GraphLens lens) {
+      return new Builder(lens);
     }
 
     public Type getType() {
@@ -72,6 +163,38 @@
     public RewrittenPrototypeDescription getPrototypeChanges() {
       return prototypeChanges;
     }
+
+    public static class Builder extends MemberLookupResult.Builder<DexMethod, Builder> {
+
+      private final GraphLens lens;
+      private RewrittenPrototypeDescription prototypeChanges = RewrittenPrototypeDescription.none();
+      private Type type;
+
+      private Builder(GraphLens lens) {
+        this.lens = lens;
+      }
+
+      public Builder setPrototypeChanges(RewrittenPrototypeDescription prototypeChanges) {
+        this.prototypeChanges = prototypeChanges;
+        return this;
+      }
+
+      public Builder setType(Type type) {
+        this.type = type;
+        return this;
+      }
+
+      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);
+      }
+
+      @Override
+      public Builder self() {
+        return this;
+      }
+    }
   }
 
   public static class Builder {
@@ -141,9 +264,11 @@
     }
   }
 
-  public static Builder builder() {
-    return new Builder();
-  }
+  /**
+   * Intentionally private. All graph lenses except for {@link IdentityGraphLens} should inherit
+   * from {@link NonIdentityGraphLens}.
+   */
+  private GraphLens() {}
 
   public abstract DexType getOriginalType(DexType type);
 
@@ -191,25 +316,50 @@
   public ProgramMethod mapProgramMethod(
       ProgramMethod oldMethod, DexDefinitionSupplier definitions) {
     DexMethod newMethod = getRenamedMethodSignature(oldMethod.getReference());
-    DexProgramClass holder = definitions.definitionForHolder(newMethod).asProgramClass();
-    return holder.lookupProgramMethod(newMethod);
+    DexProgramClass holder = asProgramClassOrNull(definitions.definitionForHolder(newMethod));
+    return newMethod.lookupOnProgramClass(holder);
   }
 
+  public abstract DexType lookupClassType(DexType type);
+
   public abstract DexType lookupType(DexType type);
 
   // This overload can be used when the graph lens is known to be context insensitive.
-  public DexMethod lookupMethod(DexMethod method) {
+  public final DexMethod lookupMethod(DexMethod method) {
     assert verifyIsContextFreeForMethod(method);
-    return lookupMethod(method, null, null).getMethod();
+    return lookupMethod(method, null, null).getReference();
   }
 
-  public abstract GraphLensLookupResult lookupMethod(
-      DexMethod method, DexMethod context, Type type);
+  /** Lookup a rebound or non-rebound method reference using the current graph lens. */
+  public MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
+    return internalLookupMethod(method, context, type, result -> result);
+  }
+
+  protected abstract MethodLookupResult internalLookupMethod(
+      DexMethod reference, DexMethod context, Type type, LookupMethodContinuation continuation);
+
+  interface LookupMethodContinuation {
+
+    MethodLookupResult lookupMethod(MethodLookupResult previous);
+  }
 
   public abstract RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
       DexMethod method);
 
-  public abstract DexField lookupField(DexField field);
+  /** Lookup a rebound or non-rebound field reference using the current graph lens. */
+  public DexField lookupField(DexField field) {
+    // Lookup the field using the graph lens and return the (non-rebound) reference from the lookup
+    // result.
+    return internalLookupField(field, FieldLookupResult::getReference);
+  }
+
+  protected abstract DexField internalLookupField(
+      DexField reference, LookupFieldContinuation continuation);
+
+  interface LookupFieldContinuation {
+
+    DexField lookupField(FieldLookupResult previous);
+  }
 
   public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
     return null;
@@ -254,8 +404,16 @@
     return true;
   }
 
-  public final boolean isIdentityLens() {
-    return this == getIdentityLens();
+  public boolean isAppliedLens() {
+    return false;
+  }
+
+  public abstract boolean isIdentityLens();
+
+  public abstract boolean isNonIdentityLens();
+
+  public NonIdentityGraphLens asNonIdentityLens() {
+    return null;
   }
 
   public boolean isInterfaceProcessorLens() {
@@ -266,17 +424,9 @@
     return null;
   }
 
-  public boolean isGraphLensWithPrevious() {
-    return false;
-  }
-
-  public GraphLensWithPrevious asGraphLensWithPrevious() {
-    return null;
-  }
-
-  public GraphLens withCodeRewritingsApplied() {
+  public GraphLens withCodeRewritingsApplied(DexItemFactory dexItemFactory) {
     if (hasCodeRewritings()) {
-      return new ClearCodeRewritingGraphLens(this);
+      return new ClearCodeRewritingGraphLens(dexItemFactory, this);
     }
     return this;
   }
@@ -317,6 +467,22 @@
     return true;
   }
 
+  public Map<DexCallSite, ProgramMethodSet> rewriteCallSites(
+      Map<DexCallSite, ProgramMethodSet> callSites, DexDefinitionSupplier definitions) {
+    Map<DexCallSite, ProgramMethodSet> result = new IdentityHashMap<>();
+    LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(definitions, this);
+    callSites.forEach(
+        (callSite, contexts) -> {
+          for (ProgramMethod context : contexts.rewrittenWithLens(definitions, this)) {
+            DexCallSite rewrittenCallSite = rewriter.rewriteCallSite(callSite, context);
+            result
+                .computeIfAbsent(rewrittenCallSite, ignore -> ProgramMethodSet.create())
+                .add(context);
+          }
+        });
+    return result;
+  }
+
   public DexReference rewriteReference(DexReference reference) {
     if (reference.isDexField()) {
       return getRenamedFieldSignature(reference.asDexField());
@@ -432,7 +598,96 @@
     return true;
   }
 
-  private static class IdentityGraphLens extends GraphLens {
+  public abstract static class NonIdentityGraphLens extends GraphLens {
+
+    private final DexItemFactory dexItemFactory;
+    private GraphLens previousLens;
+
+    private final Map<DexType, DexType> arrayTypeCache = new ConcurrentHashMap<>();
+
+    public NonIdentityGraphLens(DexItemFactory dexItemFactory, GraphLens previousLens) {
+      this.dexItemFactory = dexItemFactory;
+      this.previousLens = previousLens;
+    }
+
+    public final GraphLens getPrevious() {
+      return previousLens;
+    }
+
+    public final void withAlternativeParentLens(GraphLens lens, Action action) {
+      GraphLens oldParent = getPrevious();
+      previousLens = lens;
+      action.execute();
+      previousLens = oldParent;
+    }
+
+    @Override
+    public final DexType lookupType(DexType type) {
+      if (type.isPrimitiveType() || type.isVoidType() || type.isNullValueType()) {
+        return type;
+      }
+      if (type.isArrayType()) {
+        DexType result = arrayTypeCache.get(type);
+        if (result == null) {
+          DexType baseType = type.toBaseType(dexItemFactory);
+          DexType newType = lookupType(baseType);
+          result = baseType == newType ? type : type.replaceBaseType(newType, dexItemFactory);
+          arrayTypeCache.put(type, result);
+        }
+        return result;
+      }
+      return lookupClassType(type);
+    }
+
+    @Override
+    public final DexType lookupClassType(DexType type) {
+      assert type.isClassType() : "Expected class type, but was `" + type.toSourceString() + "`";
+      return internalDescribeLookupClassType(getPrevious().lookupClassType(type));
+    }
+
+    @Override
+    protected DexField internalLookupField(
+        DexField reference, LookupFieldContinuation continuation) {
+      return previousLens.internalLookupField(
+          reference, previous -> continuation.lookupField(internalDescribeLookupField(previous)));
+    }
+
+    @Override
+    protected MethodLookupResult internalLookupMethod(
+        DexMethod reference, DexMethod context, Type type, LookupMethodContinuation continuation) {
+      return previousLens.internalLookupMethod(
+          reference,
+          internalGetPreviousMethodSignature(context),
+          type,
+          previous -> continuation.lookupMethod(internalDescribeLookupMethod(previous, context)));
+    }
+
+    protected abstract FieldLookupResult internalDescribeLookupField(FieldLookupResult previous);
+
+    protected abstract MethodLookupResult internalDescribeLookupMethod(
+        MethodLookupResult previous, DexMethod context);
+
+    protected abstract DexType internalDescribeLookupClassType(DexType previous);
+
+    protected abstract DexMethod internalGetPreviousMethodSignature(DexMethod method);
+
+    @Override
+    public final boolean isIdentityLens() {
+      return false;
+    }
+
+    @Override
+    public final boolean isNonIdentityLens() {
+      return true;
+    }
+
+    @Override
+    public final NonIdentityGraphLens asNonIdentityLens() {
+      return this;
+    }
+  }
+
+  private static final class IdentityGraphLens extends GraphLens {
 
     private static IdentityGraphLens INSTANCE = new IdentityGraphLens();
 
@@ -443,6 +698,16 @@
     }
 
     @Override
+    public boolean isIdentityLens() {
+      return true;
+    }
+
+    @Override
+    public boolean isNonIdentityLens() {
+      return false;
+    }
+
+    @Override
     public DexType getOriginalType(DexType type) {
       return type;
     }
@@ -473,8 +738,9 @@
     }
 
     @Override
-    public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
-      return new GraphLensLookupResult(method, type, RewrittenPrototypeDescription.none());
+    public DexType lookupClassType(DexType type) {
+      assert type.isClassType();
+      return type;
     }
 
     @Override
@@ -484,8 +750,21 @@
     }
 
     @Override
-    public DexField lookupField(DexField field) {
-      return field;
+    protected DexField internalLookupField(
+        DexField reference, LookupFieldContinuation continuation) {
+      // Passes the field reference back to the next graph lens. The identity lens intentionally
+      // does not set the rebound field reference, since it does not know what that is.
+      return continuation.lookupField(
+          FieldLookupResult.builder(this).setReference(reference).build());
+    }
+
+    @Override
+    protected MethodLookupResult internalLookupMethod(
+        DexMethod reference, DexMethod context, Type type, LookupMethodContinuation continuation) {
+      // Passes the method reference back to the next graph lens. The identity lens intentionally
+      // does not set the rebound method reference, since it does not know what that is.
+      return continuation.lookupMethod(
+          MethodLookupResult.builder(this).setReference(reference).setType(type).build());
     }
 
     @Override
@@ -501,66 +780,82 @@
 
   // This lens clears all code rewriting (lookup methods mimics identity lens behavior) but still
   // relies on the previous lens for names (getRenamed/Original methods).
-  public static class ClearCodeRewritingGraphLens extends IdentityGraphLens
-      implements GraphLensWithPrevious {
+  public static class ClearCodeRewritingGraphLens extends NonIdentityGraphLens {
 
-    private final GraphLens previous;
-
-    public ClearCodeRewritingGraphLens(GraphLens previous) {
-      this.previous = previous;
-    }
-
-    @Override
-    public boolean isGraphLensWithPrevious() {
-      return true;
-    }
-
-    @Override
-    public GraphLensWithPrevious asGraphLensWithPrevious() {
-      return this;
-    }
-
-    @Override
-    public GraphLens getPrevious() {
-      return previous;
+    public ClearCodeRewritingGraphLens(DexItemFactory dexItemFactory, GraphLens previousLens) {
+      super(dexItemFactory, previousLens);
     }
 
     @Override
     public DexType getOriginalType(DexType type) {
-      return previous.getOriginalType(type);
+      return getPrevious().getOriginalType(type);
     }
 
     @Override
     public DexField getOriginalFieldSignature(DexField field) {
-      return previous.getOriginalFieldSignature(field);
+      return getPrevious().getOriginalFieldSignature(field);
     }
 
     @Override
     public DexMethod getOriginalMethodSignature(DexMethod method) {
-      return previous.getOriginalMethodSignature(method);
+      return getPrevious().getOriginalMethodSignature(method);
     }
 
     @Override
     public DexField getRenamedFieldSignature(DexField originalField) {
-      return previous.getRenamedFieldSignature(originalField);
+      return getPrevious().getRenamedFieldSignature(originalField);
     }
 
     @Override
     public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
       return this != applied
-          ? previous.getRenamedMethodSignature(originalMethod, applied)
+          ? getPrevious().getRenamedMethodSignature(originalMethod, applied)
           : originalMethod;
     }
 
     @Override
-    public DexType lookupType(DexType type) {
-      return previous.lookupType(type);
+    public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
+        DexMethod method) {
+      return getIdentityLens().lookupPrototypeChangesForMethodDefinition(method);
     }
-  }
 
-  public interface GraphLensWithPrevious {
+    @Override
+    public final DexType internalDescribeLookupClassType(DexType previous) {
+      return previous;
+    }
 
-    GraphLens getPrevious();
+    @Override
+    protected DexField internalLookupField(
+        DexField reference, LookupFieldContinuation continuation) {
+      return getIdentityLens().internalLookupField(reference, continuation);
+    }
+
+    @Override
+    protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+      throw new Unreachable();
+    }
+
+    @Override
+    protected MethodLookupResult internalLookupMethod(
+        DexMethod reference, DexMethod context, Type type, LookupMethodContinuation continuation) {
+      return getIdentityLens().internalLookupMethod(reference, context, type, continuation);
+    }
+
+    @Override
+    public MethodLookupResult internalDescribeLookupMethod(
+        MethodLookupResult previous, DexMethod context) {
+      throw new Unreachable();
+    }
+
+    @Override
+    protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+      return method;
+    }
+
+    @Override
+    public boolean isContextFreeForMethods() {
+      return getIdentityLens().isContextFreeForMethods();
+    }
   }
 
   /**
@@ -573,13 +868,11 @@
    * #mapInvocationType(DexMethod, DexMethod, Type)} if the default name mapping applies, and only
    * invocation type might need to change.
    */
-  public static class NestedGraphLens extends GraphLens implements GraphLensWithPrevious {
+  public static class NestedGraphLens extends NonIdentityGraphLens {
 
-    protected GraphLens previousLens;
     protected final DexItemFactory dexItemFactory;
 
     protected final Map<DexType, DexType> typeMap;
-    private final Map<DexType, DexType> arrayTypeCache = new IdentityHashMap<>();
     protected final Map<DexMethod, DexMethod> methodMap;
     protected final Map<DexField, DexField> fieldMap;
 
@@ -602,6 +895,7 @@
         BiMap<DexMethod, DexMethod> originalMethodSignatures,
         GraphLens previousLens,
         DexItemFactory dexItemFactory) {
+      super(dexItemFactory, previousLens);
       assert !typeMap.isEmpty()
           || !methodMap.isEmpty()
           || !fieldMap.isEmpty()
@@ -611,36 +905,16 @@
       this.fieldMap = fieldMap;
       this.originalFieldSignatures = originalFieldSignatures;
       this.originalMethodSignatures = originalMethodSignatures;
-      this.previousLens = previousLens;
       this.dexItemFactory = dexItemFactory;
     }
 
-    @Override
-    public GraphLens getPrevious() {
-      return previousLens;
-    }
-
-    public <T> T withAlternativeParentLens(GraphLens lens, Supplier<T> action) {
-      GraphLens oldParent = previousLens;
-      previousLens = lens;
-      T result = action.get();
-      previousLens = oldParent;
-      return result;
-    }
-
-    @Override
-    public boolean isGraphLensWithPrevious() {
-      return true;
-    }
-
-    @Override
-    public NestedGraphLens asGraphLensWithPrevious() {
-      return this;
+    public static Builder builder() {
+      return new Builder();
     }
 
     @Override
     public DexType getOriginalType(DexType type) {
-      return previousLens.getOriginalType(type);
+      return getPrevious().getOriginalType(type);
     }
 
     @Override
@@ -649,7 +923,7 @@
           originalFieldSignatures != null
               ? originalFieldSignatures.getOrDefault(field, field)
               : field;
-      return previousLens.getOriginalFieldSignature(originalField);
+      return getPrevious().getOriginalFieldSignature(originalField);
     }
 
     @Override
@@ -658,12 +932,12 @@
           originalMethodSignatures != null
               ? originalMethodSignatures.getOrDefault(method, method)
               : method;
-      return previousLens.getOriginalMethodSignature(originalMethod);
+      return getPrevious().getOriginalMethodSignature(originalMethod);
     }
 
     @Override
     public DexField getRenamedFieldSignature(DexField originalField) {
-      DexField renamedField = previousLens.getRenamedFieldSignature(originalField);
+      DexField renamedField = getPrevious().getRenamedFieldSignature(originalField);
       return originalFieldSignatures != null
           ? originalFieldSignatures.inverse().getOrDefault(renamedField, renamedField)
           : renamedField;
@@ -674,49 +948,73 @@
       if (this == applied) {
         return originalMethod;
       }
-      DexMethod renamedMethod = previousLens.getRenamedMethodSignature(originalMethod, applied);
+      DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied);
       return originalMethodSignatures != null
           ? originalMethodSignatures.inverse().getOrDefault(renamedMethod, renamedMethod)
           : renamedMethod;
     }
 
     @Override
-    public DexType lookupType(DexType type) {
-      if (type.isArrayType()) {
-        synchronized (this) {
-          // This block need to be synchronized due to arrayTypeCache.
-          DexType result = arrayTypeCache.get(type);
-          if (result == null) {
-            DexType baseType = type.toBaseType(dexItemFactory);
-            DexType newType = lookupType(baseType);
-            if (baseType == newType) {
-              result = type;
-            } else {
-              result = type.replaceBaseType(newType, dexItemFactory);
-            }
-            arrayTypeCache.put(type, result);
-          }
-          return result;
-        }
-      }
-      DexType previous = previousLens.lookupType(type);
+    protected DexType internalDescribeLookupClassType(DexType previous) {
       return typeMap != null ? typeMap.getOrDefault(previous, previous) : previous;
     }
 
     @Override
-    public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
-      DexMethod previousContext = internalGetPreviousMethodSignature(context);
-      GraphLensLookupResult lookup = previousLens.lookupMethod(method, previousContext, type);
-      DexMethod newMethod = methodMap.get(lookup.getMethod());
-      if (newMethod == null) {
-        return lookup;
+    protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+      if (previous.hasReboundReference()) {
+        // Rewrite the rebound reference and then "fixup" the non-rebound reference.
+        DexField rewrittenReboundReference = previous.getRewrittenReboundReference(fieldMap);
+        return FieldLookupResult.builder(this)
+            .setReboundReference(rewrittenReboundReference)
+            .setReference(
+                rewrittenReboundReference.withHolder(
+                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
+                    dexItemFactory))
+            .build();
+      } else {
+        // TODO(b/168282032): We should always have the rebound reference, so this should become
+        //  unreachable.
+        DexField rewrittenReference = previous.getRewrittenReference(fieldMap);
+        return FieldLookupResult.builder(this).setReference(rewrittenReference).build();
       }
-      // 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 new GraphLensLookupResult(
-          newMethod,
-          mapInvocationType(newMethod, method, lookup.getType()),
-          internalDescribePrototypeChanges(lookup.getPrototypeChanges(), newMethod));
+    }
+
+    @Override
+    public MethodLookupResult internalDescribeLookupMethod(
+        MethodLookupResult previous, DexMethod context) {
+      if (previous.hasReboundReference()) {
+        // 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?
+        DexMethod rewrittenReboundReference = previous.getRewrittenReboundReference(methodMap);
+        return MethodLookupResult.builder(this)
+            .setReboundReference(rewrittenReboundReference)
+            .setReference(
+                rewrittenReboundReference.withHolder(
+                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
+                    dexItemFactory))
+            .setPrototypeChanges(
+                internalDescribePrototypeChanges(
+                    previous.getPrototypeChanges(), rewrittenReboundReference))
+            .setType(
+                mapInvocationType(
+                    rewrittenReboundReference, previous.getReference(), previous.getType()))
+            .build();
+      } else {
+        // TODO(b/168282032): We should always have the rebound reference, so this should become
+        //  unreachable.
+        DexMethod newMethod = methodMap.get(previous.getReference());
+        if (newMethod == null) {
+          return previous;
+        }
+        // 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)
+            .setReference(newMethod)
+            .setPrototypeChanges(
+                internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod))
+            .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
+            .build();
+      }
     }
 
     @Override
@@ -724,7 +1022,7 @@
         DexMethod method) {
       DexMethod previous = internalGetPreviousMethodSignature(method);
       RewrittenPrototypeDescription lookup =
-          previousLens.lookupPrototypeChangesForMethodDefinition(previous);
+          getPrevious().lookupPrototypeChangesForMethodDefinition(previous);
       return internalDescribePrototypeChanges(lookup, method);
     }
 
@@ -733,6 +1031,7 @@
       return prototypeChanges;
     }
 
+    @Override
     protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
       return originalMethodSignatures != null
           ? originalMethodSignatures.getOrDefault(method, method)
@@ -741,12 +1040,12 @@
 
     @Override
     public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
-      return previousLens.lookupGetFieldForMethod(field, context);
+      return getPrevious().lookupGetFieldForMethod(field, context);
     }
 
     @Override
     public DexMethod lookupPutFieldForMethod(DexField field, DexMethod context) {
-      return previousLens.lookupPutFieldForMethod(field, context);
+      return getPrevious().lookupPutFieldForMethod(field, context);
     }
 
     /**
@@ -788,19 +1087,13 @@
     }
 
     @Override
-    public DexField lookupField(DexField field) {
-      DexField previous = previousLens.lookupField(field);
-      return fieldMap.getOrDefault(previous, previous);
-    }
-
-    @Override
     public boolean isContextFreeForMethods() {
-      return previousLens.isContextFreeForMethods();
+      return getPrevious().isContextFreeForMethods();
     }
 
     @Override
     public boolean verifyIsContextFreeForMethod(DexMethod method) {
-      assert previousLens.verifyIsContextFreeForMethod(method);
+      assert getPrevious().verifyIsContextFreeForMethod(method);
       return true;
     }
 
@@ -821,7 +1114,7 @@
         builder.append(entry.getKey().toSourceString()).append(" -> ");
         builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
       }
-      builder.append(previousLens.toString());
+      builder.append(getPrevious().toString());
       return builder.toString();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
index 74f9880..cd3baa0 100644
--- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.function.Consumer;
 import org.objectweb.asm.ClassWriter;
 
 /** Representation of an entry in the Java InnerClasses attribute table. */
@@ -38,6 +39,15 @@
     this.innerName = innerName;
   }
 
+  public void forEachType(Consumer<DexType> consumer) {
+    if (inner != null) {
+      consumer.accept(inner);
+    }
+    if (outer != null) {
+      consumer.accept(outer);
+    }
+  }
+
   public boolean isNamed() {
     return innerName != null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 7936e33..2b38f4f 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -239,7 +239,8 @@
                       "Malformed inner-class attribute:",
                       "\touterTypeInternal: " + outerName,
                       "\tinnerTypeInternal: " + name,
-                      "\tinnerName: " + innerName)));
+                      "\tinnerName: " + innerName),
+                  origin));
         }
       }
       innerClasses.add(
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index b0e0312..263a21b 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -67,8 +67,10 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -210,7 +212,8 @@
 
   @Override
   public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
-    return asCfCode().buildIR(method, appView, origin);
+    return verifyFrames(asCfCode(), method.getDefinition(), appView, origin, true)
+        .buildIR(method, appView, origin);
   }
 
   @Override
@@ -222,7 +225,12 @@
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
-    return asCfCode()
+    return verifyFrames(
+            asCfCode(),
+            method.getDefinition(),
+            appView,
+            origin,
+            methodProcessor.shouldApplyCodeRewritings(method))
         .buildInliningIR(
             context,
             method,
@@ -233,6 +241,18 @@
             methodProcessor);
   }
 
+  private CfCode verifyFrames(
+      CfCode cfCode,
+      DexEncodedMethod method,
+      AppView<?> appView,
+      Origin origin,
+      boolean shouldApplyCodeRewritings) {
+    if (!cfCode.verifyFrames(method, appView, origin, shouldApplyCodeRewritings)) {
+      cfCode.instructions.removeIf(CfInstruction::isFrame);
+    }
+    return cfCode;
+  }
+
   @Override
   public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
     asCfCode().registerCodeReferences(method, registry);
@@ -387,7 +407,7 @@
         int frameType, int nLocals, Object[] localTypes, int nStack, Object[] stackTypes) {
       assert frameType == Opcodes.F_NEW;
       Int2ReferenceSortedMap<FrameType> parsedLocals = parseLocals(nLocals, localTypes);
-      List<FrameType> parsedStack = parseStack(nStack, stackTypes);
+      Deque<FrameType> parsedStack = parseStack(nStack, stackTypes);
       instructions.add(new CfFrame(parsedLocals, parsedStack));
     }
 
@@ -405,8 +425,8 @@
       return types;
     }
 
-    private List<FrameType> parseStack(int nStack, Object[] stackTypes) {
-      List<FrameType> dexStack = new ArrayList<>(nStack);
+    private Deque<FrameType> parseStack(int nStack, Object[] stackTypes) {
+      Deque<FrameType> dexStack = new ArrayDeque<>(nStack);
       for (int i = 0; i < nStack; i++) {
         dexStack.add(getFrameType(stackTypes[i]));
       }
@@ -415,7 +435,7 @@
 
     private FrameType getFrameType(Object localType) {
       if (localType instanceof Label) {
-        return FrameType.uninitializedNew(getLabel((Label) localType));
+        return FrameType.uninitializedNew(getLabel((Label) localType), null);
       } else if (localType == Opcodes.UNINITIALIZED_THIS) {
         return FrameType.uninitializedThis();
       } else if (localType == null || localType == Opcodes.TOP) {
@@ -989,12 +1009,11 @@
 
   private static DebugParsingOptions getParsingOptions(
       JarApplicationReader application, boolean reachabilitySensitive) {
+    // TODO(b/166841731): We should compute our own from the compressed format.
     int parsingOptions =
-        (application.options.enableCfByteCodePassThrough
-                || application.options.testing.readInputStackMaps)
+        application.options.testing.readInputStackMaps
             ? ClassReader.EXPAND_FRAMES
             : ClassReader.SKIP_FRAMES;
-
     ProguardConfiguration configuration = application.options.getProguardConfiguration();
     if (configuration == null) {
       return new DebugParsingOptions(true, true, parsingOptions);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
new file mode 100644
index 0000000..4b20148
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
@@ -0,0 +1,185 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Sets;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class MethodAccessInfoCollection {
+
+  private final Map<DexMethod, ProgramMethodSet> directInvokes;
+  private final Map<DexMethod, ProgramMethodSet> interfaceInvokes;
+  private final Map<DexMethod, ProgramMethodSet> staticInvokes;
+  private final Map<DexMethod, ProgramMethodSet> superInvokes;
+  private final Map<DexMethod, ProgramMethodSet> virtualInvokes;
+
+  private MethodAccessInfoCollection(
+      Map<DexMethod, ProgramMethodSet> directInvokes,
+      Map<DexMethod, ProgramMethodSet> interfaceInvokes,
+      Map<DexMethod, ProgramMethodSet> staticInvokes,
+      Map<DexMethod, ProgramMethodSet> superInvokes,
+      Map<DexMethod, ProgramMethodSet> virtualInvokes) {
+    this.directInvokes = directInvokes;
+    this.interfaceInvokes = interfaceInvokes;
+    this.staticInvokes = staticInvokes;
+    this.superInvokes = superInvokes;
+    this.virtualInvokes = virtualInvokes;
+  }
+
+  public static ConcurrentBuilder concurrentBuilder() {
+    return new ConcurrentBuilder();
+  }
+
+  // TODO(b/132593519): We should not need sorted maps with the new member rebinding analysis.
+  public static SortedBuilder sortedBuilder() {
+    return new SortedBuilder();
+  }
+
+  public void forEachMethodReference(Consumer<DexMethod> method) {
+    Set<DexMethod> seen = Sets.newIdentityHashSet();
+    directInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
+    interfaceInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
+    staticInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
+    superInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
+    virtualInvokes.keySet().forEach(ConsumerUtils.acceptIfNotSeen(method, seen));
+  }
+
+  public void forEachDirectInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
+    directInvokes.forEach(consumer);
+  }
+
+  public void forEachInterfaceInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
+    interfaceInvokes.forEach(consumer);
+  }
+
+  public void forEachStaticInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
+    staticInvokes.forEach(consumer);
+  }
+
+  public void forEachSuperInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
+    superInvokes.forEach(consumer);
+  }
+
+  public void forEachVirtualInvoke(BiConsumer<DexMethod, ProgramMethodSet> consumer) {
+    virtualInvokes.forEach(consumer);
+  }
+
+  public MethodAccessInfoCollection rewrittenWithLens(
+      DexDefinitionSupplier definitions, GraphLens lens) {
+    return new MethodAccessInfoCollection(
+        rewriteInvokesWithLens(directInvokes, definitions, lens),
+        rewriteInvokesWithLens(interfaceInvokes, definitions, lens),
+        rewriteInvokesWithLens(staticInvokes, definitions, lens),
+        rewriteInvokesWithLens(superInvokes, definitions, lens),
+        rewriteInvokesWithLens(virtualInvokes, definitions, lens));
+  }
+
+  private static Map<DexMethod, ProgramMethodSet> rewriteInvokesWithLens(
+      Map<DexMethod, ProgramMethodSet> invokes, DexDefinitionSupplier definitions, GraphLens lens) {
+    return MapUtils.map(
+        invokes,
+        capacity -> new TreeMap<>(DexMethod::slowCompareTo),
+        lens::getRenamedMethodSignature,
+        methods -> methods.rewrittenWithLens(definitions, lens),
+        (methods, other) -> {
+          methods.addAll(other);
+          return methods;
+        });
+  }
+
+  public abstract static class Builder<T extends Map<DexMethod, ProgramMethodSet>> {
+
+    private final T directInvokes;
+    private final T interfaceInvokes;
+    private final T staticInvokes;
+    private final T superInvokes;
+    private final T virtualInvokes;
+
+    private Builder(Supplier<T> factory) {
+      directInvokes = factory.get();
+      interfaceInvokes = factory.get();
+      staticInvokes = factory.get();
+      superInvokes = factory.get();
+      virtualInvokes = factory.get();
+    }
+
+    public T getDirectInvokes() {
+      return directInvokes;
+    }
+
+    public T getInterfaceInvokes() {
+      return interfaceInvokes;
+    }
+
+    public T getStaticInvokes() {
+      return staticInvokes;
+    }
+
+    public T getSuperInvokes() {
+      return superInvokes;
+    }
+
+    public T getVirtualInvokes() {
+      return virtualInvokes;
+    }
+
+    public boolean registerInvokeDirectInContext(DexMethod invokedMethod, ProgramMethod context) {
+      return registerInvokeMethodInContext(invokedMethod, context, directInvokes);
+    }
+
+    public boolean registerInvokeInterfaceInContext(
+        DexMethod invokedMethod, ProgramMethod context) {
+      return registerInvokeMethodInContext(invokedMethod, context, interfaceInvokes);
+    }
+
+    public boolean registerInvokeStaticInContext(DexMethod invokedMethod, ProgramMethod context) {
+      return registerInvokeMethodInContext(invokedMethod, context, staticInvokes);
+    }
+
+    public boolean registerInvokeSuperInContext(DexMethod invokedMethod, ProgramMethod context) {
+      return registerInvokeMethodInContext(invokedMethod, context, superInvokes);
+    }
+
+    public boolean registerInvokeVirtualInContext(DexMethod invokedMethod, ProgramMethod context) {
+      return registerInvokeMethodInContext(invokedMethod, context, virtualInvokes);
+    }
+
+    private static boolean registerInvokeMethodInContext(
+        DexMethod invokedMethod, ProgramMethod context, Map<DexMethod, ProgramMethodSet> invokes) {
+      return invokes
+          .computeIfAbsent(invokedMethod, ignore -> ProgramMethodSet.create())
+          .add(context);
+    }
+
+    public MethodAccessInfoCollection build() {
+      return new MethodAccessInfoCollection(
+          directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes);
+    }
+  }
+
+  public static class ConcurrentBuilder
+      extends Builder<ConcurrentHashMap<DexMethod, ProgramMethodSet>> {
+
+    private ConcurrentBuilder() {
+      super(ConcurrentHashMap::new);
+    }
+  }
+
+  public static class SortedBuilder extends Builder<TreeMap<DexMethod, ProgramMethodSet>> {
+
+    private SortedBuilder() {
+      super(() -> new TreeMap<>(DexMethod::slowCompareTo));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index 9839a80..b4797a9 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -252,11 +252,11 @@
   }
 
   public int getOffsetFor(DexField field) {
-    return getOffsetFor(graphLens.lookupField(field), fields);
+    return getOffsetFor(field, fields);
   }
 
   public int getOffsetFor(DexMethod method) {
-    return getOffsetFor(graphLens.lookupMethod(method), methods);
+    return getOffsetFor(method, methods);
   }
 
   public int getOffsetFor(DexString string) {
@@ -264,7 +264,7 @@
   }
 
   public int getOffsetFor(DexType type) {
-    return getOffsetFor(graphLens.lookupType(type), types);
+    return getOffsetFor(type, types);
   }
 
   public int getOffsetFor(DexCallSite callSite) {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
new file mode 100644
index 0000000..91edb06
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+public interface ProgramDefinition {
+
+  DexType getContextType();
+
+  DexDefinition getDefinition();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMember.java b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
index 7dc209c..328c71f 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMember.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.graph;
 
-public interface ProgramMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
+public interface ProgramMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+    extends ProgramDefinition {
 
+  @Override
   D getDefinition();
 
   DexType getHolderType();
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
index c92c6d4..81627e9 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
@@ -9,14 +9,21 @@
 import java.util.Iterator;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 public class ProgramPackage implements Iterable<DexProgramClass> {
 
   private final String packageDescriptor;
-  private final Set<DexProgramClass> classes = Sets.newIdentityHashSet();
+  private final Set<DexProgramClass> classes;
 
   public ProgramPackage(String packageDescriptor) {
+    this(packageDescriptor, Sets::newIdentityHashSet);
+  }
+
+  protected ProgramPackage(
+      String packageDescriptor, Supplier<Set<DexProgramClass>> backingFactory) {
     this.packageDescriptor = packageDescriptor;
+    this.classes = backingFactory.get();
   }
 
   public boolean add(DexProgramClass clazz) {
@@ -24,6 +31,10 @@
     return classes.add(clazz);
   }
 
+  public boolean contains(DexProgramClass clazz) {
+    return classes.contains(clazz);
+  }
+
   public String getLastPackageName() {
     int index = packageDescriptor.lastIndexOf('/');
     if (index >= 0) {
@@ -32,6 +43,10 @@
     return packageDescriptor;
   }
 
+  public String getPackageDescriptor() {
+    return packageDescriptor;
+  }
+
   public void forEachClass(Consumer<DexProgramClass> consumer) {
     forEach(consumer);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
index fe9a64c..5d53ac0 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
@@ -10,9 +10,9 @@
 
 public class ProgramPackageCollection implements Iterable<ProgramPackage> {
 
-  private final Map<String, ProgramPackage> packages;
+  protected final Map<String, ProgramPackage> packages;
 
-  private ProgramPackageCollection(Map<String, ProgramPackage> packages) {
+  protected ProgramPackageCollection(Map<String, ProgramPackage> packages) {
     this.packages = packages;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/SortedProgramPackage.java b/src/main/java/com/android/tools/r8/graph/SortedProgramPackage.java
new file mode 100644
index 0000000..97afd09
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/SortedProgramPackage.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import java.util.TreeSet;
+
+public class SortedProgramPackage extends ProgramPackage {
+
+  public SortedProgramPackage(String packageDescriptor) {
+    super(packageDescriptor, () -> new TreeSet<>((a, b) -> a.getType().slowCompareTo(b.getType())));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/SortedProgramPackageCollection.java b/src/main/java/com/android/tools/r8/graph/SortedProgramPackageCollection.java
new file mode 100644
index 0000000..fdaff05
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/SortedProgramPackageCollection.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import java.util.TreeMap;
+
+public class SortedProgramPackageCollection extends ProgramPackageCollection {
+
+  private SortedProgramPackageCollection() {
+    super(new TreeMap<>());
+  }
+
+  public static SortedProgramPackageCollection createWithAllProgramClasses(AppView<?> appView) {
+    assert !appView.appInfo().getSyntheticItems().hasPendingSyntheticClasses();
+    SortedProgramPackageCollection programPackages = new SortedProgramPackageCollection();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      programPackages.addProgramClass(clazz);
+    }
+    return programPackages;
+  }
+
+  @Override
+  public boolean addProgramClass(DexProgramClass clazz) {
+    return packages
+        .computeIfAbsent(clazz.getType().getPackageDescriptor(), SortedProgramPackage::new)
+        .add(clazz);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 75e6565..5830357 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -17,6 +17,10 @@
     this.factory = factory;
   }
 
+  public final void accept(ProgramMethod method) {
+    method.registerCodeReferences(this);
+  }
+
   public abstract void registerInitClass(DexType type);
 
   public abstract void registerInvokeVirtual(DexMethod method);
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 1f751ad..c80e363 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -21,10 +21,12 @@
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -72,6 +74,19 @@
     buildClassIdentifierMap();
   }
 
+  /** Returns an iterable over all classes that should be merged into the target class. */
+  public Iterable<DexProgramClass> getToMergeClasses() {
+    return toMergeGroup;
+  }
+
+  /**
+   * Returns an iterable over both the target class as well as all classes that should be merged
+   * into the target class.
+   */
+  public Iterable<DexProgramClass> getClasses() {
+    return Iterables.concat(Collections.singleton(target), getToMergeClasses());
+  }
+
   void buildClassIdentifierMap() {
     classIdentifiers.put(target.type, 0);
     for (DexProgramClass toMerge : toMergeGroup) {
@@ -114,9 +129,10 @@
         tryMethod -> target.lookupMethod(tryMethod) == null);
   }
 
-  void mergeConstructors() {
+  void mergeConstructors(SyntheticArgumentClass syntheticArgumentClass) {
     for (ConstructorMerger merger : constructorMergers) {
-      merger.merge(lensBuilder, fieldAccessChangesBuilder, classIdentifiers);
+      merger.merge(
+          lensBuilder, fieldAccessChangesBuilder, classIdentifiers, syntheticArgumentClass);
     }
   }
 
@@ -137,7 +153,7 @@
     target.appendInstanceField(encodedField);
   }
 
-  public void mergeGroup() {
+  public void mergeGroup(SyntheticArgumentClass syntheticArgumentClass) {
     appendClassIdField();
 
     for (DexProgramClass clazz : toMergeGroup) {
@@ -145,7 +161,7 @@
       lensBuilder.mapType(clazz.type, target.type);
     }
 
-    mergeConstructors();
+    mergeConstructors(syntheticArgumentClass);
     mergeVirtualMethods();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
index ff2a898..732a5d2 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
@@ -78,7 +78,9 @@
 
   protected void prepareMultiConstructorInstructions() {
     int typeConstructorCount = typeConstructors.size();
-    int idRegister = getParamRegister(method.getArity() - 1);
+    DexMethod exampleTargetConstructor = typeConstructors.values().iterator().next();
+    // The class id register is always the first synthetic argument.
+    int idRegister = getParamRegister(exampleTargetConstructor.getArity());
 
     addRegisterClassIdAssignment(idRegister);
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index cd2d31c..2f3c068 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -16,6 +16,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
+import com.android.tools.r8.ir.conversion.ExtraParameter;
+import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
@@ -23,6 +26,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 
 public class ConstructorMerger {
@@ -90,8 +94,8 @@
     return method;
   }
 
-  private DexProto getNewConstructorProto() {
-    DexEncodedMethod firstConstructor = constructors.stream().findFirst().get();
+  private DexProto getNewConstructorProto(SyntheticArgumentClass syntheticArgumentClass) {
+    DexEncodedMethod firstConstructor = constructors.iterator().next();
     DexProto oldProto = firstConstructor.getProto();
 
     if (isTrivialMerge()) {
@@ -101,10 +105,18 @@
     List<DexType> parameters = new ArrayList<>();
     Collections.addAll(parameters, oldProto.parameters.values);
     parameters.add(dexItemFactory.intType);
+    if (syntheticArgumentClass != null) {
+      parameters.add(syntheticArgumentClass.getArgumentClass());
+    }
     // TODO(b/165783587): add synthesised class to prevent constructor merge conflict
     return dexItemFactory.createProto(oldProto.returnType, parameters);
   }
 
+  private DexMethod getNewConstructorReference(SyntheticArgumentClass syntheticArgumentClass) {
+    DexProto proto = getNewConstructorProto(syntheticArgumentClass);
+    return appView.dexItemFactory().createMethod(target.type, proto, dexItemFactory.initMethodName);
+  }
+
   private MethodAccessFlags getAccessFlags() {
     // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
     return MethodAccessFlags.fromSharedAccessFlags(
@@ -115,7 +127,8 @@
   void merge(
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
-      Reference2IntMap<DexType> classIdentifiers) {
+      Reference2IntMap<DexType> classIdentifiers,
+      SyntheticArgumentClass syntheticArgumentClass) {
     // Tree map as must be sorted.
     Int2ReferenceSortedMap<DexMethod> typeConstructorClassMap = new Int2ReferenceAVLTreeMap<>();
 
@@ -130,11 +143,15 @@
           classIdentifiers.getInt(constructor.getHolderType()), movedConstructor);
     }
 
-    DexProto newProto = getNewConstructorProto();
+    DexMethod newConstructorReference = getNewConstructorReference(null);
+    boolean addExtraNull = target.lookupMethod(newConstructorReference) != null;
+    if (addExtraNull) {
+      newConstructorReference = getNewConstructorReference(syntheticArgumentClass);
+      assert target.lookupMethod(newConstructorReference) == null;
+    }
 
-    DexMethod originalConstructorReference = constructors.iterator().next().method;
-    DexMethod newConstructorReference =
-        appView.dexItemFactory().createMethod(target.type, newProto, dexItemFactory.initMethodName);
+    DexMethod originalConstructorReference =
+        appView.graphLens().getOriginalMethodSignature(constructors.iterator().next().method);
     ConstructorEntryPointSynthesizedCode synthesizedCode =
         new ConstructorEntryPointSynthesizedCode(
             typeConstructorClassMap,
@@ -158,10 +175,16 @@
     } else {
       // Map each old constructor to the newly synthesized constructor in the graph lens.
       for (DexEncodedMethod oldConstructor : constructors) {
+        int classIdentifier = classIdentifiers.getInt(oldConstructor.getHolderType());
+
+        List<ExtraParameter> extraParameters = new LinkedList<>();
+        extraParameters.add(new ExtraConstantIntParameter(classIdentifier));
+        if (addExtraNull) {
+          extraParameters.add(new ExtraUnusedNullParameter());
+        }
+
         lensBuilder.mapMergedConstructor(
-            oldConstructor.method,
-            newConstructorReference,
-            classIdentifiers.getInt(oldConstructor.getHolderType()));
+            oldConstructor.method, newConstructorReference, extraParameters);
       }
     }
     // Map the first constructor to the newly synthesized constructor.
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 8b17c78..c993870 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,8 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
 import com.android.tools.r8.horizontalclassmerging.policies.NoFields;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
@@ -15,6 +17,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoStaticClassInitializer;
 import com.android.tools.r8.horizontalclassmerging.policies.NotEntryPoint;
 import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
+import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDex;
 import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -22,6 +25,7 @@
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -34,7 +38,7 @@
 
   public HorizontalClassMerger(
       AppView<AppInfoWithLiveness> appView,
-      MainDexTracingResult mainDexClasses,
+      MainDexTracingResult mainDexTracingResult,
       ClassMergingEnqueuerExtension classMergingEnqueuerExtension) {
     this.appView = appView;
 
@@ -50,8 +54,10 @@
             new NoKeepRules(appView),
             new NoRuntimeTypeChecks(classMergingEnqueuerExtension),
             new NotEntryPoint(appView.dexItemFactory()),
+            new PreventMergeIntoMainDex(appView, mainDexTracingResult),
             new SameParentClass(),
-            new RespectPackageBoundaries(appView)
+            new RespectPackageBoundaries(appView),
+            new DontMergeSynchronizedClasses(appView)
             // TODO: add policies
             );
 
@@ -59,7 +65,7 @@
   }
 
   // TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup
-  public HorizontalClassMergerGraphLens run() {
+  public HorizontalClassMergerGraphLens run(DirectMappedDexApplication.Builder appBuilder) {
     Map<FieldMultiset, Collection<DexProgramClass>> classes = new HashMap<>();
 
     // Group classes by same field signature using the hash map.
@@ -69,6 +75,10 @@
 
     // Run the policies on all collected classes to produce a final grouping.
     Collection<Collection<DexProgramClass>> groups = policyExecutor.run(classes.values());
+    // If there are no groups, then end horizontal class merging.
+    if (groups.isEmpty()) {
+      return null;
+    }
 
     HorizontalClassMergerGraphLens.Builder lensBuilder =
         new HorizontalClassMergerGraphLens.Builder();
@@ -78,9 +88,13 @@
     // Set up a class merger for each group.
     Collection<ClassMerger> classMergers =
         initializeClassMergers(lensBuilder, fieldAccessChangesBuilder, groups);
+    Iterable<DexProgramClass> allMergeClasses =
+        Iterables.concat(Iterables.transform(classMergers, ClassMerger::getClasses));
 
     // Merge the classes.
-    applyClassMergers(classMergers);
+    SyntheticArgumentClass syntheticArgumentClass =
+        new SyntheticArgumentClass.Builder().build(appView, appBuilder, allMergeClasses);
+    applyClassMergers(classMergers, syntheticArgumentClass);
 
     // Generate the class lens.
     return createLens(lensBuilder, fieldAccessChangesBuilder);
@@ -114,9 +128,10 @@
   }
 
   /** Merges all class groups using {@link ClassMerger}. */
-  private void applyClassMergers(Collection<ClassMerger> classMergers) {
+  private void applyClassMergers(
+      Collection<ClassMerger> classMergers, SyntheticArgumentClass syntheticArgumentClass) {
     for (ClassMerger merger : classMergers) {
-      merger.mergeGroup();
+      merger.mergeGroup(syntheticArgumentClass);
     }
   }
 
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 b0e8d13..395409f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -10,24 +10,24 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
+import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 public class HorizontalClassMergerGraphLens extends NestedGraphLens {
   private final AppView<?> appView;
-  private final Map<DexMethod, Integer> constructorIds;
+  private final Map<DexMethod, List<ExtraParameter>> methodExtraParameters;
   private final Map<DexMethod, DexMethod> originalConstructorSignatures;
 
   private HorizontalClassMergerGraphLens(
       AppView<?> appView,
-      Map<DexMethod, Integer> constructorIds,
+      Map<DexMethod, List<ExtraParameter>> methodExtraParameters,
       Map<DexType, DexType> typeMap,
       Map<DexField, DexField> fieldMap,
       Map<DexMethod, DexMethod> methodMap,
@@ -44,7 +44,7 @@
         previousLens,
         appView.dexItemFactory());
     this.appView = appView;
-    this.constructorIds = constructorIds;
+    this.methodExtraParameters = methodExtraParameters;
     this.originalConstructorSignatures = originalConstructorSignatures;
   }
 
@@ -54,7 +54,7 @@
     if (originalConstructor == null) {
       return super.getOriginalMethodSignature(method);
     }
-    return previousLens.getOriginalMethodSignature(originalConstructor);
+    return getPrevious().getOriginalMethodSignature(originalConstructor);
   }
 
   public HorizontallyMergedClasses getHorizontallyMergedClasses() {
@@ -66,20 +66,18 @@
    * constructor. Otherwise return the lookup on the underlying graph lens.
    */
   @Override
-  public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
-    Integer constructorId = constructorIds.get(method);
-    GraphLensLookupResult lookup = super.lookupMethod(method, context, type);
-    if (constructorId != null) {
-      DexMethod newMethod = lookup.getMethod();
-      return new GraphLensLookupResult(
-          newMethod,
-          mapInvocationType(newMethod, method, lookup.getType()),
-          lookup
-              .getPrototypeChanges()
-              .withExtraParameter(new ExtraConstantIntParameter(constructorId)));
-    } else {
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    List<ExtraParameter> extraParameters = methodExtraParameters.get(previous.getReference());
+    MethodLookupResult lookup = super.internalDescribeLookupMethod(previous, context);
+    if (extraParameters == null) {
       return lookup;
     }
+    return MethodLookupResult.builder(this)
+        .setReference(lookup.getReference())
+        .setPrototypeChanges(lookup.getPrototypeChanges().withExtraParameters(extraParameters))
+        .setType(lookup.getType())
+        .build();
   }
 
   public static class Builder {
@@ -91,26 +89,25 @@
     private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
     private final Map<DexMethod, DexMethod> extraOriginalMethodSignatures = new IdentityHashMap<>();
 
-    private final Map<DexMethod, Integer> constructorIds = new IdentityHashMap<>();
+    private final Map<DexMethod, List<ExtraParameter>> methodExtraParameters =
+        new IdentityHashMap<>();
 
     Builder() {}
 
     public HorizontalClassMergerGraphLens build(AppView<?> appView) {
-      if (typeMap.isEmpty()) {
-        return null;
-      } else {
-        BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
-        return new HorizontalClassMergerGraphLens(
-            appView,
-            constructorIds,
-            typeMap,
-            fieldMap,
-            methodMap,
-            originalFieldSignatures,
-            originalMethodSignatures,
-            extraOriginalMethodSignatures,
-            appView.graphLens());
-      }
+      assert !typeMap.isEmpty();
+
+      BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
+      return new HorizontalClassMergerGraphLens(
+          appView,
+          methodExtraParameters,
+          typeMap,
+          fieldMap,
+          methodMap,
+          originalFieldSignatures,
+          originalMethodSignatures,
+          extraOriginalMethodSignatures,
+          appView.graphLens());
     }
 
     public DexType lookupType(DexType type) {
@@ -186,13 +183,11 @@
      * One way mapping from one constructor to another. This is used for synthesized constructors,
      * where many constructors are merged into a single constructor. The synthesized constructor
      * therefore does not have a unique reverse constructor.
-     *
-     * @param constructorId The id that must be appended to the constructor call to ensure the
-     *     correct constructor is called.
      */
-    public Builder mapMergedConstructor(DexMethod from, DexMethod to, int constructorId) {
+    public Builder mapMergedConstructor(
+        DexMethod from, DexMethod to, List<ExtraParameter> extraParameters) {
       mapMethod(from, to);
-      constructorIds.put(from, constructorId);
+      methodExtraParameters.put(from, extraParameters);
       return this;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
index 5c67423..829c1f3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
@@ -10,12 +10,17 @@
 
 public abstract class MultiClassPolicy extends Policy {
 
+  // TODO(b/165577835): Move to a virtual method on MergeGroup.
+  protected boolean isTrivial(Collection<DexProgramClass> group) {
+    return group.size() < 2;
+  }
+
   /**
    * Remove all groups containing no or only a single class, as there is no point in merging these.
    */
   protected void removeTrivialGroups(Collection<Collection<DexProgramClass>> groups) {
     assert !(groups instanceof ArrayList);
-    groups.removeIf(group -> group.size() < 2);
+    groups.removeIf(this::isTrivial);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
new file mode 100644
index 0000000..db92416
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collections;
+
+/**
+ * Lets assume we are merging a class A that looks like:
+ *
+ * <pre>
+ *   class A {
+ *     A() { ... }
+ *     A(int) { ... }
+ *   }
+ * </pre>
+ *
+ * If {@code A::<init>()} is merged with other constructors, then we need to prevent a conflict with
+ * {@code A::<init>(int)}. To do this we introduce a synthetic class so that the new signature for
+ * the merged constructor is {@code A::<init>(SyntheticClass, int)}, as this is guaranteed to be
+ * unique.
+ *
+ * <p>This class generates a synthetic class in the package of the first class to be merged.
+ */
+public class SyntheticArgumentClass {
+  public static final String SYNTHETIC_CLASS_SUFFIX = "$r8$HorizontalClassMergingArgument";
+
+  private final DexType syntheticClassType;
+
+  private SyntheticArgumentClass(DexType syntheticClassType) {
+    this.syntheticClassType = syntheticClassType;
+  }
+
+  public DexType getArgumentClass() {
+    return syntheticClassType;
+  }
+
+  public static class Builder {
+
+    public SyntheticArgumentClass build(
+        AppView<AppInfoWithLiveness> appView,
+        DirectMappedDexApplication.Builder appBuilder,
+        Iterable<DexProgramClass> mergeClasses) {
+
+      // Find a fresh name in an existing package.
+      DexProgramClass context = mergeClasses.iterator().next();
+      DexType syntheticClassType =
+          context.type.addSuffix(SYNTHETIC_CLASS_SUFFIX, appView.dexItemFactory());
+
+      boolean requiresMainDex = appView.appInfo().getMainDexClasses().containsAnyOf(mergeClasses);
+
+      DexProgramClass clazz =
+          new DexProgramClass(
+              syntheticClassType,
+              null,
+              new SynthesizedOrigin("horizontal class merging", HorizontalClassMerger.class),
+              ClassAccessFlags.fromCfAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
+              appView.dexItemFactory().objectType,
+              DexTypeList.empty(),
+              null,
+              null,
+              Collections.emptyList(),
+              null,
+              Collections.emptyList(),
+              DexAnnotationSet.empty(),
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              appView.dexItemFactory().getSkipNameValidationForTesting(),
+              DexProgramClass::checksumFromType);
+
+      appBuilder.addSynthesizedClass(clazz);
+      appView.appInfo().addSynthesizedClass(clazz, requiresMainDex);
+
+      return new SyntheticArgumentClass(clazz.type);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 087c6ef..a35db17 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -50,12 +50,8 @@
       fixupFields(clazz.instanceFields(), clazz::setInstanceField);
     }
     HorizontalClassMergerGraphLens lens = lensBuilder.build(appView);
-
     fieldAccessChangesBuilder.build(this::fixupMethod).modify(appView);
-
-    if (lens != null) {
-      new AnnotationFixer(lens).run(appView.appInfo().classes());
-    }
+    new AnnotationFixer(lens).run(appView.appInfo().classes());
     return lens;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 5e20c04f..fd998a5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -150,7 +150,8 @@
     }
 
     // Use the first of the original methods as the original method for the merged constructor.
-    DexMethod originalMethodReference = methods.iterator().next().getReference();
+    DexMethod originalMethodReference =
+        appView.graphLens().getOriginalMethodSignature(methods.iterator().next().getReference());
 
     DexMethod newMethodReference =
         dexItemFactory.createMethod(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java
new file mode 100644
index 0000000..958e660
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+public class DontMergeSynchronizedClasses extends MultiClassPolicy {
+  private final AppView<AppInfoWithLiveness> appView;
+
+  public DontMergeSynchronizedClasses(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  private boolean isSynchronizationClass(DexProgramClass clazz) {
+    return appView.appInfo().isLockCandidate(clazz.type) || clazz.hasStaticSynchronizedMethods();
+  }
+
+  @Override
+  public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) {
+    // Gather all synchronized classes.
+    Collection<Collection<DexProgramClass>> synchronizedGroups = new LinkedList<>();
+    group.removeIf(
+        clazz -> {
+          boolean synchronizationClass = isSynchronizationClass(clazz);
+          if (synchronizationClass) {
+            Collection<DexProgramClass> synchronizedGroup = new LinkedList<>();
+            synchronizedGroup.add(clazz);
+            synchronizedGroups.add(synchronizedGroup);
+          }
+          return synchronizationClass;
+        });
+
+    if (synchronizedGroups.isEmpty()) {
+      return Collections.singletonList(group);
+    }
+
+    Iterator<Collection<DexProgramClass>> synchronizedGroupIterator = synchronizedGroups.iterator();
+    for (DexProgramClass clazz : group) {
+      if (!synchronizedGroupIterator.hasNext()) {
+        synchronizedGroupIterator = synchronizedGroups.iterator();
+      }
+      synchronizedGroupIterator.next().add(clazz);
+    }
+
+    removeTrivialGroups(synchronizedGroups);
+
+    return synchronizedGroups;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDex.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDex.java
new file mode 100644
index 0000000..f17e923
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoMainDex.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MainDexTracingResult;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class PreventMergeIntoMainDex extends MultiClassPolicy {
+  private final MainDexClasses mainDexClasses;
+  private final MainDexTracingResult mainDexTracingResult;
+
+  public PreventMergeIntoMainDex(
+      AppView<AppInfoWithLiveness> appView, MainDexTracingResult mainDexTracingResult) {
+    this.mainDexClasses = appView.appInfo().getMainDexClasses();
+    this.mainDexTracingResult = mainDexTracingResult;
+  }
+
+  public boolean isMainDexClass(DexProgramClass clazz) {
+    return mainDexClasses.contains(clazz) || mainDexTracingResult.contains(clazz);
+  }
+
+  @Override
+  public Collection<Collection<DexProgramClass>> apply(Collection<DexProgramClass> group) {
+    List<DexProgramClass> mainDexMembers = new LinkedList<>();
+    Iterator<DexProgramClass> iterator = group.iterator();
+    while (iterator.hasNext()) {
+      DexProgramClass clazz = iterator.next();
+      if (isMainDexClass(clazz)) {
+        iterator.remove();
+        mainDexMembers.add(clazz);
+      }
+    }
+
+    Collection<Collection<DexProgramClass>> newGroups = new LinkedList<>();
+    if (!isTrivial(mainDexMembers)) {
+      newGroups.add(mainDexMembers);
+    }
+    if (!isTrivial(group)) {
+      newGroups.add(group);
+    }
+
+    return newGroups;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index e71ef7d..edd1b91 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -16,6 +16,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Queue;
@@ -132,7 +133,10 @@
     builder.append(" {");
     Set<DexType> interfaces = getInterfaces();
     if (interfaces != null) {
-      builder.append(interfaces.stream().map(DexType::toString).collect(Collectors.joining(", ")));
+      List<DexType> sortedInterfaces = new ArrayList<>(interfaces);
+      sortedInterfaces.sort(DexType::slowCompareTo);
+      builder.append(
+          sortedInterfaces.stream().map(DexType::toString).collect(Collectors.joining(", ")));
     }
     builder.append("}");
     return builder.toString();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 020254f..6d452e1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -166,7 +166,7 @@
     if (instructionMayHaveSideEffects(appView, context)) {
       return DeadInstructionResult.notDead();
     }
-    if (!getInvokedMethod().isInstanceInitializer(appView)) {
+    if (!getInvokedMethod().isInstanceInitializer(appView.dexItemFactory())) {
       return DeadInstructionResult.deadIfOutValueIsDead();
     }
     // Super-constructor calls cannot be removed.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 183d352..6cc4f93 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -8,9 +8,10 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.google.common.annotations.VisibleForTesting;
+import java.util.Comparator;
 import java.util.Objects;
 
-public class Position {
+public class Position implements Comparable<Position> {
 
   // A no-position marker. Not having a position means the position is implicitly defined by the
   // context, e.g., the marker does not materialize anything concrete.
@@ -114,18 +115,7 @@
 
   @Override
   public boolean equals(Object other) {
-    if (this == other) {
-      return true;
-    }
-    if (other instanceof Position) {
-      Position o = (Position) other;
-      return line == o.line
-          && file == o.file
-          && method == o.method
-          && synthetic == o.synthetic
-          && Objects.equals(callerPosition, o.callerPosition);
-    }
-    return false;
+    return other instanceof Position && compareTo((Position) other) == 0;
   }
 
   @Override
@@ -138,6 +128,19 @@
     return result;
   }
 
+  @Override
+  public int compareTo(Position o) {
+    if (this == o) {
+      return 0;
+    }
+    return Comparator.comparingInt((Position p) -> p.line)
+        .thenComparing(p -> p.file, Comparator.nullsFirst(DexString::slowCompareTo))
+        .thenComparing(p -> p.synthetic)
+        .thenComparing(p -> p.method, DexMethod::slowCompareTo)
+        .thenComparing(p -> p.callerPosition, Comparator.nullsFirst(Position::compareTo))
+        .compare(this, o);
+  }
+
   private String toString(boolean forceMethod) {
     if (isNone()) {
       return "--";
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index f5a51c8..6756693 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
-import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -166,9 +166,9 @@
 
     private void processInvoke(Invoke.Type originalType, DexMethod originalMethod) {
       ProgramMethod context = currentMethod.getProgramMethod();
-      GraphLensLookupResult result =
+      MethodLookupResult result =
           appView.graphLens().lookupMethod(originalMethod, context.getReference(), originalType);
-      DexMethod method = result.getMethod();
+      DexMethod method = result.getReference();
       Invoke.Type type = result.getType();
       if (type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL) {
         // For virtual and interface calls add all potential targets that could be called.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 9055230..47db073 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -59,7 +59,6 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -165,6 +164,7 @@
     DexBuilder.removeRedundantDebugPositions(code);
     CfCode code = buildCfCode();
     assert verifyInvokeInterface(code, appView);
+    assert code.verifyFrames(method, appView, this.code.origin, false);
     return code;
   }
 
@@ -530,13 +530,14 @@
 
   private void addFrame(BasicBlock block) {
     List<TypeInfo> stack = registerAllocator.getTypesAtBlockEntry(block).stack;
-    List<FrameType> stackTypes;
+    Deque<FrameType> stackTypes;
     if (block.entry().isMoveException()) {
       assert stack.isEmpty();
       StackValue exception = (StackValue) block.entry().outValue();
-      stackTypes = Collections.singletonList(getFrameType(block, exception.getTypeInfo()));
+      stackTypes = new ArrayDeque<>();
+      stackTypes.add(getFrameType(block, exception.getTypeInfo()));
     } else {
-      stackTypes = new ArrayList<>(stack.size());
+      stackTypes = new ArrayDeque<>(stack.size());
       for (TypeInfo typeInfo : stack) {
         stackTypes.add(getFrameType(block, typeInfo));
       }
@@ -577,8 +578,11 @@
     FrameType res;
     Instruction definition;
     if (typeInfo instanceof NewInstanceInfo) {
-      definition = ((NewInstanceInfo) typeInfo).newInstance;
-      res = FrameType.uninitializedNew(newInstanceLabels.get(definition));
+      NewInstanceInfo newInstanceInfo = (NewInstanceInfo) typeInfo;
+      definition = newInstanceInfo.newInstance;
+      res =
+          FrameType.uninitializedNew(
+              newInstanceLabels.get(definition), newInstanceInfo.getDexType());
     } else if (typeInfo instanceof ThisInstanceInfo) {
       definition = ((ThisInstanceInfo) typeInfo).thisArgument;
       res = FrameType.uninitializedThis();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index d4c8040..f7e8a90 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -292,7 +292,11 @@
   // This is the only instruction that differ in throwing between DEX and CF. If we find more
   // consider rewriting CfInstruction.canThrow to take in options.
   private boolean canThrowHelper(CfInstruction instruction) {
-    if (internalOutputMode.isGeneratingClassFiles()
+    return canThrowHelper(instruction, internalOutputMode.isGeneratingClassFiles());
+  }
+
+  public static boolean canThrowHelper(CfInstruction instruction, boolean isGeneratingClassFiles) {
+    if (isGeneratingClassFiles
         && (instruction.isConstString() || instruction.isDexItemBasedConstString())) {
       return false;
     }
@@ -605,8 +609,9 @@
     for (Int2ReferenceMap.Entry<FrameType> entry : frameLocals.int2ReferenceEntrySet()) {
       locals[entry.getIntKey()] = convertUninitialized(entry.getValue());
     }
-    for (int i = 0; i < stack.length; i++) {
-      stack[i] = convertUninitialized(frame.getStack().get(i));
+    int index = 0;
+    for (FrameType frameType : frame.getStack()) {
+      stack[index++] = convertUninitialized(frameType);
     }
     state.setStateFromFrame(
         locals, stack, getCanonicalDebugPositionAtOffset(currentInstructionIndex));
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 605f698..32da612 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -592,6 +592,9 @@
   public DexApplication optimize(
       AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
       throws ExecutionException {
+    // Lambda rewriting happens in the enqueuer.
+    assert lambdaRewriter == null;
+
     DexApplication application = appView.appInfo().app();
 
     computeReachabilitySensitivity(application);
@@ -702,9 +705,6 @@
     Builder<?> builder = appView.appInfo().app().builder();
     builder.setHighestSortingString(highestSortingString);
 
-    printPhase("Lambda class synthesis");
-    synthesizeLambdaClasses(builder, executorService);
-
     printPhase("Interface method desugaring");
     desugarInterfaceMethods(builder, IncludeAllResources, executorService);
     feedback.updateVisibleOptimizationInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 576a376..8b827d3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -42,7 +42,7 @@
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
@@ -214,9 +214,9 @@
               if (invoke.isInvokeDirect()) {
                 checkInvokeDirect(method.getReference(), invoke.asInvokeDirect());
               }
-              GraphLensLookupResult lensLookup =
+              MethodLookupResult lensLookup =
                   graphLens.lookupMethod(invokedMethod, method.getReference(), invoke.getType());
-              DexMethod actualTarget = lensLookup.getMethod();
+              DexMethod actualTarget = lensLookup.getReference();
               Invoke.Type actualInvokeType = lensLookup.getType();
               if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
                 RewrittenPrototypeDescription prototypeChanges = lensLookup.getPrototypeChanges();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
index 27b3064..4b44ada 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import java.util.ArrayList;
@@ -73,9 +73,9 @@
     if (methodHandle.isMethodHandle()) {
       DexMethod invokedMethod = methodHandle.asMethod();
       MethodHandleType oldType = methodHandle.type;
-      GraphLensLookupResult lensLookup =
+      MethodLookupResult lensLookup =
           graphLens.lookupMethod(invokedMethod, context.getReference(), oldType.toInvokeType());
-      DexMethod rewrittenTarget = lensLookup.getMethod();
+      DexMethod rewrittenTarget = lensLookup.getReference();
       DexMethod actualTarget;
       MethodHandleType newType;
       if (use == ARGUMENT_TO_LAMBDA_METAFACTORY) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
index d687d19..7586210 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -19,6 +19,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 public class DesugaredLibraryConfigurationParser {
 
@@ -77,6 +78,12 @@
   }
 
   public DesugaredLibraryConfiguration parse(StringResource stringResource) {
+    return parse(stringResource, builder -> {});
+  }
+
+  public DesugaredLibraryConfiguration parse(
+      StringResource stringResource,
+      Consumer<DesugaredLibraryConfiguration.Builder> configurationAmender) {
     origin = stringResource.getOrigin();
     assert origin != null;
     configurationBuilder = DesugaredLibraryConfiguration.builder(dexItemFactory, reporter, origin);
@@ -142,6 +149,7 @@
       }
       configurationBuilder.setExtraKeepRules(extraKeepRules);
     }
+    configurationAmender.accept(configurationBuilder);
     DesugaredLibraryConfiguration config = configurationBuilder.build();
     configurationBuilder = null;
     origin = null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index a792f06..67d5b2a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -415,8 +415,8 @@
       if (lens.isIdentityLens()) {
         return null;
       }
-      if (lens.isGraphLensWithPrevious()) {
-        return find(lens.asGraphLensWithPrevious().getPrevious());
+      if (lens.isNonIdentityLens()) {
+        return find(lens.asNonIdentityLens().getPrevious());
       }
       assert false;
       return null;
@@ -452,7 +452,7 @@
                 ? originalMethodSignatures.getOrDefault(method, method)
                 : method;
       }
-      return previousLens.getOriginalMethodSignature(originalMethod);
+      return getPrevious().getOriginalMethodSignature(originalMethod);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index fad4980..2faa07a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -354,14 +354,20 @@
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute();
       if (enclosingMethod != null) {
-        DexMethod mappedEnclosingMethod = lens.lookupMethod(enclosingMethod.getEnclosingMethod());
-        if (mappedEnclosingMethod != null
-            && mappedEnclosingMethod != enclosingMethod.getEnclosingMethod()) {
-          clazz.setEnclosingMethodAttribute(new EnclosingMethodAttribute(mappedEnclosingMethod));
+        if (enclosingMethod.getEnclosingMethod() != null) {
+          DexMethod mappedEnclosingMethod = lens.lookupMethod(enclosingMethod.getEnclosingMethod());
+          if (mappedEnclosingMethod != enclosingMethod.getEnclosingMethod()) {
+            clazz.setEnclosingMethodAttribute(new EnclosingMethodAttribute(mappedEnclosingMethod));
+          }
+        } else {
+          assert enclosingMethod.getEnclosingClass() != null;
+          DexType mappedEnclosingClass = lens.lookupType(enclosingMethod.getEnclosingClass());
+          if (mappedEnclosingClass != enclosingMethod.getEnclosingClass()) {
+            clazz.setEnclosingMethodAttribute(new EnclosingMethodAttribute(mappedEnclosingClass));
+          }
         }
       }
     }
-    ;
     // Return lens without method map (but still retaining originalMethodSignatures), as the
     // generated lambdas classes are generated with the an invoke to the new method, so no
     // code rewriting is required.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 8370326..89dc96c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -87,7 +87,7 @@
   private DexEncodedMethod lookupOnHolder(
       DexMethod method, DexClassAndMethod context, Invoke.Type invokeType) {
     DexMethod rewritten =
-        appView.graphLens().lookupMethod(method, context.getReference(), invokeType).getMethod();
+        appView.graphLens().lookupMethod(method, context.getReference(), invokeType).getReference();
     return rewritten.lookupOnClass(appView.definitionForHolder(rewritten));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
index 9e429a4..7bc5e6b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
-import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.google.common.collect.ImmutableMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -61,13 +61,13 @@
 
   @Override
   public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
-    assert previousLens.lookupGetFieldForMethod(field, context) == null;
+    assert getPrevious().lookupGetFieldForMethod(field, context) == null;
     return lookupFieldForMethod(field, context, getFieldMap);
   }
 
   @Override
   public DexMethod lookupPutFieldForMethod(DexField field, DexMethod context) {
-    assert previousLens.lookupPutFieldForMethod(field, context) == null;
+    assert getPrevious().lookupPutFieldForMethod(field, context) == null;
     return lookupFieldForMethod(field, context, putFieldMap);
   }
 
@@ -78,7 +78,7 @@
 
   @Override
   public boolean verifyIsContextFreeForMethod(DexMethod method) {
-    assert !methodMap.containsKey(previousLens.lookupMethod(method));
+    assert !methodMap.containsKey(getPrevious().lookupMethod(method));
     return true;
   }
 
@@ -110,24 +110,28 @@
   }
 
   @Override
-  public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Invoke.Type type) {
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
     assert originalMethodSignatures == null;
-    GraphLensLookupResult lookup = previousLens.lookupMethod(method, context, type);
-    DexMethod bridge = methodMap.get(lookup.getMethod());
+    DexMethod bridge = methodMap.get(previous.getReference());
     if (bridge == null) {
-      return lookup;
+      return previous;
     }
     assert context != null : "Guaranteed by isContextFreeForMethod";
     if (bridge.holder == context.holder) {
-      return lookup;
+      return previous;
     }
+    MethodLookupResult.Builder resultBuilder =
+        MethodLookupResult.builder(this).setReference(bridge);
     if (isConstructorBridge(bridge)) {
-      return new GraphLensLookupResult(
-          bridge,
-          Invoke.Type.DIRECT,
-          internalDescribePrototypeChanges(lookup.getPrototypeChanges(), bridge));
+      resultBuilder
+          .setPrototypeChanges(
+              internalDescribePrototypeChanges(previous.getPrototypeChanges(), bridge))
+          .setType(Type.DIRECT);
+    } else {
+      resultBuilder.setPrototypeChanges(previous.getPrototypeChanges()).setType(Type.STATIC);
     }
-    return new GraphLensLookupResult(bridge, Invoke.Type.STATIC, lookup.getPrototypeChanges());
+    return resultBuilder.build();
   }
 
   public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
index f0b74ea..8f0ddf7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
@@ -11,6 +11,8 @@
 
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -28,6 +30,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.collections.ImmutableDeque;
+import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 
@@ -85,6 +89,9 @@
     CfLabel tryCatchHandler = new CfLabel();
     builder.add(
         tryCatchHandler,
+        new CfFrame(
+            ImmutableInt2ReferenceSortedMap.empty(),
+            ImmutableDeque.of(FrameType.initialized(factory.throwableType))),
         new CfStore(ValueType.OBJECT, 0),
         new CfNew(factory.serviceLoaderConfigurationErrorType),
         new CfStackInstruction(CfStackInstruction.Opcode.Dup),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index d28ee6d..da56b69 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import java.util.ArrayDeque;
 import java.util.Arrays;
 
 public final class BackportedMethods {
@@ -133,7 +134,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfIf(If.Type.EQ, ValueType.INT, label2),
             new CfConstNumber(1, ValueType.INT),
@@ -146,7 +147,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(-1, ValueType.INT),
             label3,
             new CfFrame(
@@ -156,7 +157,8 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label4),
         ImmutableList.of(),
@@ -183,14 +185,15 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.intType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(1237, ValueType.INT),
             label2,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.intType)}),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -331,7 +334,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             new CfStore(ValueType.INT, 4),
             label5,
@@ -360,7 +363,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 4),
             new CfLoad(ValueType.INT, 5),
             new CfIfCmp(If.Type.GE, ValueType.INT, label12),
@@ -409,7 +412,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfIinc(4, 1),
             new CfGoto(label6),
             label12,
@@ -422,7 +425,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 2),
             new CfLoad(ValueType.INT, 3),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
@@ -533,7 +536,7 @@
                       FrameType.initialized(options.itemFactory.throwableType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
                 182,
@@ -582,9 +585,10 @@
                       FrameType.initialized(options.itemFactory.throwableType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(
-                    FrameType.initialized(
-                        options.itemFactory.createType("Ljava/lang/Exception;")))),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(
+                            options.itemFactory.createType("Ljava/lang/Exception;"))))),
             new CfStore(ValueType.OBJECT, 2),
             label6,
             new CfNew(options.itemFactory.createType("Ljava/lang/AssertionError;")),
@@ -650,7 +654,8 @@
                       FrameType.initialized(options.itemFactory.throwableType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.throwableType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.throwableType)))),
             new CfStore(ValueType.OBJECT, 2),
             label8,
             new CfNew(options.itemFactory.createType("Ljava/lang/AssertionError;")),
@@ -716,10 +721,11 @@
                       FrameType.initialized(options.itemFactory.throwableType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(
-                    FrameType.initialized(
-                        options.itemFactory.createType(
-                            "Ljava/lang/reflect/InvocationTargetException;")))),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(
+                            options.itemFactory.createType(
+                                "Ljava/lang/reflect/InvocationTargetException;"))))),
             new CfStore(ValueType.OBJECT, 2),
             label10,
             new CfLoad(ValueType.OBJECT, 2),
@@ -739,7 +745,7 @@
                       FrameType.initialized(options.itemFactory.throwableType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfGoto(label16),
             label12,
             new CfFrame(
@@ -749,7 +755,8 @@
                       FrameType.initialized(options.itemFactory.throwableType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.throwableType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.throwableType)))),
             new CfStore(ValueType.OBJECT, 2),
             label13,
             new CfLoad(ValueType.OBJECT, 0),
@@ -765,7 +772,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.throwableType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             label15,
             new CfFrame(
@@ -776,7 +783,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.throwableType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.throwableType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.throwableType)))),
             new CfThrow(),
             label16,
             new CfFrame(
@@ -786,7 +794,7 @@
                       FrameType.initialized(options.itemFactory.throwableType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfReturnVoid(),
             label17),
         ImmutableList.of(
@@ -881,7 +889,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 4),
             new CfLoad(ValueType.INT, 3),
             new CfIfCmp(If.Type.GE, ValueType.INT, label5),
@@ -920,7 +928,7 @@
                       FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/ArrayList;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
                 184,
@@ -1040,7 +1048,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 4),
             new CfLoad(ValueType.INT, 3),
             new CfIfCmp(If.Type.GE, ValueType.INT, label8),
@@ -1158,7 +1166,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfIinc(4, 1),
             new CfGoto(label2),
             label8,
@@ -1170,7 +1178,7 @@
                           options.itemFactory.createType("[Ljava/util/Map$Entry;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/HashMap;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
                 184,
@@ -1234,7 +1242,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 4),
             new CfLoad(ValueType.INT, 3),
             new CfIfCmp(If.Type.GE, ValueType.INT, label6),
@@ -1319,7 +1327,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfIinc(4, 1),
             new CfGoto(label2),
             label6,
@@ -1330,7 +1338,7 @@
                       FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/HashSet;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
                 184,
@@ -1402,7 +1410,7 @@
                           options.itemFactory.createType("Ljava/util/ArrayList;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/Iterator;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 185,
@@ -1452,7 +1460,7 @@
                           options.itemFactory.createType("Ljava/util/Collection;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/ArrayList;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
                 184,
@@ -1533,7 +1541,7 @@
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/HashMap;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/Iterator;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 185,
@@ -1611,7 +1619,7 @@
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/Map;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/HashMap;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
                 184,
@@ -1682,7 +1690,7 @@
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/HashSet;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/Iterator;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 185,
@@ -1732,7 +1740,7 @@
                           options.itemFactory.createType("Ljava/util/Collection;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/HashSet;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
                 184,
@@ -1910,14 +1918,15 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.doubleType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.doubleType)}),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -1962,14 +1971,15 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.floatType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.floatType)}),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -2001,7 +2011,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfLoad(ValueType.INT, 1),
             new CfIfCmp(If.Type.GE, ValueType.INT, label2),
@@ -2015,7 +2025,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(1, ValueType.INT),
             label3,
             new CfFrame(
@@ -2025,7 +2035,8 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label4),
         ImmutableList.of(),
@@ -2188,7 +2199,7 @@
                       FrameType.initialized(options.itemFactory.stringType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.INT, 1),
             new CfInvoke(
@@ -2292,7 +2303,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 2),
             new CfNumberConversion(NumericType.LONG, NumericType.INT),
             new CfReturn(ValueType.INT),
@@ -2509,7 +2520,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(1, ValueType.LONG),
             new CfReturn(ValueType.LONG),
             label6,
@@ -2520,7 +2531,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(0, ValueType.LONG),
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
@@ -2538,7 +2549,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(1, ValueType.INT),
             new CfLogicalBinop(CfLogicalBinop.Opcode.Ushr, NumericType.LONG),
@@ -2584,7 +2595,8 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.longType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.longType)))),
             new CfConstNumber(0, ValueType.INT),
             label14,
             new CfFrame(
@@ -2598,9 +2610,10 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(
-                    FrameType.initialized(options.itemFactory.longType),
-                    FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(options.itemFactory.longType),
+                        FrameType.initialized(options.itemFactory.intType)))),
             new CfNumberConversion(NumericType.INT, NumericType.LONG),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.LONG),
             new CfReturn(ValueType.LONG),
@@ -2721,7 +2734,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 1),
             new CfConstNumber(2, ValueType.INT),
             new CfIfCmp(If.Type.LT, ValueType.INT, label4),
@@ -2737,7 +2750,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("illegal radix: ")),
@@ -2776,7 +2789,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(-1, ValueType.LONG),
             new CfLoad(ValueType.INT, 1),
             new CfNumberConversion(NumericType.INT, NumericType.LONG),
@@ -2819,7 +2832,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label8,
             new CfFrame(
@@ -2831,7 +2844,8 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfStore(ValueType.INT, 5),
             label9,
             new CfConstNumber(0, ValueType.LONG),
@@ -2852,7 +2866,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 8),
             new CfLoad(ValueType.INT, 2),
             new CfIfCmp(If.Type.GE, ValueType.INT, label20),
@@ -2910,7 +2924,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 6),
             new CfConstNumber(0, ValueType.LONG),
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
@@ -2954,7 +2968,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("Too large for unsigned long: ")),
@@ -2990,7 +3004,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 6),
             new CfLoad(ValueType.INT, 1),
             new CfNumberConversion(NumericType.INT, NumericType.LONG),
@@ -3014,7 +3028,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 6),
             new CfReturn(ValueType.LONG),
             label21),
@@ -3077,7 +3091,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfLoad(ValueType.LONG, 2),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.LONG),
@@ -3090,7 +3104,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(0, ValueType.LONG),
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
@@ -3108,7 +3122,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(1, ValueType.INT),
             new CfLogicalBinop(CfLogicalBinop.Opcode.Ushr, NumericType.LONG),
@@ -3154,7 +3168,8 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.longType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.longType)))),
             new CfConstNumber(0, ValueType.LONG),
             label14,
             new CfFrame(
@@ -3168,9 +3183,10 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(
-                    FrameType.initialized(options.itemFactory.longType),
-                    FrameType.initialized(options.itemFactory.longType))),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(options.itemFactory.longType),
+                        FrameType.initialized(options.itemFactory.longType)))),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.LONG),
             new CfReturn(ValueType.LONG),
             label15),
@@ -3255,7 +3271,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(0, ValueType.LONG),
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
@@ -3282,7 +3298,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 2),
             new CfConstNumber(2, ValueType.INT),
             new CfIfCmp(If.Type.LT, ValueType.INT, label5),
@@ -3297,7 +3313,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(10, ValueType.INT),
             new CfStore(ValueType.INT, 2),
             label6,
@@ -3308,7 +3324,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(64, ValueType.INT),
             new CfNewArray(options.itemFactory.charArrayType),
             new CfStore(ValueType.OBJECT, 3),
@@ -3351,7 +3367,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 3),
             new CfIinc(4, -1),
             new CfLoad(ValueType.INT, 4),
@@ -3393,7 +3409,7 @@
                       FrameType.initialized(options.itemFactory.charArrayType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 2),
             new CfConstNumber(1, ValueType.INT),
             new CfLogicalBinop(CfLogicalBinop.Opcode.And, NumericType.INT),
@@ -3420,7 +3436,7 @@
                       FrameType.initialized(options.itemFactory.charArrayType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfLoad(ValueType.INT, 2),
             new CfNumberConversion(NumericType.INT, NumericType.LONG),
@@ -3446,7 +3462,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfLoad(ValueType.LONG, 5),
             new CfLoad(ValueType.INT, 2),
@@ -3487,7 +3503,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(0, ValueType.LONG),
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
@@ -3530,7 +3546,7 @@
                       FrameType.initialized(options.itemFactory.charArrayType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.stringType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 3),
@@ -3598,7 +3614,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
@@ -3652,7 +3668,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label3,
             new CfFrame(
@@ -3663,7 +3679,8 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfLoad(ValueType.LONG, 0),
             new CfLoad(ValueType.LONG, 4),
             new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG),
@@ -3681,7 +3698,8 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfConstNumber(0, ValueType.INT),
             label5,
             new CfFrame(
@@ -3692,9 +3710,10 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(
-                    FrameType.initialized(options.itemFactory.intType),
-                    FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(options.itemFactory.intType),
+                        FrameType.initialized(options.itemFactory.intType)))),
             new CfLogicalBinop(CfLogicalBinop.Opcode.Or, NumericType.INT),
             new CfIf(If.Type.EQ, ValueType.INT, label7),
             label6,
@@ -3709,7 +3728,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
@@ -3755,7 +3774,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.intType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfConstNumber(1, ValueType.INT),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
@@ -3796,7 +3815,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.longType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(1, ValueType.LONG),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.LONG),
@@ -3849,7 +3868,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(1, ValueType.INT),
             new CfLoad(ValueType.INT, 0),
             new CfLoad(ValueType.INT, 1),
@@ -3876,7 +3895,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 2),
             label7,
             new CfFrame(
@@ -3889,7 +3908,8 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label8),
         ImmutableList.of(),
@@ -3941,7 +3961,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(1, ValueType.LONG),
             new CfLoad(ValueType.LONG, 0),
             new CfLoad(ValueType.LONG, 2),
@@ -3970,7 +3990,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 4),
             label7,
             new CfFrame(
@@ -3983,7 +4003,8 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.longType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.longType)))),
             new CfReturn(ValueType.LONG),
             label8),
         ImmutableList.of(),
@@ -4052,7 +4073,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(1, ValueType.INT),
             new CfLoad(ValueType.INT, 0),
             new CfLoad(ValueType.INT, 1),
@@ -4076,7 +4097,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 2),
             new CfLoad(ValueType.INT, 1),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
@@ -4090,7 +4111,8 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label7),
         ImmutableList.of(),
@@ -4133,7 +4155,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(1, ValueType.LONG),
             new CfLoad(ValueType.LONG, 0),
             new CfLoad(ValueType.LONG, 2),
@@ -4159,7 +4181,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 4),
             new CfLoad(ValueType.LONG, 2),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.LONG),
@@ -4173,7 +4195,8 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.longType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.longType)))),
             new CfReturn(ValueType.LONG),
             label7),
         ImmutableList.of(),
@@ -4239,7 +4262,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.intType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfConstNumber(1, ValueType.INT),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
@@ -4280,7 +4303,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.longType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfConstNumber(1, ValueType.LONG),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.LONG),
@@ -4332,7 +4355,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
@@ -4438,7 +4461,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 4),
             new CfConstNumber(64, ValueType.INT),
             new CfIfCmp(If.Type.LT, ValueType.INT, label15),
@@ -4457,7 +4480,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label9,
             new CfFrame(
@@ -4468,7 +4491,8 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfLoad(ValueType.LONG, 2),
             new CfConstNumber(-9223372036854775808L, ValueType.LONG),
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
@@ -4484,7 +4508,8 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfConstNumber(0, ValueType.INT),
             label11,
             new CfFrame(
@@ -4495,9 +4520,10 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList(
-                    FrameType.initialized(options.itemFactory.intType),
-                    FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(options.itemFactory.intType),
+                        FrameType.initialized(options.itemFactory.intType)))),
             new CfLogicalBinop(CfLogicalBinop.Opcode.Or, NumericType.INT),
             new CfIf(If.Type.EQ, ValueType.INT, label15),
             label12,
@@ -4526,7 +4552,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 5),
             new CfReturn(ValueType.LONG),
             label15,
@@ -4538,7 +4564,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
@@ -4736,7 +4762,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.intType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfNeg(NumericType.INT),
             new CfReturn(ValueType.INT),
@@ -4776,7 +4802,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.longType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.LONG, 0),
             new CfNeg(NumericType.LONG),
             new CfReturn(ValueType.LONG),
@@ -4879,7 +4905,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
@@ -4933,7 +4959,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label3,
             new CfFrame(
@@ -4944,7 +4970,8 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfLoad(ValueType.LONG, 0),
             new CfLoad(ValueType.LONG, 4),
             new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG),
@@ -4962,7 +4989,8 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfConstNumber(0, ValueType.INT),
             label5,
             new CfFrame(
@@ -4973,9 +5001,10 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList(
-                    FrameType.initialized(options.itemFactory.intType),
-                    FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(options.itemFactory.intType),
+                        FrameType.initialized(options.itemFactory.intType)))),
             new CfLogicalBinop(CfLogicalBinop.Opcode.Or, NumericType.INT),
             new CfIf(If.Type.EQ, ValueType.INT, label7),
             label6,
@@ -4990,7 +5019,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.longType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
@@ -5046,7 +5075,7 @@
                       FrameType.initialized(options.itemFactory.longType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 2),
             new CfReturn(ValueType.INT),
             label4),
@@ -5086,7 +5115,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
@@ -5195,7 +5224,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfReturn(ValueType.INT),
             label3),
@@ -5231,7 +5260,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
@@ -5322,7 +5351,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfReturn(ValueType.INT),
             label3),
@@ -5354,7 +5383,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
@@ -5426,7 +5455,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfReturn(ValueType.INT),
             label3),
@@ -5460,7 +5489,7 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/Comparator;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 1),
@@ -5484,7 +5513,8 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/Comparator;"))
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -5551,7 +5581,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfIf(If.Type.NE, ValueType.OBJECT, label2),
             new CfConstNumber(0, ValueType.INT),
@@ -5564,7 +5594,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.booleanArrayType),
             new CfIf(If.Type.EQ, ValueType.INT, label6),
@@ -5597,7 +5627,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label5,
             new CfFrame(
@@ -5607,7 +5637,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label6,
             new CfFrame(
@@ -5617,7 +5648,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.byteArrayType),
             new CfIf(If.Type.EQ, ValueType.INT, label10),
@@ -5650,7 +5681,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label9,
             new CfFrame(
@@ -5660,7 +5691,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label10,
             new CfFrame(
@@ -5670,7 +5702,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.charArrayType),
             new CfIf(If.Type.EQ, ValueType.INT, label14),
@@ -5703,7 +5735,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label13,
             new CfFrame(
@@ -5713,7 +5745,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label14,
             new CfFrame(
@@ -5723,7 +5756,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.doubleArrayType),
             new CfIf(If.Type.EQ, ValueType.INT, label18),
@@ -5756,7 +5789,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label17,
             new CfFrame(
@@ -5766,7 +5799,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label18,
             new CfFrame(
@@ -5776,7 +5810,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.floatArrayType),
             new CfIf(If.Type.EQ, ValueType.INT, label22),
@@ -5809,7 +5843,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label21,
             new CfFrame(
@@ -5819,7 +5853,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label22,
             new CfFrame(
@@ -5829,7 +5864,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.intArrayType),
             new CfIf(If.Type.EQ, ValueType.INT, label26),
@@ -5862,7 +5897,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label25,
             new CfFrame(
@@ -5872,7 +5907,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label26,
             new CfFrame(
@@ -5882,7 +5918,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.longArrayType),
             new CfIf(If.Type.EQ, ValueType.INT, label30),
@@ -5915,7 +5951,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label29,
             new CfFrame(
@@ -5925,7 +5961,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label30,
             new CfFrame(
@@ -5935,7 +5972,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.shortArrayType),
             new CfIf(If.Type.EQ, ValueType.INT, label34),
@@ -5968,7 +6005,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label33,
             new CfFrame(
@@ -5978,7 +6015,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label34,
             new CfFrame(
@@ -5988,7 +6026,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInstanceOf(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfIf(If.Type.EQ, ValueType.INT, label38),
@@ -6021,7 +6059,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label37,
             new CfFrame(
@@ -6031,7 +6069,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label38,
             new CfFrame(
@@ -6041,7 +6080,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
@@ -6094,7 +6133,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(1, ValueType.INT),
             new CfGoto(label3),
             label2,
@@ -6105,7 +6144,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label3,
             new CfFrame(
@@ -6115,7 +6154,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label4),
         ImmutableList.of(),
@@ -6142,7 +6182,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 182,
@@ -6156,7 +6196,8 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -6183,14 +6224,15 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -6217,14 +6259,15 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -6254,7 +6297,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.objectType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfConstString(options.itemFactory.createString("defaultObj")),
             new CfInvoke(
@@ -6298,7 +6341,7 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/function/Supplier;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfConstString(options.itemFactory.createString("supplier")),
             new CfInvoke(
@@ -6374,7 +6417,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.stringType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfReturn(ValueType.OBJECT),
             label3),
@@ -6432,7 +6475,7 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.stringType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 182,
@@ -6449,7 +6492,8 @@
                       FrameType.initialized(options.itemFactory.objectType),
                       FrameType.initialized(options.itemFactory.stringType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.stringType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.stringType)))),
             new CfReturn(ValueType.OBJECT),
             label3),
         ImmutableList.of(),
@@ -6506,7 +6550,7 @@
                           options.itemFactory.createType("Ljava/util/function/Consumer;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 185,
@@ -6525,7 +6569,7 @@
                           options.itemFactory.createType("Ljava/util/function/Consumer;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfReturnVoid(),
             label4),
         ImmutableList.of(),
@@ -6584,7 +6628,7 @@
                           options.itemFactory.createType("Ljava/util/function/DoubleConsumer;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 185,
@@ -6604,7 +6648,7 @@
                           options.itemFactory.createType("Ljava/util/function/DoubleConsumer;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfReturnVoid(),
             label4),
         ImmutableList.of(),
@@ -6663,7 +6707,7 @@
                           options.itemFactory.createType("Ljava/util/function/IntConsumer;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 185,
@@ -6683,7 +6727,7 @@
                           options.itemFactory.createType("Ljava/util/function/IntConsumer;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfReturnVoid(),
             label4),
         ImmutableList.of(),
@@ -6742,7 +6786,7 @@
                           options.itemFactory.createType("Ljava/util/function/LongConsumer;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 185,
@@ -6762,7 +6806,7 @@
                           options.itemFactory.createType("Ljava/util/function/LongConsumer;")),
                       FrameType.initialized(options.itemFactory.createType("Ljava/lang/Runnable;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfReturnVoid(),
             label4),
         ImmutableList.of(),
@@ -6798,7 +6842,7 @@
                     new FrameType[] {
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/Optional;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
@@ -6807,7 +6851,8 @@
                     new FrameType[] {
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/Optional;"))
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -6844,7 +6889,7 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/OptionalDouble;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
@@ -6854,7 +6899,8 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/OptionalDouble;"))
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -6891,7 +6937,7 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/OptionalInt;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
@@ -6901,7 +6947,8 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/OptionalInt;"))
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -6938,7 +6985,7 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/OptionalLong;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label2,
             new CfFrame(
@@ -6948,7 +6995,8 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/OptionalLong;"))
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label3),
         ImmutableList.of(),
@@ -7000,7 +7048,7 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/function/Supplier;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
                 185,
@@ -7074,7 +7122,7 @@
                     new FrameType[] {
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/Optional;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
@@ -7136,7 +7184,7 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/OptionalDouble;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
@@ -7198,7 +7246,7 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/OptionalInt;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
@@ -7260,7 +7308,7 @@
                       FrameType.initialized(
                           options.itemFactory.createType("Ljava/util/OptionalLong;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
@@ -7379,7 +7427,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 184,
@@ -7395,9 +7443,10 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
-                Arrays.asList(
-                    FrameType.initialized(
-                        options.itemFactory.createType("Ljava/util/stream/Stream;")))),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(
+                            options.itemFactory.createType("Ljava/util/stream/Stream;"))))),
             new CfReturn(ValueType.OBJECT),
             label3),
         ImmutableList.of(),
@@ -7442,7 +7491,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 1),
             new CfLoad(ValueType.INT, 2),
             new CfIfCmp(If.Type.GE, ValueType.INT, label8),
@@ -7482,7 +7531,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 1),
             new CfLoad(ValueType.INT, 3),
             new CfInvoke(
@@ -7502,7 +7551,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.stringType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(1, ValueType.INT),
             new CfReturn(ValueType.INT),
             label9),
@@ -7551,7 +7600,7 @@
                       FrameType.initialized(
                           options.itemFactory.createType("[Ljava/lang/CharSequence;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
@@ -7595,7 +7644,7 @@
                       FrameType.initialized(options.itemFactory.stringBuilderType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 3),
             new CfLoad(ValueType.OBJECT, 1),
             new CfArrayLength(),
@@ -7641,7 +7690,7 @@
                           options.itemFactory.createType("[Ljava/lang/CharSequence;")),
                       FrameType.initialized(options.itemFactory.stringBuilderType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 182,
@@ -7695,7 +7744,7 @@
                       FrameType.initialized(options.itemFactory.charSequenceType),
                       FrameType.initialized(options.itemFactory.createType("Ljava/lang/Iterable;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
@@ -7758,7 +7807,7 @@
                       FrameType.initialized(options.itemFactory.stringBuilderType),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/Iterator;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 3),
             new CfInvoke(
                 185,
@@ -7813,7 +7862,7 @@
                       FrameType.initialized(options.itemFactory.stringBuilderType),
                       FrameType.initialized(options.itemFactory.createType("Ljava/util/Iterator;"))
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 182,
@@ -7905,7 +7954,7 @@
                       FrameType.initialized(options.itemFactory.stringType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 182,
@@ -7929,7 +7978,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstString(options.itemFactory.createString("")),
             new CfReturn(ValueType.OBJECT),
             label5,
@@ -7941,7 +7990,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 1),
             new CfConstNumber(1, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label7),
@@ -7957,7 +8006,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.INT, 2),
@@ -7986,7 +8035,7 @@
                       FrameType.initialized(options.itemFactory.stringBuilderType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 4),
             new CfLoad(ValueType.INT, 1),
             new CfIfCmp(If.Type.GE, ValueType.INT, label12),
@@ -8015,7 +8064,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.stringBuilderType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 3),
             new CfInvoke(
                 182,
@@ -8074,7 +8123,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 1),
             new CfLoad(ValueType.INT, 2),
             new CfIfCmp(If.Type.GE, ValueType.INT, label8),
@@ -8113,7 +8162,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 1),
             new CfLoad(ValueType.INT, 3),
             new CfInvoke(
@@ -8137,7 +8186,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 2),
             new CfLoad(ValueType.INT, 1),
             new CfIfCmp(If.Type.LE, ValueType.INT, label14),
@@ -8178,7 +8227,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 2),
             new CfLoad(ValueType.INT, 3),
             new CfInvoke(
@@ -8202,7 +8251,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.INT, 1),
             new CfLoad(ValueType.INT, 2),
@@ -8260,7 +8309,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 1),
             new CfLoad(ValueType.INT, 2),
             new CfIfCmp(If.Type.GE, ValueType.INT, label8),
@@ -8299,7 +8348,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 1),
             new CfLoad(ValueType.INT, 3),
             new CfInvoke(
@@ -8323,7 +8372,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfLoad(ValueType.INT, 1),
             new CfLoad(ValueType.INT, 2),
@@ -8376,7 +8425,7 @@
                       FrameType.initialized(options.itemFactory.stringType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 1),
             new CfIf(If.Type.LE, ValueType.INT, label7),
             label2,
@@ -8415,7 +8464,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 1),
             new CfLoad(ValueType.INT, 2),
             new CfInvoke(
@@ -8438,7 +8487,7 @@
                       FrameType.initialized(options.itemFactory.stringType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 0),
             new CfConstNumber(0, ValueType.INT),
             new CfLoad(ValueType.INT, 1),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index eb6bddd..b6bd724 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -99,6 +99,11 @@
       it.removeOrReplaceByDebugLocalRead();
       value.uniquePhiUsers().forEach(Phi::removeTrivialPhi);
     }
+
+    @Override
+    public String toString() {
+      return "ExistingValue(v" + value.getNumber() + ")";
+    }
   }
 
   private class MaterializableValue implements FieldValue {
@@ -361,7 +366,7 @@
 
     InstanceFieldInitializationInfoCollection fieldInitializationInfos =
         instanceInitializerInfo.fieldInitializationInfos();
-    fieldInitializationInfos.forEach(
+    fieldInitializationInfos.forEachWithDeterministicOrder(
         appView,
         (field, info) -> {
           if (!appView.appInfo().withLiveness().mayPropagateValueFor(field.field)) {
@@ -372,13 +377,22 @@
                 invoke.getArgument(info.asArgumentInitializationInfo().getArgumentIndex());
             Value object = invoke.getReceiver().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
-            activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(value));
+            if (field.isFinal()) {
+              activeState.putFinalInstanceField(fieldAndObject, new ExistingValue(value));
+            } else {
+              activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(value));
+            }
           } else if (info.isSingleValue()) {
             SingleValue value = info.asSingleValue();
             if (value.isMaterializableInContext(appView.withLiveness(), method)) {
               Value object = invoke.getReceiver().getAliasedValue();
               FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
-              activeState.putNonFinalInstanceField(fieldAndObject, new MaterializableValue(value));
+              if (field.isFinal()) {
+                activeState.putFinalInstanceField(fieldAndObject, new MaterializableValue(value));
+              } else {
+                activeState.putNonFinalInstanceField(
+                    fieldAndObject, new MaterializableValue(value));
+              }
             }
           } else {
             assert info.isTypeInitializationInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
index 922b5f0..21c8b07 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
@@ -38,6 +38,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import java.util.ArrayDeque;
 import java.util.Arrays;
 
 public final class EnumUnboxingCfMethods {
@@ -69,7 +70,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
@@ -88,7 +89,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfLoad(ValueType.INT, 1),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
@@ -132,7 +133,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfLoad(ValueType.INT, 1),
             new CfIfCmp(If.Type.NE, ValueType.INT, label3),
@@ -146,7 +147,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfConstNumber(0, ValueType.INT),
             label4,
             new CfFrame(
@@ -156,7 +157,8 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList(FrameType.initialized(options.itemFactory.intType))),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
             new CfReturn(ValueType.INT),
             label5),
         ImmutableList.of(),
@@ -192,7 +194,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.intType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 0),
             new CfConstNumber(1, ValueType.INT),
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
@@ -231,7 +233,7 @@
                       FrameType.initialized(options.itemFactory.intArrayType),
                       FrameType.initialized(options.itemFactory.intType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.INT, 2),
             new CfLoad(ValueType.OBJECT, 1),
             new CfArrayLength(),
@@ -254,7 +256,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.intArrayType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfLoad(ValueType.OBJECT, 1),
             new CfReturn(ValueType.OBJECT),
             label6),
@@ -291,7 +293,7 @@
                 new Int2ReferenceAVLTreeMap<>(
                     new int[] {0},
                     new FrameType[] {FrameType.initialized(options.itemFactory.intType)}),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfReturnVoid(),
             label3),
         ImmutableList.of(),
@@ -333,7 +335,7 @@
                       FrameType.initialized(options.itemFactory.intType),
                       FrameType.initialized(options.itemFactory.stringType)
                     }),
-                Arrays.asList()),
+                new ArrayDeque<>(Arrays.asList())),
             new CfReturnVoid(),
             label3),
         ImmutableList.of(),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
index 4e45bac..9c7d07c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
@@ -35,6 +35,13 @@
   }
 
   @Override
+  public void forEachWithDeterministicOrder(
+      DexDefinitionSupplier definitions,
+      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) {
+    // Intentionally empty.
+  }
+
+  @Override
   public InstanceFieldInitializationInfo get(DexEncodedField field) {
     return UnknownInstanceFieldInitializationInfo.getInstance();
   }
@@ -49,4 +56,9 @@
       AppView<AppInfoWithLiveness> appView, GraphLens lens) {
     return this;
   }
+
+  @Override
+  public String toString() {
+    return "EmptyInstanceFieldInitializationInfoCollection";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
index 0b87005..0447d5d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldArgumentInitializationInfo.java
@@ -43,4 +43,9 @@
     // initialization info.
     return this;
   }
+
+  @Override
+  public String toString() {
+    return "InstanceFieldArgumentInitializationInfo(argumentIndex=" + argumentIndex + ")";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
index 5979031..775d7e9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -10,8 +10,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.IdentityHashMap;
-import java.util.Map;
+import java.util.TreeMap;
 import java.util.function.BiConsumer;
 
 /**
@@ -31,6 +30,10 @@
       DexDefinitionSupplier definitions,
       BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer);
 
+  public abstract void forEachWithDeterministicOrder(
+      DexDefinitionSupplier definitions,
+      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer);
+
   public abstract InstanceFieldInitializationInfo get(DexEncodedField field);
 
   public abstract boolean isEmpty();
@@ -40,7 +43,8 @@
 
   public static class Builder {
 
-    Map<DexField, InstanceFieldInitializationInfo> infos = new IdentityHashMap<>();
+    TreeMap<DexField, InstanceFieldInitializationInfo> infos =
+        new TreeMap<>(DexField::slowCompareTo);
 
     public void recordInitializationInfo(
         DexEncodedField field, InstanceFieldInitializationInfo info) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
index fe09706..7e78f27 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
@@ -87,4 +87,9 @@
     return Objects.equals(dynamicLowerBoundType, info.dynamicLowerBoundType)
         && Objects.equals(dynamicUpperBoundType, info.dynamicUpperBoundType);
   }
+
+  @Override
+  public String toString() {
+    return "InstanceFieldTypeInitializationInfo";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
index e8daa83..6fa0363 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -12,17 +12,20 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.Map;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeMap;
 import java.util.function.BiConsumer;
 
 /** See {@link InstanceFieldArgumentInitializationInfo}. */
 public class NonTrivialInstanceFieldInitializationInfoCollection
     extends InstanceFieldInitializationInfoCollection {
 
-  private final Map<DexField, InstanceFieldInitializationInfo> infos;
+  private final TreeMap<DexField, InstanceFieldInitializationInfo> infos;
 
   NonTrivialInstanceFieldInitializationInfoCollection(
-      Map<DexField, InstanceFieldInitializationInfo> infos) {
+      TreeMap<DexField, InstanceFieldInitializationInfo> infos) {
     assert !infos.isEmpty();
     assert infos.values().stream().noneMatch(InstanceFieldInitializationInfo::isUnknown);
     this.infos = infos;
@@ -45,6 +48,14 @@
   }
 
   @Override
+  public void forEachWithDeterministicOrder(
+      DexDefinitionSupplier definitions,
+      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) {
+    // We currently use a sorted backing and can therefore simply use forEach().
+    forEach(definitions, consumer);
+  }
+
+  @Override
   public InstanceFieldInitializationInfo get(DexEncodedField field) {
     return infos.getOrDefault(field.field, UnknownInstanceFieldInitializationInfo.getInstance());
   }
@@ -68,4 +79,13 @@
         });
     return builder.build();
   }
+
+  @Override
+  public String toString() {
+    List<String> strings = new ArrayList<>();
+    infos.forEach((field, info) -> strings.add(field.toSourceString() + " -> " + info));
+    return "NonTrivialInstanceFieldInitializationInfoCollection("
+        + StringUtils.join(strings, "; ")
+        + ")";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
index ab4bbfe..13d4944 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/UnknownInstanceFieldInitializationInfo.java
@@ -33,4 +33,9 @@
       AppView<AppInfoWithLiveness> appView, GraphLens lens) {
     return this;
   }
+
+  @Override
+  public String toString() {
+    return "UnknownInstanceFieldInitializationInfo";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
index e4424ec..c9a7f32 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
@@ -59,4 +59,9 @@
       AppView<AppInfoWithLiveness> appView, GraphLens lens) {
     return this;
   }
+
+  @Override
+  public String toString() {
+    return "DefaultInstanceInitializerInfo";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index 84ce93f..ee08c65 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -92,6 +92,11 @@
         lens.getRenamedMethodSignature(parent));
   }
 
+  @Override
+  public String toString() {
+    return "NonTrivialInstanceInitializerInfo(" + fieldInitializationInfos + ")";
+  }
+
   public static class Builder {
 
     private final InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
index 0f6a8ed..cc01f83 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.Lists;
 import com.google.common.io.BaseEncoding;
@@ -148,7 +149,7 @@
     int lastUsed = -1;
     int lastSeen = -1;
     for (Entry<DexType, LambdaInfo> entry : lambdas.entrySet()) {
-      Integer index = entry.getValue().id;
+      int index = entry.getValue().id;
       assert lastUsed <= lastSeen && lastSeen < index;
       lastUsed++;
       lastSeen = index;
@@ -184,11 +185,12 @@
                     + getGroupSuffix()
                     + createHash(lambdas)
                     + ";");
-    return getBuilder(appView.dexItemFactory()).synthesizeClass(appView, feedback);
+    return getBuilder(appView.dexItemFactory(), appView.options())
+        .synthesizeClass(appView, feedback);
   }
 
   protected abstract LambdaGroupClassBuilder<? extends LambdaGroup> getBuilder(
-      DexItemFactory factory);
+      DexItemFactory factory, InternalOptions options);
 
   private String createHash(List<LambdaInfo> lambdas) {
     try {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
index 697638f..5ddc8dd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
@@ -38,28 +38,26 @@
       AppView<? extends AppInfoWithClassHierarchy> appView, OptimizationFeedback feedback) {
     DexType groupClassType = group.getGroupClassType();
     DexType superClassType = getSuperClassType();
-    DexProgramClass programClass =
-        new DexProgramClass(
-            groupClassType,
-            null,
-            new SynthesizedOrigin(origin, getClass()),
-            buildAccessFlags(),
-            superClassType,
-            buildInterfaces(),
-            factory.createString(origin),
-            null,
-            Collections.emptyList(),
-            buildEnclosingMethodAttribute(),
-            buildInnerClasses(),
-            buildAnnotations(),
-            buildStaticFields(appView, feedback),
-            buildInstanceFields(),
-            buildDirectMethods(),
-            buildVirtualMethods(),
-            factory.getSkipNameValidationForTesting(),
-            // The name of the class is based on the hash of the content.
-            DexProgramClass::checksumFromType);
-    return programClass;
+    return new DexProgramClass(
+        groupClassType,
+        null,
+        new SynthesizedOrigin(origin, getClass()),
+        buildAccessFlags(),
+        superClassType,
+        buildInterfaces(),
+        factory.createString(origin),
+        null,
+        Collections.emptyList(),
+        buildEnclosingMethodAttribute(),
+        buildInnerClasses(),
+        buildAnnotations(),
+        buildStaticFields(appView, feedback),
+        buildInstanceFields(),
+        buildDirectMethods(),
+        buildVirtualMethods(),
+        factory.getSkipNameValidationForTesting(),
+        // The name of the class is based on the hash of the content.
+        DexProgramClass::checksumFromType);
   }
 
   protected abstract DexType getSuperClassType();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 3414861..3bb366d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -167,7 +167,8 @@
         }
       }
 
-      assert invokesToInline.size() > 1;
+      assert invokesToInline.size() > 1
+          || appView.options().testing.verificationSizeLimitInBytesOverride > -1;
 
       inliner.performForcedInlining(method, code, invokesToInline, provider, Timing.empty());
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
index 4aafb60..117ee3b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.Lists;
 import java.util.List;
@@ -108,8 +109,8 @@
   }
 
   @Override
-  protected ClassBuilder getBuilder(DexItemFactory factory) {
-    return new ClassBuilder(factory, "java-style lambda group");
+  protected ClassBuilder getBuilder(DexItemFactory factory, InternalOptions options) {
+    return new ClassBuilder(factory, options, "java-style lambda group");
   }
 
   @Override
@@ -183,8 +184,8 @@
 
   // Specialized class builder.
   private final class ClassBuilder extends KotlinLambdaGroupClassBuilder<JStyleLambdaGroup> {
-    ClassBuilder(DexItemFactory factory, String origin) {
-      super(JStyleLambdaGroup.this, factory, origin);
+    ClassBuilder(DexItemFactory factory, InternalOptions options, String origin) {
+      super(JStyleLambdaGroup.this, factory, options, origin);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
index e9ab954..2e4e1a9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
@@ -27,8 +28,10 @@
     assert lambda.getKotlinInfo().isSyntheticClass();
     assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
 
-    checkAccessFlags("class access flags", lambda.accessFlags,
-        PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
+    // Ignore ACC_SUPER.
+    ClassAccessFlags copy = lambda.accessFlags.copy();
+    copy.unsetSuper();
+    checkAccessFlags("class access flags", copy, PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
 
     // Class and interface.
     validateSuperclass(kotlin, lambda);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
index cc9061a..cfae611 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.Lists;
 import java.util.List;
@@ -114,8 +115,8 @@
   }
 
   @Override
-  protected ClassBuilder getBuilder(DexItemFactory factory) {
-    return new ClassBuilder(factory, "kotlin-style lambda group");
+  protected ClassBuilder getBuilder(DexItemFactory factory, InternalOptions options) {
+    return new ClassBuilder(factory, options, "kotlin-style lambda group");
   }
 
   @Override
@@ -194,8 +195,8 @@
 
   // Specialized class builder.
   private final class ClassBuilder extends KotlinLambdaGroupClassBuilder<KStyleLambdaGroup> {
-    ClassBuilder(DexItemFactory factory, String origin) {
-      super(KStyleLambdaGroup.this, factory, origin);
+    ClassBuilder(DexItemFactory factory, InternalOptions options, String origin) {
+      super(KStyleLambdaGroup.this, factory, options, origin);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
index 495ab0b..6f7650b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
@@ -27,8 +28,10 @@
     assert lambda.getKotlinInfo().isSyntheticClass();
     assert lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda();
 
-    checkAccessFlags("class access flags", lambda.accessFlags,
-        PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
+    // Ignore ACC_SUPER.
+    ClassAccessFlags copy = lambda.accessFlags.copy();
+    copy.unsetSuper();
+    checkAccessFlags("class access flags", copy, PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
 
     // Class and interface.
     validateSuperclass(kotlin, lambda);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index dfd62d3..5b0b0b4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -26,11 +27,16 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.code.IntSwitch;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.TriConsumer;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -44,10 +50,13 @@
     extends LambdaGroupClassBuilder<T> implements KotlinLambdaConstants {
 
   final KotlinLambdaGroupId id;
+  final InternalOptions options;
 
-  KotlinLambdaGroupClassBuilder(T group, DexItemFactory factory, String origin) {
+  KotlinLambdaGroupClassBuilder(
+      T group, DexItemFactory factory, InternalOptions options, String origin) {
     super(group, factory, origin);
     this.id = group.id();
+    this.options = options;
   }
 
   abstract SyntheticSourceCode createInstanceInitializerSourceCode(
@@ -107,34 +116,104 @@
         boolean isMainMethod =
             id.mainMethodName == methodName && id.mainMethodProto == methodProto;
 
-        // For bridge methods we still use same PUBLIC FINAL as for the main method,
-        // since inlining removes BRIDGE & SYNTHETIC attributes from the bridge methods
-        // anyways and our new method is a product of inlining.
-        MethodAccessFlags accessFlags = MAIN_METHOD_FLAGS.copy();
-
-        DexMethod method = factory.createMethod(group.getGroupClassType(), methodProto, methodName);
-        result.add(
-            new DexEncodedMethod(
-                method,
-                accessFlags,
-                isMainMethod ? id.mainMethodAnnotations : DexAnnotationSet.empty(),
-                isMainMethod ? id.mainMethodParamAnnotations : ParameterAnnotationsList.empty(),
-                new SynthesizedCode(
-                    callerPosition ->
-                        new KotlinLambdaVirtualMethodSourceCode(
-                            factory,
-                            group.getGroupClassType(),
-                            method,
-                            group.getLambdaIdField(factory),
-                            implMethods,
-                            callerPosition)),
-                true));
+        // Merging lambdas can introduce methods with too many instructions for the verifier on
+        // ART to give up statically verifying the method. We therefore split up the implementation
+        // methods and chain them with fallthrough:
+        // function <method>() {
+        //   switch(field.id) {
+        //     case 1:
+        //     case 2:
+        //     ...
+        //     case n:
+        //     default: <method$1>()
+        // }
+        //
+        // function <method$1>() {
+        //     case n + 1:
+        //     case n + 2:
+        //     ...
+        //     case n + m:
+        //     default: throw null
+        // }
+        IntBox counter = new IntBox(0);
+        Box<DexMethod> currentMethodBox =
+            new Box<>(factory.createMethod(group.getGroupClassType(), methodProto, methodName));
+        splitIntoGroupsBasedOnInstructionSize(
+            implMethods,
+            (implMethodsToAdd, methodsSoFar, methodsRemaining) -> {
+              assert currentMethodBox.isSet();
+              // For bridge methods we still use same PUBLIC FINAL as for the main method,
+              // since inlining removes BRIDGE & SYNTHETIC attributes from the bridge methods
+              // anyways and our new method is a product of inlining.
+              MethodAccessFlags accessFlags = MAIN_METHOD_FLAGS.copy();
+              DexMethod method = currentMethodBox.get();
+              DexMethod fallthrough =
+                  methodsRemaining
+                      ? factory.createMethod(
+                          group.getGroupClassType(),
+                          methodProto,
+                          methodName.toString() + "$" + counter.getAndIncrement())
+                      : null;
+              result.add(
+                  new DexEncodedMethod(
+                      method,
+                      accessFlags,
+                      isMainMethod ? id.mainMethodAnnotations : DexAnnotationSet.empty(),
+                      isMainMethod
+                          ? id.mainMethodParamAnnotations
+                          : ParameterAnnotationsList.empty(),
+                      new SynthesizedCode(
+                          callerPosition ->
+                              new KotlinLambdaVirtualMethodSourceCode(
+                                  factory,
+                                  group.getGroupClassType(),
+                                  method,
+                                  group.getLambdaIdField(factory),
+                                  implMethodsToAdd,
+                                  fallthrough,
+                                  methodsSoFar,
+                                  callerPosition)),
+                      true));
+              currentMethodBox.set(fallthrough);
+            });
+        assert !currentMethodBox.isSet();
       }
     }
-
     return result.toArray(DexEncodedMethod.EMPTY_ARRAY);
   }
 
+  private void splitIntoGroupsBasedOnInstructionSize(
+      List<DexEncodedMethod> implMethods,
+      TriConsumer<List<DexEncodedMethod>, Integer, Boolean> consumer) {
+    List<DexEncodedMethod> methods = new ArrayList<>();
+    // Upper bound in DEX for reading the field for switching on the group id.
+    final int fieldLoadInstructionSize = 10;
+    int verificationSizeLimitInBytes = options.verificationSizeLimitInBytes();
+    int currentInstructionsSize = fieldLoadInstructionSize;
+    int implMethodsCommitted = 0;
+    for (DexEncodedMethod implMethod : implMethods) {
+      int packedSwitchPayloadSize =
+          (int)
+              (IntSwitch.basePackedSize(options.getInternalOutputMode())
+                  + IntSwitch.packedPayloadSize(options.getInternalOutputMode(), methods.size()));
+      Code code = implMethod.getCode();
+      // We only do lambda merging for DEX. If we started doing lambda merging for CF, we would
+      // have to compute a size.
+      assert code.isDexCode();
+      int codeSize = code.asDexCode().codeSizeInBytes();
+      int estimatedMethodSize = currentInstructionsSize + codeSize + packedSwitchPayloadSize;
+      if (methods.size() > 0 && estimatedMethodSize > verificationSizeLimitInBytes) {
+        consumer.accept(methods, implMethodsCommitted, true);
+        currentInstructionsSize = fieldLoadInstructionSize;
+        implMethodsCommitted += methods.size();
+        methods = new ArrayList<>();
+      }
+      methods.add(implMethod);
+      currentInstructionsSize += codeSize;
+    }
+    consumer.accept(methods, implMethodsCommitted, false);
+  }
+
   // Build a map of virtual methods with unique name/proto pointing to a list of methods
   // from lambda classes implementing appropriate logic. The indices in the list correspond
   // to lambda ids. Note that some of the slots in the lists may be empty, indicating the
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
index a89ee11..657a796 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
@@ -22,6 +22,8 @@
   private final DexItemFactory factory;
   private final DexField idField;
   private final List<DexEncodedMethod> implMethods;
+  private final DexMethod fallThroughMethod;
+  private final int keyStart;
 
   KotlinLambdaVirtualMethodSourceCode(
       DexItemFactory factory,
@@ -29,11 +31,15 @@
       DexMethod method,
       DexField idField,
       List<DexEncodedMethod> implMethods,
+      DexMethod fallThroughMethod,
+      int keyStart,
       Position callerPosition) {
     super(groupClass, method, callerPosition);
     this.factory = factory;
     this.idField = idField;
     this.implMethods = implMethods;
+    this.fallThroughMethod = fallThroughMethod;
+    this.keyStart = keyStart;
   }
 
   @Override
@@ -64,17 +70,21 @@
     add(builder -> builder.addSwitch(idRegister, keys, fallthrough[0], offsets),
         builder -> endsSwitch(builder, switchIndex, fallthrough[0], offsets));
 
-    // Fallthrough treated as unreachable.
-    fallthrough[0] = nextInstructionIndex();
-    int nullRegister = nextRegister(ValueType.OBJECT);
-    add(builder -> builder.addNullConst(nullRegister));
-    add(builder -> builder.addThrow(nullRegister), endsBlock);
-
     List<Value> arguments = new ArrayList<>(proto.parameters.values.length + 1);
 
+    fallthrough[0] = nextInstructionIndex();
+    if (fallThroughMethod == null) {
+      // Fallthrough treated as unreachable.
+      int nullRegister = nextRegister(ValueType.OBJECT);
+      add(builder -> builder.addNullConst(nullRegister));
+      add(builder -> builder.addThrow(nullRegister), endsBlock);
+    } else {
+      addMethodCall(fallThroughMethod, arguments, returnsValue, retRegister);
+    }
+
     // Blocks for each lambda id.
     for (int i = 0; i < implMethodCount; i++) {
-      keys[i] = i;
+      keys[i] = keyStart + i;
       DexEncodedMethod impl = implMethods.get(i);
       if (impl == null) {
         // Virtual method is missing in lambda class.
@@ -82,28 +92,31 @@
         continue;
       }
       offsets[i] = nextInstructionIndex();
+      addMethodCall(impl.method, arguments, returnsValue, retRegister);
+    }
+  }
 
-      // Emit fake call on `this` receiver.
-      add(
-          builder -> {
-            if (arguments.isEmpty()) {
-              arguments.add(builder.getReceiverValue());
-              List<Value> argumentValues = builder.getArgumentValues();
-              if (argumentValues != null) {
-                arguments.addAll(builder.getArgumentValues());
-              }
+  private void addMethodCall(
+      DexMethod method, List<Value> arguments, boolean returnsValue, int retRegister) {
+    // Emit fake call on `this` receiver.
+    add(
+        builder -> {
+          if (arguments.isEmpty()) {
+            arguments.add(builder.getReceiverValue());
+            List<Value> argumentValues = builder.getArgumentValues();
+            if (argumentValues != null) {
+              arguments.addAll(builder.getArgumentValues());
             }
-            builder.addInvoke(
-                Type.VIRTUAL, impl.method, impl.method.proto, arguments, false /* isInterface */);
-          });
-
-      // Handle return value if needed.
-      if (returnsValue) {
-        add(builder -> builder.addMoveResult(retRegister));
-        add(builder -> builder.addReturn(retRegister), endsBlock);
-      } else {
-        add(IRBuilder::addReturn, endsBlock);
-      }
+          }
+          builder.addInvoke(
+              Invoke.Type.VIRTUAL, method, method.proto, arguments, false /* isInterface */);
+        });
+    // Handle return value if needed.
+    if (returnsValue) {
+      add(builder -> builder.addMoveResult(retRegister));
+      add(builder -> builder.addReturn(retRegister), endsBlock);
+    } else {
+      add(IRBuilder::addReturn, endsBlock);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index a8f3018..09790f1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 
 /**
  * An optimization that merges a method override (B.m()) into the method it overrides (A.m()).
@@ -41,8 +42,8 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final IRConverter converter;
 
-  private final ProgramMethodSet candidatesForInstanceOfOptimization =
-      ProgramMethodSet.createSorted();
+  private final SortedProgramMethodSet candidatesForInstanceOfOptimization =
+      SortedProgramMethodSet.create();
 
   public CheckCastAndInstanceOfMethodSpecialization(
       AppView<AppInfoWithLiveness> appView, IRConverter converter) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 5036205..2c80105 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -32,8 +32,8 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
-import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
@@ -250,7 +250,7 @@
       instructions.add(new CfConstNull());
       instructions.add(new CfReturn(ValueType.OBJECT));
       instructions.add(nullDest);
-      instructions.add(new CfFrame(locals, ImmutableList.of()));
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
 
       // if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField};
       assert reverseWrapperField != null;
@@ -264,7 +264,7 @@
           new CfFieldInstruction(Opcodes.GETFIELD, reverseWrapperField, reverseWrapperField));
       instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
       instructions.add(unwrapDest);
-      instructions.add(new CfFrame(locals, ImmutableList.of()));
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
 
       // return new Wrapper(wrappedValue);
       instructions.add(new CfNew(wrapperField.holder));
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
index e0579ed..7f26a19 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateInterfaceSyntheticCfCodeProvider.java
@@ -22,8 +22,8 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
-import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
@@ -81,7 +81,7 @@
     for (Pair<DexType, DexMethod> dispatch : extraDispatchCases) {
       // Type check basic block.
       instructions.add(labels[nextLabel++]);
-      instructions.add(new CfFrame(locals, ImmutableList.of()));
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
       instructions.add(new CfLoad(ValueType.fromDexType(interfaceType), 0));
       instructions.add(new CfInstanceOf(dispatch.getFirst()));
       instructions.add(new CfIf(If.Type.EQ, ValueType.INT, labels[nextLabel]));
@@ -96,7 +96,7 @@
 
     // Branch with companion call.
     instructions.add(labels[nextLabel]);
-    instructions.add(new CfFrame(locals, ImmutableList.of()));
+    instructions.add(new CfFrame(locals, ImmutableDeque.of()));
     instructions.add(new CfLoad(ValueType.fromDexType(interfaceType), 0));
     loadExtraParameters(instructions);
     instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, companionMethod, false));
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 52cdb30..75358f7 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -33,8 +33,8 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
+import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
-import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
@@ -112,7 +112,7 @@
               addCfInstructionsForAbstractValue(instructions, value, returnType);
               instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
               instructions.add(dest);
-              instructions.add(new CfFrame(locals, ImmutableList.of()));
+              instructions.add(new CfFrame(locals, ImmutableDeque.of()));
             }
           });
 
@@ -176,7 +176,7 @@
           new CfInvoke(Opcodes.INVOKESPECIAL, factory.npeMethods.initWithMessage, false));
       instructions.add(new CfThrow());
       instructions.add(nullDest);
-      instructions.add(new CfFrame(locals, ImmutableList.of()));
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
 
       // if (s.equals("A")) { return 1;}
       // if (s.equals("B")) { return 2;}
@@ -192,7 +192,7 @@
             instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
             instructions.add(new CfReturn(ValueType.INT));
             instructions.add(dest);
-            instructions.add(new CfFrame(locals, ImmutableList.of()));
+            instructions.add(new CfFrame(locals, ImmutableDeque.of()));
           });
 
       // throw new IllegalArgumentException("No enum constant com.x.MyEnum." + s);
@@ -255,7 +255,7 @@
       instructions.add(nullDest);
       instructions.add(
           new CfFrame(
-              ImmutableInt2ReferenceSortedMap.<FrameType>builder().build(), ImmutableList.of()));
+              ImmutableInt2ReferenceSortedMap.<FrameType>builder().build(), ImmutableDeque.of()));
       instructions.add(new CfFieldInstruction(Opcodes.GETSTATIC, utilityField, utilityField));
       instructions.add(new CfReturn(ValueType.OBJECT));
       return standardCfCodeFromInstructions(instructions);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index c7d94d4..3f6569e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -24,7 +24,7 @@
   protected final static Predicate<IRBuilder> doesNotEndBlock = x -> false;
   protected final static Predicate<IRBuilder> endsBlock = x -> true;
 
-  // TODO(b/146124603): Remove these fields as optimzations (e.g., merging) could invalidate them.
+  // TODO(b/146124603): Remove these fields as optimizations (e.g., merging) could invalidate them.
   protected final DexType receiver;
   protected final DexMethod method;
   protected final DexProto proto;
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index e13331f..9ccc19c 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -163,6 +163,15 @@
     String sourceDebug = getSourceDebugExtension(clazz.annotations());
     writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, sourceDebug);
     int version = getClassFileVersion(clazz);
+    if (version >= V1_8) {
+      // JDK8 and after ignore ACC_SUPER so unset it.
+      clazz.accessFlags.unsetSuper();
+    } else {
+      // In all other cases set the super bit as D8/R8 do not support targeting pre 1.0.2 JDKs.
+      if (!clazz.accessFlags.isInterface()) {
+        clazz.accessFlags.setSuper();
+      }
+    }
     int access = clazz.accessFlags.getAsCfAccessFlags();
     String desc = namingLens.lookupDescriptor(clazz.type).toString();
     String name = namingLens.lookupInternalName(clazz.type);
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 060f171..448a8fa 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -441,7 +441,7 @@
     // desugared lambdas this is a conservative estimate, as we don't track if the generated
     // lambda classes survive into the output. As multi-interface lambda expressions are rare
     // this is not a big deal.
-    Set<DexCallSite> liveCallSites = appView.appInfo().callSites;
+    Set<DexCallSite> liveCallSites = appView.appInfo().callSites.keySet();
     // If the input program contains a multi-interface lambda expression that implements
     // interface methods with different protos, we need to make sure tha the implemented lambda
     // methods are renamed to the same name.
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index c19bcd2..038d078 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -379,6 +379,11 @@
     }
 
     @Override
+    public boolean hasCodeRewritings() {
+      return getPrevious().hasCodeRewritings();
+    }
+
+    @Override
     public boolean isLegitimateToHaveEmptyMappings() {
       return true;
     }
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 dcd003c6..b8022b9 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -16,13 +16,14 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BiForEachable;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.Map;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
@@ -127,50 +128,71 @@
     return appView.appInfo().unsafeResolveMethodDueToDexFormat(method).getSingleTarget();
   }
 
+  private void computeMethodRebinding(MethodAccessInfoCollection methodAccessInfoCollection) {
+    // Virtual invokes are on classes, so use class resolution.
+    computeMethodRebinding(
+        methodAccessInfoCollection::forEachVirtualInvoke, this::classLookup, Type.VIRTUAL);
+    // Interface invokes are always on interfaces, so use interface resolution.
+    computeMethodRebinding(
+        methodAccessInfoCollection::forEachInterfaceInvoke, this::interfaceLookup, Type.INTERFACE);
+    // Super invokes can be on both kinds, decide using the holder class.
+    computeMethodRebinding(
+        methodAccessInfoCollection::forEachSuperInvoke, this::anyLookup, Type.SUPER);
+    // Direct invokes (private/constructor) can also be on both kinds.
+    computeMethodRebinding(
+        methodAccessInfoCollection::forEachDirectInvoke, this::anyLookup, Type.DIRECT);
+    // Likewise static invokes.
+    computeMethodRebinding(
+        methodAccessInfoCollection::forEachStaticInvoke, this::anyLookup, Type.STATIC);
+  }
+
   private void computeMethodRebinding(
-      Map<DexMethod, ProgramMethodSet> methodsWithContexts,
+      BiForEachable<DexMethod, ProgramMethodSet> methodsWithContexts,
       Function<DexMethod, DexEncodedMethod> lookupTarget,
       Type invokeType) {
-    for (DexMethod method : methodsWithContexts.keySet()) {
-      // We can safely ignore array types, as the corresponding methods are defined in a library.
-      if (!method.holder.isClassType()) {
-        continue;
-      }
-      DexClass originalClass = appView.definitionFor(method.holder);
-      if (originalClass == null || originalClass.isNotProgramClass()) {
-        continue;
-      }
-      DexEncodedMethod target = lookupTarget.apply(method);
-      // TODO(b/128404854) Rebind to the lowest library class or program class. For now we allow
-      //  searching in library for methods, but this should be done on classpath instead.
-      if (target != null && target.method != method) {
-        DexClass targetClass = appView.definitionFor(target.holder());
-        if (originalClass.isProgramClass()) {
-          // In Java bytecode, it is only possible to target interface methods that are in one of
-          // the immediate super-interfaces via a super-invocation (see IndirectSuperInterfaceTest).
-          // To avoid introducing an IncompatibleClassChangeError at runtime we therefore insert a
-          // bridge method when we are about to rebind to an interface method that is not the
-          // original target.
-          if (needsBridgeForInterfaceMethod(originalClass, targetClass, invokeType)) {
-            target =
-                insertBridgeForInterfaceMethod(
-                    method, target, originalClass.asProgramClass(), targetClass, lookupTarget);
+    methodsWithContexts.forEach(
+        (method, contexts) -> {
+          // We can safely ignore array types, as the corresponding methods are defined in a
+          // library.
+          if (!method.holder.isClassType()) {
+            return;
           }
+          DexClass originalClass = appView.definitionFor(method.holder);
+          if (originalClass == null || originalClass.isNotProgramClass()) {
+            return;
+          }
+          DexEncodedMethod target = lookupTarget.apply(method);
+          // TODO(b/128404854) Rebind to the lowest library class or program class. For now we allow
+          //  searching in library for methods, but this should be done on classpath instead.
+          if (target == null || target.method == method) {
+            return;
+          }
+          DexClass targetClass = appView.definitionFor(target.holder());
+          if (originalClass.isProgramClass()) {
+            // In Java bytecode, it is only possible to target interface methods that are in one of
+            // the immediate super-interfaces via a super-invocation (see
+            // IndirectSuperInterfaceTest).
+            // To avoid introducing an IncompatibleClassChangeError at runtime we therefore insert a
+            // bridge method when we are about to rebind to an interface method that is not the
+            // original target.
+            if (needsBridgeForInterfaceMethod(originalClass, targetClass, invokeType)) {
+              target =
+                  insertBridgeForInterfaceMethod(
+                      method, target, originalClass.asProgramClass(), targetClass, lookupTarget);
+            }
 
-          // If the target class is not public but the targeted method is, we might run into
-          // visibility problems when rebinding.
-          final DexEncodedMethod finalTarget = target;
-          ProgramMethodSet contexts = methodsWithContexts.get(method);
-          if (contexts.stream()
-              .anyMatch(context -> mayNeedBridgeForVisibility(context, finalTarget))) {
-            target =
-                insertBridgeForVisibilityIfNeeded(
-                    method, target, originalClass, targetClass, lookupTarget);
+            // If the target class is not public but the targeted method is, we might run into
+            // visibility problems when rebinding.
+            final DexEncodedMethod finalTarget = target;
+            if (contexts.stream()
+                .anyMatch(context -> mayNeedBridgeForVisibility(context, finalTarget))) {
+              target =
+                  insertBridgeForVisibilityIfNeeded(
+                      method, target, originalClass, targetClass, lookupTarget);
+            }
           }
-        }
-        builder.map(method, lens.lookupMethod(validTargetFor(target.method, method)), invokeType);
-      }
-    }
+          builder.map(method, lens.lookupMethod(validTargetFor(target.method, method)), invokeType);
+        });
   }
 
   private boolean needsBridgeForInterfaceMethod(
@@ -337,16 +359,7 @@
 
   public GraphLens run() {
     AppInfoWithLiveness appInfo = appView.appInfo();
-    // Virtual invokes are on classes, so use class resolution.
-    computeMethodRebinding(appInfo.virtualInvokes, this::classLookup, Type.VIRTUAL);
-    // Interface invokes are always on interfaces, so use interface resolution.
-    computeMethodRebinding(appInfo.interfaceInvokes, this::interfaceLookup, Type.INTERFACE);
-    // Super invokes can be on both kinds, decide using the holder class.
-    computeMethodRebinding(appInfo.superInvokes, this::anyLookup, Type.SUPER);
-    // Direct invokes (private/constructor) can also be on both kinds.
-    computeMethodRebinding(appInfo.directInvokes, this::anyLookup, Type.DIRECT);
-    // Likewise static invokes.
-    computeMethodRebinding(appInfo.staticInvokes, this::anyLookup, Type.STATIC);
+    computeMethodRebinding(appInfo.getMethodAccessInfoCollection());
     computeFieldRebinding();
     GraphLens lens = builder.build(this.lens);
     appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
new file mode 100644
index 0000000..9e3cbce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -0,0 +1,170 @@
+// 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.DexClass;
+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.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * This lens is used to populate the rebound field and method reference during lookup, such that
+ * both the non-rebound and rebound field and method references are available to all descendants of
+ * this lens.
+ */
+public class MemberRebindingIdentityLens extends NonIdentityGraphLens {
+
+  private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap;
+  private final Map<DexMethod, DexMethod> nonReboundMethodReferenceToDefinitionMap;
+
+  private MemberRebindingIdentityLens(
+      Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap,
+      Map<DexMethod, DexMethod> nonReboundMethodReferenceToDefinitionMap,
+      DexItemFactory dexItemFactory,
+      GraphLens previousLens) {
+    super(dexItemFactory, previousLens);
+    assert !previousLens.hasCodeRewritings();
+    this.nonReboundFieldReferenceToDefinitionMap = nonReboundFieldReferenceToDefinitionMap;
+    this.nonReboundMethodReferenceToDefinitionMap = nonReboundMethodReferenceToDefinitionMap;
+  }
+
+  public static Builder builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return new Builder(appView);
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    assert previous.getReboundReference() == null;
+    return FieldLookupResult.builder(this)
+        .setReference(previous.getReference())
+        .setReboundReference(getReboundFieldReference(previous.getReference()))
+        .build();
+  }
+
+  private DexField getReboundFieldReference(DexField field) {
+    return nonReboundFieldReferenceToDefinitionMap.getOrDefault(field, field);
+  }
+
+  @Override
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    assert previous.getReboundReference() == null;
+    return MethodLookupResult.builder(this)
+        .setReference(previous.getReference())
+        .setReboundReference(getReboundMethodReference(previous.getReference()))
+        .setPrototypeChanges(previous.getPrototypeChanges())
+        .setType(previous.getType())
+        .build();
+  }
+
+  private DexMethod getReboundMethodReference(DexMethod method) {
+    return nonReboundMethodReferenceToDefinitionMap.getOrDefault(method, method);
+  }
+
+  @Override
+  public DexType getOriginalType(DexType type) {
+    return getPrevious().getOriginalType(type);
+  }
+
+  @Override
+  public DexField getOriginalFieldSignature(DexField field) {
+    return getPrevious().getOriginalFieldSignature(field);
+  }
+
+  @Override
+  public DexMethod getOriginalMethodSignature(DexMethod method) {
+    return getPrevious().getOriginalMethodSignature(method);
+  }
+
+  @Override
+  public DexField getRenamedFieldSignature(DexField originalField) {
+    return getPrevious().getRenamedFieldSignature(originalField);
+  }
+
+  @Override
+  public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+    return getPrevious().getRenamedMethodSignature(originalMethod, applied);
+  }
+
+  @Override
+  public final DexType internalDescribeLookupClassType(DexType previous) {
+    return previous;
+  }
+
+  @Override
+  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    return method;
+  }
+
+  @Override
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method);
+  }
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return getPrevious().isContextFreeForMethods();
+  }
+
+  public static class Builder {
+
+    private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap =
+        new IdentityHashMap<>();
+    private final Map<DexMethod, DexMethod> nonReboundMethodReferenceToDefinitionMap =
+        new IdentityHashMap<>();
+
+    private Builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
+      this.appView = appView;
+    }
+
+    void recordNonReboundFieldAccesses(FieldAccessInfo fieldAccessInfo) {
+      fieldAccessInfo.forEachIndirectAccess(
+          nonReboundFieldReference ->
+              recordNonReboundFieldAccess(nonReboundFieldReference, fieldAccessInfo.getField()));
+    }
+
+    private void recordNonReboundFieldAccess(
+        DexField nonReboundFieldReference, DexField reboundFieldReference) {
+      nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
+    }
+
+    void recordMethodAccess(DexMethod reference) {
+      if (reference.getHolderType().isArrayType()) {
+        return;
+      }
+      DexClass holder = appView.contextIndependentDefinitionFor(reference.getHolderType());
+      if (holder != null) {
+        SingleResolutionResult resolutionResult =
+            appView.appInfo().resolveMethodOn(holder, reference).asSingleResolution();
+        if (resolutionResult != null && resolutionResult.getResolvedHolder() != holder) {
+          nonReboundMethodReferenceToDefinitionMap.put(
+              reference, resolutionResult.getResolvedMethod().getReference());
+        }
+      }
+    }
+
+    MemberRebindingIdentityLens build() {
+      // This intentionally does not return null when the maps are empty. In this case there are no
+      // non-rebound field or method references, but the member rebinding lens is still needed to
+      // populate the rebound reference during field and method lookup.
+      return new MemberRebindingIdentityLens(
+          nonReboundFieldReferenceToDefinitionMap,
+          nonReboundMethodReferenceToDefinitionMap,
+          appView.dexItemFactory(),
+          appView.graphLens());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
new file mode 100644
index 0000000..944af37
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
@@ -0,0 +1,249 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize;
+
+import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
+import com.android.tools.r8.graph.FieldAccessInfoImpl;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
+import com.android.tools.r8.graph.MethodAccessInfoCollection;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Sets;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class MemberRebindingIdentityLensFactory {
+
+  /**
+   * 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 {
+    FieldAccessInfoCollection<?> fieldAccessInfoCollection;
+    MethodAccessInfoCollection methodAccessInfoCollection;
+    if (appView.appInfo().hasLiveness()
+        && appView.options().testing.alwaysUseExistingAccessInfoCollectionsInMemberRebinding) {
+      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+      fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
+      methodAccessInfoCollection = appInfo.getMethodAccessInfoCollection();
+    } else {
+      FieldAccessInfoCollectionImpl mutableFieldAccessInfoCollection =
+          new FieldAccessInfoCollectionImpl(new ConcurrentHashMap<>());
+      MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder =
+          MethodAccessInfoCollection.concurrentBuilder();
+      initializeMemberAccessInfoCollectionsForMemberRebinding(
+          appView,
+          mutableFieldAccessInfoCollection,
+          methodAccessInfoCollectionBuilder,
+          executorService);
+      fieldAccessInfoCollection = mutableFieldAccessInfoCollection;
+      methodAccessInfoCollection = methodAccessInfoCollectionBuilder.build();
+    }
+    return create(appView, fieldAccessInfoCollection, methodAccessInfoCollection);
+  }
+
+  public static MemberRebindingIdentityLens create(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      FieldAccessInfoCollection<?> fieldAccessInfoCollection,
+      MethodAccessInfoCollection methodAccessInfoCollection) {
+    MemberRebindingIdentityLens.Builder builder = MemberRebindingIdentityLens.builder(appView);
+    fieldAccessInfoCollection.forEach(builder::recordNonReboundFieldAccesses);
+    methodAccessInfoCollection.forEachMethodReference(builder::recordMethodAccess);
+    return builder.build();
+  }
+
+  /**
+   * Applies {@link NonReboundMemberReferencesRegistry} to all code objects to construct a mapping
+   * from non-rebound field references to their definition.
+   */
+  private static void initializeMemberAccessInfoCollectionsForMemberRebinding(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
+      MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder,
+      ExecutorService executorService)
+      throws ExecutionException {
+    Set<DexField> seenFieldReferences = Sets.newConcurrentHashSet();
+    Set<DexMethod> seenMethodReferences = Sets.newConcurrentHashSet();
+    ThreadUtils.processItems(
+        appView.appInfo()::forEachMethod,
+        method ->
+            new NonReboundMemberReferencesRegistry(
+                    appView,
+                    method,
+                    fieldAccessInfoCollection,
+                    methodAccessInfoCollectionBuilder,
+                    seenFieldReferences,
+                    seenMethodReferences)
+                .accept(method),
+        executorService);
+  }
+
+  private static class NonReboundMemberReferencesRegistry extends UseRegistry {
+
+    private final AppInfoWithClassHierarchy appInfo;
+    private final ProgramMethod context;
+    private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
+    private final MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder;
+    private final Set<DexField> seenFieldReferences;
+    private final Set<DexMethod> seenMethodReferences;
+
+    public NonReboundMemberReferencesRegistry(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        ProgramMethod context,
+        FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
+        MethodAccessInfoCollection.ConcurrentBuilder methodAccessInfoCollectionBuilder,
+        Set<DexField> seenFieldReferences,
+        Set<DexMethod> seenMethodReferences) {
+      super(appView.dexItemFactory());
+      this.appInfo = appView.appInfo();
+      this.context = context;
+      this.fieldAccessInfoCollection = fieldAccessInfoCollection;
+      this.methodAccessInfoCollectionBuilder = methodAccessInfoCollectionBuilder;
+      this.seenFieldReferences = seenFieldReferences;
+      this.seenMethodReferences = seenMethodReferences;
+    }
+
+    @Override
+    public void registerInstanceFieldRead(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    @Override
+    public void registerInstanceFieldWrite(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    @Override
+    public void registerStaticFieldRead(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    @Override
+    public void registerStaticFieldWrite(DexField field) {
+      registerFieldAccess(field);
+    }
+
+    private void registerFieldAccess(DexField field) {
+      if (!seenFieldReferences.add(field)) {
+        return;
+      }
+      SuccessfulFieldResolutionResult resolutionResult =
+          appInfo.resolveField(field).asSuccessfulResolution();
+      if (resolutionResult == null) {
+        return;
+      }
+      DexField reboundReference = resolutionResult.getResolvedField().toReference();
+      if (field == reboundReference) {
+        // For the purpose of member rebinding, we don't care about already rebound references.
+        return;
+      }
+      FieldAccessInfoImpl fieldAccessInfo =
+          fieldAccessInfoCollection.computeIfAbsent(reboundReference, FieldAccessInfoImpl::new);
+      synchronized (fieldAccessInfo) {
+        // Record the fact that there is a non-rebound access to the given field. We don't
+        // distinguish between non-rebound reads and writes, so we just record it as a read.
+        ConcreteAccessContexts accessContexts =
+            fieldAccessInfo.getReadsWithContexts().isConcrete()
+                ? fieldAccessInfo.getReadsWithContexts().asConcrete()
+                : new ConcreteAccessContexts();
+        // For the purpose of member rebinding, we don't care about the access contexts, so we
+        // simply use the empty set.
+        accessContexts.getAccessesWithContexts().put(field, ProgramMethodSet.empty());
+      }
+    }
+
+    @Override
+    public void registerInvokeDirect(DexMethod method) {
+      registerInvokeMethod(method, methodAccessInfoCollectionBuilder.getDirectInvokes());
+    }
+
+    @Override
+    public void registerInvokeInterface(DexMethod method) {
+      registerInvokeMethod(method, methodAccessInfoCollectionBuilder.getInterfaceInvokes());
+    }
+
+    @Override
+    public void registerInvokeStatic(DexMethod method) {
+      registerInvokeMethod(method, methodAccessInfoCollectionBuilder.getStaticInvokes());
+    }
+
+    @Override
+    public void registerInvokeSuper(DexMethod method) {
+      registerInvokeMethod(method, methodAccessInfoCollectionBuilder.getSuperInvokes());
+    }
+
+    @Override
+    public void registerInvokeVirtual(DexMethod method) {
+      registerInvokeMethod(method, methodAccessInfoCollectionBuilder.getVirtualInvokes());
+    }
+
+    private void registerInvokeMethod(DexMethod method, Map<DexMethod, ProgramMethodSet> invokes) {
+      if (!seenMethodReferences.add(method)) {
+        return;
+      }
+      if (method.getHolderType().isArrayType()) {
+        return;
+      }
+      DexClass holder = appInfo.definitionFor(method.getHolderType(), context);
+      if (holder == null) {
+        return;
+      }
+      SingleResolutionResult resolutionResult =
+          appInfo.resolveMethodOn(holder, method).asSingleResolution();
+      if (resolutionResult == null) {
+        return;
+      }
+      DexMethod reboundReference = resolutionResult.getResolvedMethod().getReference();
+      if (method == reboundReference) {
+        // For the purpose of member rebinding, we don't care about already rebound references.
+        return;
+      }
+      // For the purpose of member rebinding, we don't care about the access contexts, so we
+      // simply use the empty set.
+      invokes.put(method, ProgramMethodSet.empty());
+    }
+
+    @Override
+    public void registerInitClass(DexType type) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void registerNewInstance(DexType type) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void registerTypeReference(DexType type) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void registerInstanceOf(DexType type) {
+      // Intentionally empty.
+    }
+  }
+}
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 2e55916..a6a6021 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -86,17 +86,19 @@
   }
 
   @Override
-  public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
-    GraphLensLookupResult lookup = previousLens.lookupMethod(method, context, type);
-    Map<DexMethod, DexMethod> methodMap = methodMaps.getOrDefault(type, Collections.emptyMap());
-    DexMethod newMethod = methodMap.get(lookup.getMethod());
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    Map<DexMethod, DexMethod> methodMap =
+        methodMaps.getOrDefault(previous.getType(), Collections.emptyMap());
+    DexMethod newMethod = methodMap.get(previous.getReference());
     if (newMethod == null) {
-      return lookup;
+      return previous;
     }
-    return new GraphLensLookupResult(
-        newMethod,
-        mapInvocationType(newMethod, method, lookup.getType()),
-        lookup.getPrototypeChanges());
+    return MethodLookupResult.builder(this)
+        .setReference(newMethod)
+        .setPrototypeChanges(previous.getPrototypeChanges())
+        .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
+        .build();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
index 1e8eff2..d36cbee 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
@@ -40,19 +40,22 @@
   }
 
   @Override
-  public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
-    GraphLensLookupResult lookup = previousLens.lookupMethod(method, context, type);
-    if (lookup.getType() == Type.DIRECT && publicizedMethods.contains(lookup.getMethod())) {
-      assert publicizedMethodIsPresentOnHolder(lookup.getMethod(), context);
-      return new GraphLensLookupResult(
-          lookup.getMethod(), Type.VIRTUAL, lookup.getPrototypeChanges());
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    if (previous.getType() == Type.DIRECT && publicizedMethods.contains(previous.getReference())) {
+      assert publicizedMethodIsPresentOnHolder(previous.getReference(), context);
+      return MethodLookupResult.builder(this)
+          .setReference(previous.getReference())
+          .setPrototypeChanges(previous.getPrototypeChanges())
+          .setType(Type.VIRTUAL)
+          .build();
     }
-    return lookup;
+    return previous;
   }
 
   private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexMethod context) {
-    GraphLensLookupResult lookup = appView.graphLens().lookupMethod(method, context, Type.VIRTUAL);
-    DexMethod signatureInCurrentWorld = lookup.getMethod();
+    MethodLookupResult lookup = appView.graphLens().lookupMethod(method, context, Type.VIRTUAL);
+    DexMethod signatureInCurrentWorld = lookup.getReference();
     DexClass clazz = appView.definitionFor(signatureInCurrentWorld.holder);
     assert clazz != null;
     DexEncodedMethod actualEncodedTarget = clazz.lookupVirtualMethod(signatureInCurrentWorld);
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
index b8355c3..7da1116 100644
--- a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
@@ -157,8 +157,6 @@
     options.dataResourceConsumer = consumer.getDataResourceConsumer();
     // Set debug to ensure that we are writing all information to the application writer.
     options.debug = true;
-    // We need to read stack maps since we are not processing anything.
-    options.testing.readInputStackMaps = true;
     return options;
   }
 
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 848008c..5868aa8 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -4,19 +4,27 @@
 
 package com.android.tools.r8.repackaging;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.DescriptorUtils.INNER_CLASS_SEPARATOR;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ProgramPackage;
 import com.android.tools.r8.graph.ProgramPackageCollection;
+import com.android.tools.r8.graph.SortedProgramPackageCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
-import java.util.IdentityHashMap;
-import java.util.Map;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -60,29 +68,92 @@
       return null;
     }
 
-    // For each package, find the set of classes that can be repackaged, and move them to the
-    // desired namespace.
-    Map<DexType, DexType> mappings = new IdentityHashMap<>();
-    for (ProgramPackage pkg : ProgramPackageCollection.createWithAllProgramClasses(appView)) {
-      Iterable<DexProgramClass> classesToRepackage =
-          computeClassesToRepackage(pkg, executorService);
-      String newPackageDescriptor = getNewPackageDescriptor(pkg);
-      for (DexProgramClass classToRepackage : classesToRepackage) {
-        // TODO(b/165783399): Handle class collisions when different packages are repackaged into
-        //  the same package.
-        DexType newType =
-            classToRepackage.getType().replacePackage(newPackageDescriptor, dexItemFactory);
-        mappings.put(classToRepackage.getType(), newType);
-      }
-      // TODO(b/165783399): Investigate if repackaging can lead to different dynamic dispatch. See,
-      //  for example, CrossPackageInvokeSuperToPackagePrivateMethodTest.
-    }
+    BiMap<DexType, DexType> mappings = HashBiMap.create();
+    Set<String> seenPackageDescriptors = new HashSet<>();
+    ProgramPackageCollection packages =
+        SortedProgramPackageCollection.createWithAllProgramClasses(appView);
+    processPackagesInDesiredLocation(packages, mappings, seenPackageDescriptors);
+    processRemainingPackages(packages, mappings, seenPackageDescriptors, executorService);
+    mappings.entrySet().removeIf(entry -> entry.getKey() == entry.getValue());
     if (mappings.isEmpty()) {
       return null;
     }
     return new RepackagingTreeFixer(appBuilder, appView, mappings).run();
   }
 
+  private void processPackagesInDesiredLocation(
+      ProgramPackageCollection packages,
+      BiMap<DexType, DexType> mappings,
+      Set<String> seenPackageDescriptors) {
+    // For each package that is already in the desired location, record all the classes from the
+    // package in the mapping for collision detection.
+    Iterator<ProgramPackage> iterator = packages.iterator();
+    while (iterator.hasNext()) {
+      ProgramPackage pkg = iterator.next();
+      String newPackageDescriptor = getNewPackageDescriptor(pkg, seenPackageDescriptors);
+      if (pkg.getPackageDescriptor().equals(newPackageDescriptor)) {
+        for (DexProgramClass alreadyRepackagedClass : pkg) {
+          mappings.put(alreadyRepackagedClass.getType(), alreadyRepackagedClass.getType());
+        }
+        seenPackageDescriptors.add(newPackageDescriptor);
+        iterator.remove();
+      }
+    }
+  }
+
+  private void processRemainingPackages(
+      ProgramPackageCollection packages,
+      BiMap<DexType, DexType> mappings,
+      Set<String> seenPackageDescriptors,
+      ExecutorService executorService)
+      throws ExecutionException {
+    // For each package, find the set of classes that can be repackaged, and move them to the
+    // desired package.
+    for (ProgramPackage pkg : packages) {
+      // Already processed packages should have been removed.
+      String newPackageDescriptor = getNewPackageDescriptor(pkg, seenPackageDescriptors);
+      assert !pkg.getPackageDescriptor().equals(newPackageDescriptor);
+
+      Iterable<DexProgramClass> classesToRepackage =
+          computeClassesToRepackage(pkg, executorService);
+      for (DexProgramClass classToRepackage : classesToRepackage) {
+        processClass(classToRepackage, pkg, newPackageDescriptor, mappings);
+      }
+
+      seenPackageDescriptors.add(newPackageDescriptor);
+      // TODO(b/165783399): Investigate if repackaging can lead to different dynamic dispatch. See,
+      //  for example, CrossPackageInvokeSuperToPackagePrivateMethodTest.
+    }
+  }
+
+  private void processClass(
+      DexProgramClass classToRepackage,
+      ProgramPackage pkg,
+      String newPackageDescriptor,
+      BiMap<DexType, DexType> mappings) {
+    // Check if the class has already been processed.
+    if (mappings.containsKey(classToRepackage.getType())) {
+      return;
+    }
+
+    // Always repackage outer classes first, if any.
+    InnerClassAttribute innerClassAttribute = classToRepackage.getInnerClassAttributeForThisClass();
+    DexProgramClass outerClass = null;
+    if (innerClassAttribute != null && innerClassAttribute.getOuter() != null) {
+      outerClass = asProgramClassOrNull(appView.definitionFor(innerClassAttribute.getOuter()));
+      if (outerClass != null) {
+        if (pkg.contains(outerClass)) {
+          processClass(outerClass, pkg, newPackageDescriptor, mappings);
+        } else {
+          outerClass = null;
+        }
+      }
+    }
+    mappings.put(
+        classToRepackage.getType(),
+        getRepackagedType(classToRepackage, outerClass, newPackageDescriptor, mappings));
+  }
+
   private Iterable<DexProgramClass> computeClassesToRepackage(
       ProgramPackage pkg, ExecutorService executorService) throws ExecutionException {
     RepackagingConstraintGraph constraintGraph = new RepackagingConstraintGraph(appView, pkg);
@@ -94,13 +165,46 @@
     return constraintGraph.computeClassesToRepackage();
   }
 
-  private String getNewPackageDescriptor(ProgramPackage pkg) {
+  private String getNewPackageDescriptor(ProgramPackage pkg, Set<String> seenPackageDescriptors) {
     String newPackageDescriptor =
         StringUtils.replaceAll(proguardConfiguration.getPackagePrefix(), ".", "/");
-    if (proguardConfiguration.getPackageObfuscationMode().isFlattenPackageHierarchy()) {
-      // TODO(b/165783399): Handle collisions among package names.
-      newPackageDescriptor += "/" + pkg.getLastPackageName();
+    if (proguardConfiguration.getPackageObfuscationMode().isRepackageClasses()) {
+      return newPackageDescriptor;
     }
-    return newPackageDescriptor;
+    if (!newPackageDescriptor.isEmpty()) {
+      newPackageDescriptor += "/";
+    }
+    newPackageDescriptor += pkg.getLastPackageName();
+    String finalPackageDescriptor = newPackageDescriptor;
+    for (int i = 1; seenPackageDescriptors.contains(finalPackageDescriptor); i++) {
+      finalPackageDescriptor = newPackageDescriptor + INNER_CLASS_SEPARATOR + i;
+    }
+    return finalPackageDescriptor;
+  }
+
+  private DexType getRepackagedType(
+      DexProgramClass clazz,
+      DexProgramClass outerClass,
+      String newPackageDescriptor,
+      BiMap<DexType, DexType> mappings) {
+    DexType repackagedDexType =
+        clazz.getType().replacePackage(newPackageDescriptor, dexItemFactory);
+    if (outerClass != null) {
+      String simpleName = clazz.getType().getSimpleName();
+      String outerClassSimpleName = outerClass.getType().getSimpleName();
+      if (simpleName.startsWith(outerClassSimpleName + INNER_CLASS_SEPARATOR)) {
+        String newSimpleName =
+            mappings.get(outerClass.getType()).getSimpleName()
+                + simpleName.substring(outerClassSimpleName.length());
+        repackagedDexType = repackagedDexType.withSimpleName(newSimpleName, dexItemFactory);
+      }
+    }
+    DexType finalRepackagedDexType = repackagedDexType;
+    for (int i = 1; mappings.inverse().containsKey(finalRepackagedDexType); i++) {
+      finalRepackagedDexType =
+          repackagedDexType.addSuffix(
+              Character.toString(INNER_CLASS_SEPARATOR) + i, dexItemFactory);
+    }
+    return finalRepackagedDexType;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingAnnotationTracer.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingAnnotationTracer.java
new file mode 100644
index 0000000..f52b8b7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingAnnotationTracer.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackaging;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+
+public class RepackagingAnnotationTracer {
+
+  private final AppInfoWithClassHierarchy appInfo;
+  private final RepackagingUseRegistry registry;
+
+  public RepackagingAnnotationTracer(
+      AppView<? extends AppInfoWithClassHierarchy> appView, RepackagingUseRegistry registry) {
+    this.appInfo = appView.appInfo();
+    this.registry = registry;
+  }
+
+  public void trace(DexAnnotationSet annotations) {
+    annotations.forEach(this::traceAnnotation);
+  }
+
+  public void trace(ParameterAnnotationsList annotations) {
+    annotations.forEachAnnotation(this::traceAnnotation);
+  }
+
+  private void traceAnnotation(DexAnnotation annotation) {
+    traceEncodedAnnotation(annotation.annotation);
+  }
+
+  private void traceEncodedAnnotation(DexEncodedAnnotation annotation) {
+    registry.registerTypeReference(annotation.type);
+    annotation.forEachElement(this::traceAnnotationElement);
+  }
+
+  private void traceAnnotationElement(DexAnnotationElement element) {
+    traceDexValue(element.value);
+  }
+
+  private void traceDexValue(DexValue value) {
+    switch (value.getValueKind()) {
+      case BOOLEAN:
+      case BYTE:
+      case CHAR:
+      case DOUBLE:
+      case FLOAT:
+      case INT:
+      case LONG:
+      case NULL:
+      case SHORT:
+      case STRING:
+        break;
+
+      case ANNOTATION:
+        traceEncodedAnnotation(value.asDexValueAnnotation().getValue());
+        break;
+
+      case ARRAY:
+        value.asDexValueArray().forEachElement(this::traceDexValue);
+        break;
+
+      case ENUM:
+        registry.registerFieldAccess(value.asDexValueEnum().getValue());
+        break;
+
+      case FIELD:
+        registry.registerFieldAccess(value.asDexValueField().getValue());
+        break;
+
+      case METHOD:
+        registry.registerMethodReference(value.asDexValueMethod().getValue());
+        break;
+
+      case METHOD_HANDLE:
+        {
+          DexMethodHandle handle = value.asDexValueMethodHandle().getValue();
+          if (handle.isFieldHandle()) {
+            registry.registerFieldAccess(handle.asField());
+          } else {
+            assert handle.isMethodHandle();
+            registry.registerMethodReference(handle.asMethod());
+          }
+        }
+        break;
+
+      case METHOD_TYPE:
+        value.asDexValueMethodType().getValue().forEachType(registry::registerTypeReference);
+        break;
+
+      case TYPE:
+        registry.registerTypeReference(value.asDexValueType().getValue());
+        break;
+
+      default:
+        throw new Unreachable();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
index 33bbc11..b50015b 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ProgramPackage;
@@ -68,7 +69,7 @@
   }
 
   private Node createNode(DexDefinition definition) {
-    Node node = new Node(definition);
+    Node node = new Node();
     nodes.put(definition, node);
     return node;
   }
@@ -89,23 +90,60 @@
   }
 
   private void registerReferencesFromClass(DexProgramClass clazz) {
-    // TODO(b/165783399): Trace the references to the immediate super types.
-    // TODO(b/165783399): Maybe trace the references in the nest host and/or members.
-    // TODO(b/165783399): Maybe trace the references to the inner classes.
-    // TODO(b/165783399): Maybe trace the references in @kotlin.Metadata.
+    RepackagingUseRegistry registry = new RepackagingUseRegistry(appView, this, clazz);
+
+    // Trace the references to the immediate super types.
+    registry.registerTypeReference(clazz.getSuperType());
+    clazz.interfaces.forEach(registry::registerTypeReference);
+
+    // Trace the references from the class annotations.
+    new RepackagingAnnotationTracer(appView, registry).trace(clazz.annotations());
+
+    // Trace the references in the nest host and/or members.
+    if (clazz.isInANest()) {
+      if (clazz.isNestHost()) {
+        clazz.forEachNestMember(registry::registerTypeReference);
+      } else {
+        assert clazz.isNestMember();
+        registry.registerTypeReference(clazz.getNestHost());
+      }
+    }
+
+    // Trace the references to the inner and outer classes.
+    clazz.getInnerClasses().forEach(registry::registerInnerClassAttribute);
+
+    // Trace the references from the enclosing method attribute.
+    EnclosingMethodAttribute attr = clazz.getEnclosingMethodAttribute();
+    if (attr != null) {
+      registry.registerEnclosingMethodAttribute(attr);
+    }
   }
 
   private void registerReferencesFromField(ProgramField field) {
-    // TODO(b/165783399): Trace the type of the field.
-    // TODO(b/165783399): Trace the references in the field annotations.
+    RepackagingUseRegistry registry = new RepackagingUseRegistry(appView, this, field);
+
+    // Trace the type of the field.
+    registry.registerTypeReference(field.getReference().getType());
+
+    // Trace the references in the field annotations.
+    new RepackagingAnnotationTracer(appView, registry).trace(field.getDefinition().annotations());
   }
 
   private void registerReferencesFromMethod(ProgramMethod method) {
-    // TODO(b/165783399): Trace the type references in the method signature.
-    // TODO(b/165783399): Trace the references in the method and method parameter annotations.
     DexEncodedMethod definition = method.getDefinition();
+    RepackagingUseRegistry registry = new RepackagingUseRegistry(appView, this, method);
+
+    // Trace the type references in the method signature.
+    definition.getProto().forEachType(registry::registerTypeReference);
+
+    // Trace the references in the method and method parameter annotations.
+    RepackagingAnnotationTracer annotationTracer =
+        new RepackagingAnnotationTracer(appView, registry);
+    annotationTracer.trace(definition.annotations());
+    annotationTracer.trace(definition.getParameterAnnotations());
+
+    // Trace the references from the code.
     if (definition.hasCode()) {
-      RepackagingUseRegistry registry = new RepackagingUseRegistry(appView, this, method);
       definition.getCode().registerCodeReferences(method, registry);
     }
   }
@@ -127,14 +165,8 @@
 
   static class Node {
 
-    private final DexDefinition definition;
-
     private final Set<Node> neighbors = Sets.newConcurrentHashSet();
 
-    private Node(DexDefinition definition) {
-      this.definition = definition;
-    }
-
     public void addNeighbor(Node neighbor) {
       neighbors.add(neighbor);
       neighbor.neighbors.add(this);
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
index 7c19b0b..a0f6645 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -36,7 +36,7 @@
   @Override
   public DexType getOriginalType(DexType type) {
     DexType previous = originalTypes.getOrDefault(type, type);
-    return previousLens.getOriginalType(previous);
+    return getPrevious().getOriginalType(previous);
   }
 
   public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
index 3591ff7..f519f01 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
@@ -154,9 +154,9 @@
     List<InnerClassAttribute> newInnerClassAttributes = new ArrayList<>();
     for (InnerClassAttribute innerClassAttribute : innerClassAttributes) {
       DexType innerClassType = innerClassAttribute.getInner();
-      DexType newInnerClassType = fixupType(innerClassType);
+      DexType newInnerClassType = fixupTypeOrNull(innerClassType);
       DexType outerClassType = innerClassAttribute.getOuter();
-      DexType newOuterClassType = fixupType(outerClassType);
+      DexType newOuterClassType = fixupTypeOrNull(outerClassType);
       newInnerClassAttributes.add(
           new InnerClassAttribute(
               innerClassAttribute.getAccess(),
@@ -247,6 +247,10 @@
     return changed ? newSynthesizedFrom : synthesizedFrom;
   }
 
+  private DexType fixupTypeOrNull(DexType type) {
+    return type != null ? fixupType(type) : null;
+  }
+
   private DexType fixupType(DexType type) {
     if (type.isArrayType()) {
       DexType base = type.toBaseType(dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index 531e6c8..b27182d 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.repackaging;
 
+import static com.google.common.base.Predicates.alwaysTrue;
+
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
@@ -12,24 +14,30 @@
 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.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MemberResolutionResult;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.SuccessfulMemberResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class RepackagingUseRegistry extends UseRegistry {
 
   private final AppInfoWithLiveness appInfo;
   private final RepackagingConstraintGraph constraintGraph;
-  private final ProgramMethod context;
+  private final ProgramDefinition context;
   private final RepackagingConstraintGraph.Node node;
 
   public RepackagingUseRegistry(
       AppView<AppInfoWithLiveness> appView,
       RepackagingConstraintGraph constraintGraph,
-      ProgramMethod context) {
+      ProgramDefinition context) {
     super(appView.dexItemFactory());
     this.appInfo = appView.appInfo();
     this.constraintGraph = constraintGraph;
@@ -43,7 +51,7 @@
       return true;
     }
     if (accessFlags.isProtected()
-        && !appInfo.isSubtype(context.getHolderType(), referencedClass.getType())) {
+        && !appInfo.isSubtype(context.getContextType(), referencedClass.getType())) {
       return true;
     }
     return false;
@@ -55,13 +63,25 @@
       return true;
     }
     if (accessFlags.isProtected()
-        && !appInfo.isSubtype(context.getHolderType(), member.getHolderType())) {
+        && !appInfo.isSubtype(context.getContextType(), member.getHolderType())) {
       return true;
     }
     return false;
   }
 
-  private void registerMemberAccess(MemberResolutionResult<?, ?> resolutionResult) {
+  public void registerFieldAccess(DexField field) {
+    registerMemberAccess(appInfo.resolveField(field));
+  }
+
+  public ProgramMethod registerMethodReference(DexMethod method) {
+    ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
+    registerMemberAccess(resolutionResult);
+    return resolutionResult.isSingleResolution()
+        ? resolutionResult.asSingleResolution().getResolvedProgramMethod()
+        : null;
+  }
+
+  public void registerMemberAccess(MemberResolutionResult<?, ?> resolutionResult) {
     SuccessfulMemberResolutionResult<?, ?> successfulResolutionResult =
         resolutionResult.asSuccessfulMemberResolutionResult();
     if (successfulResolutionResult == null) {
@@ -88,28 +108,36 @@
   }
 
   private void registerTypeAccess(DexType type) {
+    registerTypeAccess(type, this::registerTypeAccess);
+  }
+
+  private void registerTypeAccess(DexType type, Consumer<DexClass> consumer) {
     if (type.isArrayType()) {
-      registerTypeAccess(type.toBaseType(appInfo.dexItemFactory()));
+      registerTypeAccess(type.toBaseType(appInfo.dexItemFactory()), consumer);
       return;
     }
-    if (type.isPrimitiveType()) {
+    if (type.isPrimitiveType() || type.isVoidType()) {
       return;
     }
     assert type.isClassType();
     DexClass clazz = appInfo.definitionFor(type);
     if (clazz != null) {
-      registerTypeAccess(clazz);
+      consumer.accept(clazz);
     }
   }
 
   private void registerTypeAccess(DexClass clazz) {
+    registerTypeAccess(clazz, this::isOnlyAccessibleFromSamePackage);
+  }
+
+  private void registerTypeAccess(DexClass clazz, Predicate<DexProgramClass> predicate) {
     // We only want to connect the current method node to the class node if the access requires the
     // two nodes to be in the same package. Therefore, we ignore accesses to non-program classes
     // and program classes outside the current package.
     DexProgramClass programClass = clazz.asProgramClass();
     if (programClass != null) {
       RepackagingConstraintGraph.Node classNode = constraintGraph.getNode(programClass);
-      if (classNode != null && isOnlyAccessibleFromSamePackage(programClass)) {
+      if (classNode != null && predicate.test(programClass)) {
         node.addNeighbor(classNode);
       }
     }
@@ -147,12 +175,12 @@
 
   @Override
   public void registerInstanceFieldRead(DexField field) {
-    registerMemberAccess(appInfo.resolveField(field));
+    registerFieldAccess(field);
   }
 
   @Override
   public void registerInstanceFieldWrite(DexField field) {
-    registerMemberAccess(appInfo.resolveField(field));
+    registerFieldAccess(field);
   }
 
   @Override
@@ -162,12 +190,12 @@
 
   @Override
   public void registerStaticFieldRead(DexField field) {
-    registerMemberAccess(appInfo.resolveField(field));
+    registerFieldAccess(field);
   }
 
   @Override
   public void registerStaticFieldWrite(DexField field) {
-    registerMemberAccess(appInfo.resolveField(field));
+    registerFieldAccess(field);
   }
 
   @Override
@@ -179,4 +207,29 @@
   public void registerInstanceOf(DexType type) {
     registerTypeAccess(type);
   }
+
+  public void registerEnclosingMethodAttribute(EnclosingMethodAttribute enclosingMethodAttribute) {
+    // For references in enclosing method attributes we add an edge from the context to the
+    // referenced item even if the item would be accessible from another package, to make sure that
+    // we don't split such classes into different packages.
+    if (enclosingMethodAttribute.getEnclosingClass() != null) {
+      registerTypeAccess(
+          enclosingMethodAttribute.getEnclosingClass(),
+          clazz -> registerTypeAccess(clazz, alwaysTrue()));
+    }
+    if (enclosingMethodAttribute.getEnclosingMethod() != null) {
+      ProgramMethod method = registerMethodReference(enclosingMethodAttribute.getEnclosingMethod());
+      if (method != null) {
+        registerTypeAccess(method.getHolder(), alwaysTrue());
+      }
+    }
+  }
+
+  public void registerInnerClassAttribute(InnerClassAttribute innerClassAttribute) {
+    // For references in inner class attributes we add an edge from the context to the referenced
+    // class even if the referenced class would be accessible from another package, to make sure
+    // that we don't split such classes into different packages.
+    innerClassAttribute.forEachType(
+        type -> registerTypeAccess(type, clazz -> registerTypeAccess(clazz, alwaysTrue())));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index f984045..e13c832 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -26,15 +26,18 @@
 
   private final ClassReference obfuscatedReference;
   private final ClassNamingForNameMapper mapper;
+  private final RetraceApi retracer;
 
-  private RetraceClassResult(ClassReference obfuscatedReference, ClassNamingForNameMapper mapper) {
+  private RetraceClassResult(
+      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetraceApi retracer) {
     this.obfuscatedReference = obfuscatedReference;
     this.mapper = mapper;
+    this.retracer = retracer;
   }
 
   static RetraceClassResult create(
-      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper) {
-    return new RetraceClassResult(obfuscatedReference, mapper);
+      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetraceApi retracer) {
+    return new RetraceClassResult(obfuscatedReference, mapper, retracer);
   }
 
   public RetraceFieldResult lookupField(String fieldName) {
@@ -75,12 +78,12 @@
           if (element.mapper != null) {
             mappedRangesForT = lookupFunction.apply(element.mapper, name);
           }
-          elementBox.set(constructor.create(element, mappedRangesForT, name));
+          elementBox.set(constructor.create(element, mappedRangesForT, name, retracer));
         });
     return elementBox.get();
   }
 
-  private boolean hasRetraceResult() {
+  boolean hasRetraceResult() {
     return mapper != null;
   }
 
@@ -101,7 +104,7 @@
   }
 
   private interface ResultConstructor<T, R> {
-    R create(Element element, T mappings, String obfuscatedName);
+    R create(Element element, T mappings, String obfuscatedName, RetraceApi retraceApi);
   }
 
   public boolean isAmbiguous() {
@@ -186,7 +189,10 @@
         BiFunction<ClassNamingForNameMapper, String, T> lookupFunction,
         ResultConstructor<T, R> constructor) {
       return constructor.create(
-          this, mapper != null ? lookupFunction.apply(mapper, name) : null, name);
+          this,
+          mapper != null ? lookupFunction.apply(mapper, name) : null,
+          name,
+          classResult.retracer);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
index 57b5354..5477eaa 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -23,14 +23,17 @@
   private final RetraceClassResult.Element classElement;
   private final List<MemberNaming> memberNamings;
   private final String obfuscatedName;
+  private final RetraceApi retracer;
 
   RetraceFieldResult(
       RetraceClassResult.Element classElement,
       List<MemberNaming> memberNamings,
-      String obfuscatedName) {
+      String obfuscatedName,
+      RetraceApi retracer) {
     this.classElement = classElement;
     this.memberNamings = memberNamings;
     this.obfuscatedName = obfuscatedName;
+    this.retracer = retracer;
     assert classElement != null;
     assert memberNamings == null
         || (!memberNamings.isEmpty() && memberNamings.stream().allMatch(Objects::nonNull));
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index ff1af91..7977858 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -26,15 +26,18 @@
   private final String obfuscatedName;
   private final RetraceClassResult.Element classElement;
   private final MappedRangesOfName mappedRanges;
+  private final RetraceApi retracer;
   private Boolean isAmbiguousCached = null;
 
   RetraceMethodResult(
       RetraceClassResult.Element classElement,
       MappedRangesOfName mappedRanges,
-      String obfuscatedName) {
+      String obfuscatedName,
+      RetraceApi retracer) {
     this.classElement = classElement;
     this.mappedRanges = mappedRanges;
     this.obfuscatedName = obfuscatedName;
+    this.retracer = retracer;
     assert classElement != null;
   }
 
@@ -87,7 +90,7 @@
       }
     }
     return new RetraceMethodResult(
-        classElement, new MappedRangesOfName(narrowedRanges), obfuscatedName);
+        classElement, new MappedRangesOfName(narrowedRanges), obfuscatedName, retracer);
   }
 
   @Override
@@ -182,7 +185,8 @@
     }
 
     public RetraceSourceFileResult retraceSourceFile(String sourceFile) {
-      return RetraceUtils.getSourceFile(classElement, methodReference.getHolderClass(), sourceFile);
+      return RetraceUtils.getSourceFile(
+          classElement, methodReference.getHolderClass(), sourceFile, retraceMethodResult.retracer);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
index 15479f9..40b3f41 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -838,7 +838,8 @@
                   : RetraceUtils.getSourceFile(
                       retraceString.getClassContext(),
                       retraceString.context.qualifiedContext,
-                      fileName);
+                      fileName,
+                      retracer);
           retracedStrings.add(
               retraceString
                   .updateContext(context -> context.withSource(sourceFileResult.getFilename()))
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
index 8e862d0..77aff7d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.Box;
 import com.google.common.collect.Sets;
 import com.google.common.io.Files;
 import java.util.Set;
@@ -14,7 +15,7 @@
 public class RetraceUtils {
 
   private static final Set<String> UNKNOWN_SOURCEFILE_NAMES =
-      Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source");
+      Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source", "PG");
 
   public static String methodDescriptionFromMethodReference(
       MethodReference methodReference, boolean appendHolder, boolean verbose) {
@@ -61,21 +62,33 @@
   }
 
   static RetraceSourceFileResult getSourceFile(
-      RetraceClassResult.Element classElement, ClassReference context, String sourceFile) {
-    // For inline frames we do not have the class element associated with it.
+      RetraceClassResult.Element classElement,
+      ClassReference context,
+      String sourceFile,
+      RetraceApi retracer) {
+    // If no context is specified always retrace using the found class element.
     if (context == null) {
       return classElement.retraceSourceFile(sourceFile);
     }
     if (context.equals(classElement.getClassReference())) {
       return classElement.retraceSourceFile(sourceFile);
     } else {
-      return new RetraceSourceFileResult(
-          synthesizeFileName(
-              context.getTypeName(),
-              classElement.getClassReference().getTypeName(),
-              sourceFile,
-              true),
-          true);
+      RetraceClassResult contextClassResult = retracer.retrace(context);
+      assert !contextClassResult.isAmbiguous();
+      if (contextClassResult.hasRetraceResult()) {
+        Box<RetraceSourceFileResult> retraceSourceFile = new Box<>();
+        contextClassResult.forEach(
+            element -> retraceSourceFile.set(element.retraceSourceFile(sourceFile)));
+        return retraceSourceFile.get();
+      } else {
+        return new RetraceSourceFileResult(
+            synthesizeFileName(
+                context.getTypeName(),
+                classElement.getClassReference().getTypeName(),
+                sourceFile,
+                true),
+            true);
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index b2dad89..0f7d547 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -38,7 +38,7 @@
   @Override
   public RetraceClassResult retrace(ClassReference classReference) {
     return RetraceClassResult.create(
-        classReference, classNameMapper.getClassNaming(classReference.getTypeName()));
+        classReference, classNameMapper.getClassNaming(classReference.getTypeName()), this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 627d84f..311e10d 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -31,10 +31,11 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.InstantiatedSubTypeInfo;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.LookupTarget;
+import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
@@ -118,23 +119,15 @@
    * each field. The latter is used, for example, during member rebinding.
    */
   private FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
+  /** Set of all methods referenced in invokes along with their calling contexts. */
+  private final MethodAccessInfoCollection methodAccessInfoCollection;
   /** Information about instantiated classes and their allocation sites. */
   private final ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection;
-  /** Set of all methods referenced in virtual invokes, along with calling context. */
-  public final SortedMap<DexMethod, ProgramMethodSet> virtualInvokes;
-  /** Set of all methods referenced in interface invokes, along with calling context. */
-  public final SortedMap<DexMethod, ProgramMethodSet> interfaceInvokes;
-  /** Set of all methods referenced in super invokes, along with calling context. */
-  public final SortedMap<DexMethod, ProgramMethodSet> superInvokes;
-  /** Set of all methods referenced in direct invokes, along with calling context. */
-  public final SortedMap<DexMethod, ProgramMethodSet> directInvokes;
-  /** Set of all methods referenced in static invokes, along with calling context. */
-  public final SortedMap<DexMethod, ProgramMethodSet> staticInvokes;
   /**
    * Set of live call sites in the code. Note that if desugaring has taken place call site objects
    * will have been removed from the code.
    */
-  public final Set<DexCallSite> callSites;
+  public final Map<DexCallSite, ProgramMethodSet> callSites;
   /** Collection of keep requirements for the program. */
   private final KeepInfoCollection keepInfo;
   /** All items with assumemayhavesideeffects rule. */
@@ -212,13 +205,9 @@
       SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
       SortedSet<DexMethod> liveMethods,
       FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
+      MethodAccessInfoCollection methodAccessInfoCollection,
       ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection,
-      SortedMap<DexMethod, ProgramMethodSet> virtualInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> interfaceInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> superInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> directInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
-      Set<DexCallSite> callSites,
+      Map<DexCallSite, ProgramMethodSet> callSites,
       KeepInfoCollection keepInfo,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
       Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -255,16 +244,12 @@
     this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
     this.liveMethods = liveMethods;
     this.fieldAccessInfoCollection = fieldAccessInfoCollection;
+    this.methodAccessInfoCollection = methodAccessInfoCollection;
     this.objectAllocationInfoCollection = objectAllocationInfoCollection;
     this.keepInfo = keepInfo;
     this.mayHaveSideEffects = mayHaveSideEffects;
     this.noSideEffects = noSideEffects;
     this.assumedValues = assumedValues;
-    this.virtualInvokes = virtualInvokes;
-    this.interfaceInvokes = interfaceInvokes;
-    this.superInvokes = superInvokes;
-    this.directInvokes = directInvokes;
-    this.staticInvokes = staticInvokes;
     this.callSites = callSites;
     this.alwaysInline = alwaysInline;
     this.forceInline = forceInline;
@@ -301,13 +286,9 @@
       SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
       SortedSet<DexMethod> liveMethods,
       FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
+      MethodAccessInfoCollection methodAccessInfoCollection,
       ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection,
-      SortedMap<DexMethod, ProgramMethodSet> virtualInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> interfaceInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> superInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> directInvokes,
-      SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
-      Set<DexCallSite> callSites,
+      Map<DexCallSite, ProgramMethodSet> callSites,
       KeepInfoCollection keepInfo,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
       Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -347,16 +328,12 @@
     this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
     this.liveMethods = liveMethods;
     this.fieldAccessInfoCollection = fieldAccessInfoCollection;
+    this.methodAccessInfoCollection = methodAccessInfoCollection;
     this.objectAllocationInfoCollection = objectAllocationInfoCollection;
     this.keepInfo = keepInfo;
     this.mayHaveSideEffects = mayHaveSideEffects;
     this.noSideEffects = noSideEffects;
     this.assumedValues = assumedValues;
-    this.virtualInvokes = virtualInvokes;
-    this.interfaceInvokes = interfaceInvokes;
-    this.superInvokes = superInvokes;
-    this.directInvokes = directInvokes;
-    this.staticInvokes = staticInvokes;
     this.callSites = callSites;
     this.alwaysInline = alwaysInline;
     this.forceInline = forceInline;
@@ -398,12 +375,8 @@
         previous.virtualMethodsTargetedByInvokeDirect,
         previous.liveMethods,
         previous.fieldAccessInfoCollection,
+        previous.methodAccessInfoCollection,
         previous.objectAllocationInfoCollection,
-        previous.virtualInvokes,
-        previous.interfaceInvokes,
-        previous.superInvokes,
-        previous.directInvokes,
-        previous.staticInvokes,
         previous.callSites,
         previous.keepInfo,
         previous.mayHaveSideEffects,
@@ -453,12 +426,8 @@
         previous.virtualMethodsTargetedByInvokeDirect,
         previous.liveMethods,
         previous.fieldAccessInfoCollection,
+        previous.methodAccessInfoCollection,
         previous.objectAllocationInfoCollection,
-        previous.virtualInvokes,
-        previous.interfaceInvokes,
-        previous.superInvokes,
-        previous.directInvokes,
-        previous.staticInvokes,
         previous.callSites,
         extendPinnedItems(previous, additionalPinnedItems),
         previous.mayHaveSideEffects,
@@ -545,16 +514,12 @@
     this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
     this.liveMethods = previous.liveMethods;
     this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
+    this.methodAccessInfoCollection = previous.methodAccessInfoCollection;
     this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
     this.keepInfo = previous.keepInfo;
     this.mayHaveSideEffects = previous.mayHaveSideEffects;
     this.noSideEffects = previous.noSideEffects;
     this.assumedValues = previous.assumedValues;
-    this.virtualInvokes = previous.virtualInvokes;
-    this.interfaceInvokes = previous.interfaceInvokes;
-    this.superInvokes = previous.superInvokes;
-    this.directInvokes = previous.directInvokes;
-    this.staticInvokes = previous.staticInvokes;
     this.callSites = previous.callSites;
     this.alwaysInline = previous.alwaysInline;
     this.forceInline = previous.forceInline;
@@ -653,7 +618,7 @@
     for (DexProgramClass clazz : classes()) {
       worklist.add(clazz.type);
     }
-    for (DexCallSite callSite : callSites) {
+    for (DexCallSite callSite : callSites.keySet()) {
       for (DexEncodedMethod method : lookupLambdaImplementedMethods(callSite)) {
         worklist.add(method.holder());
       }
@@ -779,6 +744,11 @@
     return fieldAccessInfoCollection;
   }
 
+  /** This method provides immutable access to `methodAccessInfoCollection`. */
+  public MethodAccessInfoCollection getMethodAccessInfoCollection() {
+    return methodAccessInfoCollection;
+  }
+
   /** This method provides immutable access to `objectAllocationInfoCollection`. */
   public ObjectAllocationInfoCollection getObjectAllocationInfoCollection() {
     return objectAllocationInfoCollection;
@@ -993,17 +963,9 @@
   }
 
   public AppInfoWithLiveness rewrittenWithLens(
-      DirectMappedDexApplication application, NestedGraphLens lens) {
+      DirectMappedDexApplication application, NonIdentityGraphLens lens) {
     assert checkIfObsolete();
-    // The application has already been rewritten with all of lens' parent lenses. Therefore, we
-    // temporarily replace lens' parent lens with an identity lens to avoid the overhead of
-    // traversing the entire lens chain upon each lookup during the rewriting.
-    return lens.withAlternativeParentLens(
-        GraphLens.getIdentityLens(), () -> createRewrittenAppInfoWithLiveness(application, lens));
-  }
 
-  private AppInfoWithLiveness createRewrittenAppInfoWithLiveness(
-      DirectMappedDexApplication application, NestedGraphLens lens) {
     // Switchmap classes should never be affected by renaming.
     assert lens.assertDefinitionsNotModified(
         switchMaps.keySet().stream()
@@ -1028,18 +990,10 @@
         lens.rewriteMethods(methodsTargetedByInvokeDynamic),
         lens.rewriteMethods(virtualMethodsTargetedByInvokeDirect),
         lens.rewriteMethods(liveMethods),
-        fieldAccessInfoCollection != null
-            ? fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens)
-            : null,
+        fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens),
+        methodAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens),
         objectAllocationInfoCollection.rewrittenWithLens(definitionSupplier, lens),
-        rewriteInvokesWithContexts(virtualInvokes, lens),
-        rewriteInvokesWithContexts(interfaceInvokes, lens),
-        rewriteInvokesWithContexts(superInvokes, lens),
-        rewriteInvokesWithContexts(directInvokes, lens),
-        rewriteInvokesWithContexts(staticInvokes, lens),
-        // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
-        //   after second tree shaking.
-        callSites,
+        lens.rewriteCallSites(callSites, definitionSupplier),
         keepInfo.rewrite(lens, application.options),
         lens.rewriteReferenceKeys(mayHaveSideEffects),
         lens.rewriteReferenceKeys(noSideEffects),
@@ -1300,7 +1254,7 @@
   static void forEachTypeInHierarchyOfLiveProgramClasses(
       Consumer<DexClass> fn,
       Collection<DexProgramClass> liveProgramClasses,
-      Set<DexCallSite> callSites,
+      Map<DexCallSite, ProgramMethodSet> callSites,
       AppInfoWithClassHierarchy appInfo) {
     Set<DexType> seen = Sets.newIdentityHashSet();
     Deque<DexType> worklist = new ArrayDeque<>();
@@ -1317,7 +1271,7 @@
         }
       }
     }
-    for (DexCallSite callSite : callSites) {
+    for (DexCallSite callSite : callSites.keySet()) {
       List<DexType> interfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
       if (interfaces != null) {
         for (DexType iface : interfaces) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 98def17..0afccd0 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
-import static com.android.tools.r8.shaking.EnqueuerUtils.toImmutableSortedMap;
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
@@ -61,6 +60,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.LookupLambdaTarget;
 import com.android.tools.r8.graph.LookupTarget;
+import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ProgramField;
@@ -141,6 +141,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -200,15 +201,12 @@
   private AnnotationRemover.Builder annotationRemoverBuilder;
   private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
 
-  private final Map<DexMethod, ProgramMethodSet> virtualInvokes = new IdentityHashMap<>();
-  private final Map<DexMethod, ProgramMethodSet> interfaceInvokes = new IdentityHashMap<>();
-  private final Map<DexMethod, ProgramMethodSet> superInvokes = new IdentityHashMap<>();
-  private final Map<DexMethod, ProgramMethodSet> directInvokes = new IdentityHashMap<>();
-  private final Map<DexMethod, ProgramMethodSet> staticInvokes = new IdentityHashMap<>();
   private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
       new FieldAccessInfoCollectionImpl();
+  private final MethodAccessInfoCollection.SortedBuilder methodAccessInfoCollection =
+      MethodAccessInfoCollection.sortedBuilder();
   private final ObjectAllocationInfoCollectionImpl.Builder objectAllocationInfoCollection;
-  private final Set<DexCallSite> callSites = Sets.newIdentityHashSet();
+  private final Map<DexCallSite, ProgramMethodSet> callSites = new IdentityHashMap<>();
 
   private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
 
@@ -784,11 +782,11 @@
   //
 
   private boolean registerMethodWithTargetAndContext(
-      Map<DexMethod, ProgramMethodSet> seen, DexMethod method, ProgramMethod context) {
+      BiPredicate<DexMethod, ProgramMethod> registration, DexMethod method, ProgramMethod context) {
     DexType baseHolder = method.holder.toBaseType(appView.dexItemFactory());
     if (baseHolder.isClassType()) {
       markTypeAsLive(baseHolder, clazz -> graphReporter.reportClassReferencedFrom(clazz, context));
-      return seen.computeIfAbsent(method, ignore -> ProgramMethodSet.create()).add(context);
+      return registration.test(method, context);
     }
     return false;
   }
@@ -883,7 +881,7 @@
     } else {
       markLambdaAsInstantiated(descriptor, context);
       transitionMethodsForInstantiatedLambda(descriptor);
-      callSites.add(callSite);
+      callSites.computeIfAbsent(callSite, ignore -> ProgramMethodSet.create()).add(context);
     }
 
     // For call sites representing a lambda, we link the targeted method
@@ -1078,7 +1076,8 @@
 
   private void traceInvokeDirect(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
-    if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, context)) {
+    if (!registerMethodWithTargetAndContext(
+        methodAccessInfoCollection::registerInvokeDirectInContext, invokedMethod, context)) {
       return;
     }
     if (Log.ENABLED) {
@@ -1098,7 +1097,8 @@
 
   private void traceInvokeInterface(
       DexMethod method, ProgramMethod context, KeepReason keepReason) {
-    if (!registerMethodWithTargetAndContext(interfaceInvokes, method, context)) {
+    if (!registerMethodWithTargetAndContext(
+        methodAccessInfoCollection::registerInvokeInterfaceInContext, method, context)) {
       return;
     }
     if (Log.ENABLED) {
@@ -1137,7 +1137,8 @@
     if (invokedMethod == dexItemFactory.proxyMethods.newProxyInstance) {
       pendingReflectiveUses.add(context);
     }
-    if (!registerMethodWithTargetAndContext(staticInvokes, invokedMethod, context)) {
+    if (!registerMethodWithTargetAndContext(
+        methodAccessInfoCollection::registerInvokeStaticInContext, invokedMethod, context)) {
       return;
     }
     if (Log.ENABLED) {
@@ -1151,7 +1152,8 @@
     // We have to revisit super invokes based on the context they are found in. The same
     // method descriptor will hit different targets, depending on the context it is used in.
     DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, context);
-    if (!registerMethodWithTargetAndContext(superInvokes, invokedMethod, context)) {
+    if (!registerMethodWithTargetAndContext(
+        methodAccessInfoCollection::registerInvokeSuperInContext, invokedMethod, context)) {
       return;
     }
     if (Log.ENABLED) {
@@ -1180,7 +1182,8 @@
       // Revisit the current method to implicitly add -keep rule for items with reflective access.
       pendingReflectiveUses.add(context);
     }
-    if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context)) {
+    if (!registerMethodWithTargetAndContext(
+        methodAccessInfoCollection::registerInvokeVirtualInContext, invokedMethod, context)) {
       return;
     }
     if (Log.ENABLED) {
@@ -3037,13 +3040,8 @@
             toSortedDescriptorSet(liveMethods.getItems()),
             // Filter out library fields and pinned fields, because these are read by default.
             fieldAccessInfoCollection,
+            methodAccessInfoCollection.build(),
             objectAllocationInfoCollection.build(appInfo),
-            // TODO(b/132593519): Do we require these sets to be sorted for determinism?
-            toImmutableSortedMap(virtualInvokes, PresortedComparable::slowCompare),
-            toImmutableSortedMap(interfaceInvokes, PresortedComparable::slowCompare),
-            toImmutableSortedMap(superInvokes, PresortedComparable::slowCompare),
-            toImmutableSortedMap(directInvokes, PresortedComparable::slowCompare),
-            toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
             callSites,
             keepInfo,
             rootSet.mayHaveSideEffects,
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index efc610a..d2cbf66 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
@@ -162,7 +162,7 @@
   @Deprecated
   public abstract void forEachPinnedField(Consumer<DexField> consumer);
 
-  public abstract KeepInfoCollection rewrite(NestedGraphLens lens, InternalOptions options);
+  public abstract KeepInfoCollection rewrite(NonIdentityGraphLens lens, InternalOptions options);
 
   public abstract KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator);
 
@@ -198,7 +198,7 @@
     }
 
     @Override
-    public KeepInfoCollection rewrite(NestedGraphLens lens, InternalOptions options) {
+    public KeepInfoCollection rewrite(NonIdentityGraphLens lens, InternalOptions options) {
       Map<DexType, KeepClassInfo> newClassInfo = new IdentityHashMap<>(keepClassInfo.size());
       keepClassInfo.forEach(
           (type, info) -> {
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
index d333211..76b6d8a 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -92,6 +93,14 @@
     return classes;
   }
 
+  public boolean contains(DexProgramClass clazz) {
+    return contains(clazz.type);
+  }
+
+  public boolean contains(DexType type) {
+    return getClasses().contains(type);
+  }
+
   private void collectTypesMatching(
       Set<DexType> types, Predicate<DexType> predicate, Consumer<DexType> consumer) {
     types.forEach(
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 5948028..953779d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -28,7 +28,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.GraphLensLookupResult;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
@@ -1139,7 +1140,7 @@
         // resolve to a method on an interface never hit an implementation below that interface.
         deferredRenamings.mapVirtualMethodToDirectInType(
             oldTarget,
-            prototypeChanges -> new GraphLensLookupResult(newTarget, STATIC, prototypeChanges),
+            prototypeChanges -> new MethodLookupResult(newTarget, null, STATIC, prototypeChanges),
             target.type);
       } else {
         // If we merge class B into class C, and class C contains an invocation super.m(), then it
@@ -1160,7 +1161,8 @@
           if (resolutionSucceeds) {
             deferredRenamings.mapVirtualMethodToDirectInType(
                 signatureInHolder,
-                prototypeChanges -> new GraphLensLookupResult(newTarget, DIRECT, prototypeChanges),
+                prototypeChanges ->
+                    new MethodLookupResult(newTarget, null, DIRECT, prototypeChanges),
                 target.type);
           } else {
             break;
@@ -1185,7 +1187,7 @@
                 deferredRenamings.mapVirtualMethodToDirectInType(
                     signatureInType,
                     prototypeChanges ->
-                        new GraphLensLookupResult(newTarget, DIRECT, prototypeChanges),
+                        new MethodLookupResult(newTarget, null, DIRECT, prototypeChanges),
                     target.type);
               }
             }
@@ -1693,12 +1695,13 @@
     return AbortReason.UNSAFE_INLINING;
   }
 
-  private class SingleTypeMapperGraphLens extends GraphLens {
+  private class SingleTypeMapperGraphLens extends NonIdentityGraphLens {
 
     private final DexType source;
     private final DexProgramClass target;
 
     public SingleTypeMapperGraphLens(DexType source, DexProgramClass target) {
+      super(appView.dexItemFactory(), GraphLens.getIdentityLens());
       this.source = source;
       this.target = target;
     }
@@ -1729,30 +1732,47 @@
     }
 
     @Override
-    public DexType lookupType(DexType type) {
-      return type == source ? target.type : mergedClasses.getOrDefault(type, type);
+    public final DexType internalDescribeLookupClassType(DexType previous) {
+      return previous == source ? target.type : mergedClasses.getOrDefault(previous, previous);
     }
 
     @Override
-    public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
+    protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
       // First look up the method using the existing graph lens (for example, the type will have
       // changed if the method was publicized by ClassAndMemberPublicizer).
-      GraphLensLookupResult lookup = appView.graphLens().lookupMethod(method, context, type);
+      MethodLookupResult lookup = appView.graphLens().lookupMethod(method, context, type);
       // Then check if there is a renaming due to the vertical class merger.
-      DexMethod newMethod = renamedMembersLens.methodMap.get(lookup.getMethod());
+      DexMethod newMethod = renamedMembersLens.methodMap.get(lookup.getReference());
       if (newMethod == null) {
         return lookup;
       }
+      MethodLookupResult.Builder methodLookupResultBuilder =
+          MethodLookupResult.builder(this)
+              .setReference(newMethod)
+              .setPrototypeChanges(lookup.getPrototypeChanges())
+              .setType(lookup.getType());
       if (lookup.getType() == Type.INTERFACE) {
         // If an interface has been merged into a class, invoke-interface needs to be translated
         // to invoke-virtual.
         DexClass clazz = appInfo.definitionFor(newMethod.holder);
         if (clazz != null && !clazz.accessFlags.isInterface()) {
           assert appInfo.definitionFor(method.holder).accessFlags.isInterface();
-          return new GraphLensLookupResult(newMethod, Type.VIRTUAL, lookup.getPrototypeChanges());
+          methodLookupResultBuilder.setType(Type.VIRTUAL);
         }
       }
-      return new GraphLensLookupResult(newMethod, lookup.getType(), lookup.getPrototypeChanges());
+      return methodLookupResultBuilder.build();
+    }
+
+    @Override
+    protected MethodLookupResult internalDescribeLookupMethod(
+        MethodLookupResult previous, DexMethod context) {
+      // This is unreachable since we override the implementation of lookupMethod() above.
+      throw new Unreachable();
     }
 
     @Override
@@ -1767,6 +1787,12 @@
     }
 
     @Override
+    protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+      // This is unreachable since we override the implementation of lookupField() above.
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean isContextFreeForMethods() {
       return true;
     }
@@ -1855,41 +1881,41 @@
     @Override
     public void registerInvokeVirtual(DexMethod method) {
       assert context != null;
-      GraphLensLookupResult lookup =
+      MethodLookupResult lookup =
           appView.graphLens().lookupMethod(method, context.getReference(), Type.VIRTUAL);
-      checkMethodReference(lookup.getMethod(), OptionalBool.FALSE);
+      checkMethodReference(lookup.getReference(), OptionalBool.FALSE);
     }
 
     @Override
     public void registerInvokeDirect(DexMethod method) {
       assert context != null;
-      GraphLensLookupResult lookup =
+      MethodLookupResult lookup =
           appView.graphLens().lookupMethod(method, context.getReference(), Type.DIRECT);
-      checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
+      checkMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
     }
 
     @Override
     public void registerInvokeStatic(DexMethod method) {
       assert context != null;
-      GraphLensLookupResult lookup =
+      MethodLookupResult lookup =
           appView.graphLens().lookupMethod(method, context.getReference(), Type.STATIC);
-      checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
+      checkMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
     }
 
     @Override
     public void registerInvokeInterface(DexMethod method) {
       assert context != null;
-      GraphLensLookupResult lookup =
+      MethodLookupResult lookup =
           appView.graphLens().lookupMethod(method, context.getReference(), Type.INTERFACE);
-      checkMethodReference(lookup.getMethod(), OptionalBool.TRUE);
+      checkMethodReference(lookup.getReference(), OptionalBool.TRUE);
     }
 
     @Override
     public void registerInvokeSuper(DexMethod method) {
       assert context != null;
-      GraphLensLookupResult lookup =
+      MethodLookupResult lookup =
           appView.graphLens().lookupMethod(method, context.getReference(), Type.SUPER);
-      checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
+      checkMethodReference(lookup.getReference(), OptionalBool.UNKNOWN);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
index 683f1c9..2848d3f 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -50,7 +50,7 @@
 
   interface GraphLensLookupResultProvider {
 
-    abstract GraphLensLookupResult get(RewrittenPrototypeDescription prototypeChanges);
+    abstract MethodLookupResult get(RewrittenPrototypeDescription prototypeChanges);
   }
 
   private final AppView<?> appView;
@@ -88,7 +88,7 @@
 
   @Override
   public DexType getOriginalType(DexType type) {
-    return previousLens.getOriginalType(type);
+    return getPrevious().getOriginalType(type);
   }
 
   @Override
@@ -98,19 +98,16 @@
   }
 
   @Override
-  public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
-    assert context != null || verifyIsContextFreeForMethod(method);
-    assert context == null || type != null;
-    DexMethod previousContext =
-        originalMethodSignaturesForBridges.containsKey(context)
-            ? originalMethodSignaturesForBridges.get(context)
-            : originalMethodSignatures.getOrDefault(context, context);
-    GraphLensLookupResult lookup = previousLens.lookupMethod(method, previousContext, type);
-    if (lookup.getType() == Type.SUPER && !mergedMethods.contains(context)) {
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    assert context != null || verifyIsContextFreeForMethod(previous.getReference());
+    assert context == null || previous.getType() != null;
+    if (previous.getType() == Type.SUPER && !mergedMethods.contains(context)) {
       Map<DexMethod, GraphLensLookupResultProvider> virtualToDirectMethodMap =
-          contextualVirtualToDirectMethodMaps.get(context.holder);
+          contextualVirtualToDirectMethodMaps.get(context.getHolderType());
       if (virtualToDirectMethodMap != null) {
-        GraphLensLookupResultProvider result = virtualToDirectMethodMap.get(lookup.getMethod());
+        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
@@ -118,18 +115,20 @@
           // 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(lookup.getPrototypeChanges());
+          return result.get(previous.getPrototypeChanges());
         }
       }
     }
-    DexMethod newMethod = methodMap.get(lookup.getMethod());
+    DexMethod newMethod = methodMap.get(previous.getReference());
     if (newMethod == null) {
-      return lookup;
+      return previous;
     }
-    return new GraphLensLookupResult(
-        newMethod,
-        mapInvocationType(newMethod, lookup.getMethod(), lookup.getType()),
-        internalDescribePrototypeChanges(lookup.getPrototypeChanges(), newMethod));
+    return MethodLookupResult.builder(this)
+        .setReference(newMethod)
+        .setPrototypeChanges(
+            internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod))
+        .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
+        .build();
   }
 
   @Override
@@ -139,13 +138,13 @@
 
   @Override
   public boolean isContextFreeForMethods() {
-    return contextualVirtualToDirectMethodMaps.isEmpty() && previousLens.isContextFreeForMethods();
+    return contextualVirtualToDirectMethodMaps.isEmpty() && getPrevious().isContextFreeForMethods();
   }
 
   @Override
   public boolean verifyIsContextFreeForMethod(DexMethod method) {
-    assert previousLens.verifyIsContextFreeForMethod(method);
-    DexMethod previous = previousLens.lookupMethod(method);
+    assert getPrevious().verifyIsContextFreeForMethod(method);
+    DexMethod previous = getPrevious().lookupMethod(method);
     assert contextualVirtualToDirectMethodMaps.values().stream()
         .noneMatch(virtualToDirectMethodMap -> virtualToDirectMethodMap.containsKey(previous));
     return true;
@@ -194,14 +193,14 @@
         for (Map.Entry<DexMethod, GraphLensLookupResultProvider> innerEntry :
             entry.getValue().entrySet()) {
           DexMethod from = innerEntry.getKey();
-          GraphLensLookupResult rewriting =
+          MethodLookupResult rewriting =
               innerEntry.getValue().get(RewrittenPrototypeDescription.none());
           DexMethod to =
-              builder.getMethodSignatureAfterClassMerging(rewriting.getMethod(), mergedClasses);
+              builder.getMethodSignatureAfterClassMerging(rewriting.getReference(), mergedClasses);
           newBuilder.mapVirtualMethodToDirectInType(
               from,
               prototypeChanges ->
-                  new GraphLensLookupResult(to, rewriting.getType(), prototypeChanges),
+                  new MethodLookupResult(to, null, rewriting.getType(), prototypeChanges),
               context);
         }
       }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index c03022e..380c2dc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.synthesis;
 
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.origin.Origin;
 
 /**
@@ -29,7 +29,7 @@
     this.origin = origin;
   }
 
-  SynthesizingContext rewrite(NestedGraphLens lens) {
+  SynthesizingContext rewrite(NonIdentityGraphLens lens) {
     DexType rewritten = lens.lookupType(type);
     return rewritten == type ? this : new SynthesizingContext(type, origin);
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index a2dd07e..2996794 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.Builder;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -99,7 +100,7 @@
     Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> equivalences =
         computeActualEquivalences(potentialEquivalences, options.itemFactory);
 
-    Builder lensBuilder = GraphLens.builder();
+    Builder lensBuilder = NestedGraphLens.builder();
     List<DexProgramClass> newProgramClasses = new ArrayList<>();
     List<DexProgramClass> finalSyntheticClasses = new ArrayList<>();
     buildLensAndProgram(
@@ -144,11 +145,12 @@
       DexApplication application,
       InternalOptions options,
       MainDexClasses mainDexClasses) {
-    if (options.intermediate) {
+    boolean includeSynthesizedClassMappingInOutput = options.intermediate && !options.cfToCfDesugar;
+    if (includeSynthesizedClassMappingInOutput) {
       updateSynthesizedClassMapping(application, finalSyntheticClasses);
     }
     updateMainDexListWithSynthesizedClassMap(application, mainDexClasses);
-    if (!options.intermediate) {
+    if (!includeSynthesizedClassMappingInOutput) {
       clearSynthesizedClassMapping(application);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index b3c179f..ef45975 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
 import com.google.common.collect.ImmutableList;
@@ -233,7 +233,8 @@
         nextSyntheticId);
   }
 
-  public CommittedItems commitRewrittenWithLens(DexApplication application, NestedGraphLens lens) {
+  public CommittedItems commitRewrittenWithLens(
+      DexApplication application, NonIdentityGraphLens lens) {
     // Rewrite the previously committed synthetic types.
     ImmutableSet<DexType> rewrittenLegacyTypes = lens.rewriteTypes(this.legacySyntheticTypes);
     ImmutableMap.Builder<DexType, SyntheticReference> rewrittenItems = ImmutableMap.builder();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index 326eee0..6070e49 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import java.util.function.Function;
 
@@ -40,7 +40,7 @@
   }
 
   @Override
-  SyntheticReference rewrite(NestedGraphLens lens) {
+  SyntheticReference rewrite(NonIdentityGraphLens lens) {
     SynthesizingContext context = getContext().rewrite(lens);
     DexMethod rewritten = lens.lookupMethod(method);
     return context == getContext() && rewritten == method
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
index 9981624..9b95337 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.origin.Origin;
 import java.util.function.Function;
 
@@ -37,5 +37,5 @@
 
   abstract DexType getHolder();
 
-  abstract SyntheticReference rewrite(NestedGraphLens lens);
+  abstract SyntheticReference rewrite(NonIdentityGraphLens lens);
 }
diff --git a/src/main/java/com/android/tools/r8/utils/BiForEachable.java b/src/main/java/com/android/tools/r8/utils/BiForEachable.java
new file mode 100644
index 0000000..f1dd413
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BiForEachable.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import java.util.function.BiConsumer;
+
+public interface BiForEachable<S, T> {
+
+  void forEach(BiConsumer<S, T> consumer);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java b/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java
new file mode 100644
index 0000000..a42fd3a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import java.util.Comparator;
+import java.util.List;
+
+public class ComparatorUtils {
+
+  public static <T extends Comparable<T>> Comparator<List<T>> listComparator() {
+    return listComparator(T::compareTo);
+  }
+
+  public static <T> Comparator<List<T>> listComparator(Comparator<T> comparator) {
+    return (List<T> xs, List<T> ys) -> {
+      int diff = Integer.compare(xs.size(), ys.size());
+      for (int i = 0; i < xs.size() && diff == 0; i++) {
+        diff = comparator.compare(xs.get(i), ys.get(i));
+      }
+      return diff;
+    };
+  }
+
+  // Compare pair-wise integers in sequenced order, i.e., (A1, A2), (B1, B2), (C1, C2), ...
+  public static int compareInts(int... ints) {
+    assert ints.length % 2 == 0;
+    int diff = 0;
+    for (int i = 0; i < ints.length && diff == 0; ) {
+      diff = Integer.compare(ints[i++], ints[i++]);
+    }
+    return diff;
+  }
+
+  public static int compareIntArray(int[] xs, int[] ys) {
+    int diff = Integer.compare(xs.length, ys.length);
+    for (int i = 0; i < xs.length && diff == 0; i++) {
+      diff = Integer.compare(xs[i], ys[i]);
+    }
+    return diff;
+  }
+
+  public static int compareShortArray(short[] xs, short[] ys) {
+    int diff = Integer.compare(xs.length, ys.length);
+    for (int i = 0; i < xs.length && diff == 0; i++) {
+      diff = Short.compare(xs[i], ys[i]);
+    }
+    return diff;
+  }
+
+  public static <T extends Comparable<T>> Comparator<T[]> arrayComparator() {
+    return arrayComparator(T::compareTo);
+  }
+
+  public static <T> Comparator<T[]> arrayComparator(Comparator<T> comparator) {
+    return (T[] xs, T[] ys) -> {
+      int diff = Integer.compare(xs.length, ys.length);
+      for (int i = 0; i < xs.length && diff == 0; i++) {
+        diff = comparator.compare(xs[i], ys[i]);
+      }
+      return diff;
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
index 2079af2..c1c7d5a 100644
--- a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
@@ -4,10 +4,19 @@
 
 package com.android.tools.r8.utils;
 
+import java.util.Set;
 import java.util.function.Consumer;
 
 public class ConsumerUtils {
 
+  public static <T> Consumer<T> acceptIfNotSeen(Consumer<T> consumer, Set<T> seen) {
+    return element -> {
+      if (seen.add(element)) {
+        consumer.accept(element);
+      }
+    };
+  }
+
   public static <T> Consumer<T> emptyConsumer() {
     return ignore -> {};
   }
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 8c070a5..95b0fde 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -291,6 +291,42 @@
         .replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR);
   }
 
+  /**
+   * Get the simple class name from its descriptor.
+   *
+   * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;"
+   * @return simple class name i.e. "Object"
+   */
+  public static String getSimpleClassNameFromDescriptor(String classDescriptor) {
+    return classDescriptor.substring(
+        getSimpleClassNameIndex(classDescriptor), classDescriptor.length() - 1);
+  }
+
+  /**
+   * Replace the simple class name from its descriptor with a new simple name.
+   *
+   * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;"
+   * @param newSimpleName a new simple name e.g. "NewObject"
+   * @return updated class descriptor i.e. "Ljava/lang/NewObject;"
+   */
+  public static String replaceSimpleClassNameInDescriptor(
+      String classDescriptor, String newSimpleName) {
+    return "L"
+        + classDescriptor.substring(1, getSimpleClassNameIndex(classDescriptor))
+        + newSimpleName
+        + ";";
+  }
+
+  /**
+   * Finds the index of the simple class name in its descriptor.
+   *
+   * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;"
+   * @return the index of the simple name i.e. 11.
+   */
+  private static int getSimpleClassNameIndex(String classDescriptor) {
+    return Integer.max(classDescriptor.lastIndexOf("/"), 0) + 1;
+  }
+
    /**
    * Get canonical class name from its descriptor.
    *
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index cf31dec..1a53b74 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -253,6 +253,15 @@
 
   public int callGraphLikelySpuriousCallEdgeThreshold = 50;
 
+  public int verificationSizeLimitInBytes() {
+    if (testing.verificationSizeLimitInBytesOverride > -1) {
+      return testing.verificationSizeLimitInBytesOverride;
+    }
+    // For CF we use the defined limit in the spec. For DEX we use the limit of the static verifier
+    // https://android.googlesource.com/platform/art/+/android10-release/compiler/compiler.cc#48
+    return isGeneratingClassFiles() ? 65534 : 16383;
+  }
+
   // TODO(b/141719453): The inlining limit at least should be consistent with normal inlining.
   public int classInliningInstructionLimit = 10;
   public int classInliningInstructionAllowance = 50;
@@ -1218,6 +1227,7 @@
     public boolean allowClassInlinerGracefulExit =
         System.getProperty("com.android.tools.r8.disallowClassInlinerGracefulExit") == null;
     public boolean reportUnusedProguardConfigurationRules = false;
+    public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true;
     public boolean alwaysUsePessimisticRegisterAllocation = false;
     public boolean enableCheckCastAndInstanceOfRemoval = true;
     public boolean enableDeadSwitchCaseElimination = true;
@@ -1245,6 +1255,7 @@
     public boolean enumUnboxingRewriteJavaCGeneratedMethod = false;
     public boolean assertConsistentRenamingOfSignature = false;
     public boolean allowStaticInterfaceMethodsForPreNApiLevel = false;
+    public int verificationSizeLimitInBytesOverride = -1;
 
     // Flag to allow processing of resources in D8. A data resource consumer still needs to be
     // specified.
@@ -1257,12 +1268,13 @@
     public boolean enableForceNestBasedAccessDesugaringForTest = false;
     public boolean verifyKeptGraphInfo = false;
 
+    public boolean readInputStackMaps = true;
+    public boolean disableStackMapVerification = false;
+
     // Force each call of application read to dump its inputs to a file, which is subsequently
     // deleted. Useful to check that our dump functionality does not cause compilation failure.
     public boolean dumpAll = false;
 
-    public boolean readInputStackMaps = false;
-
     // Option for testing outlining with interface array arguments, see b/132420510.
     public boolean allowOutlinerInterfaceArrayArguments = false;
 
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index 3929e87..c899a75 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -4,11 +4,39 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.utils.StringUtils.BraceType;
 import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.IntFunction;
 
 public class MapUtils {
 
+  public static <K, V> Map<K, V> map(
+      Map<K, V> map,
+      IntFunction<Map<K, V>> factory,
+      Function<K, K> keyMapping,
+      Function<V, V> valueMapping,
+      BiFunction<V, V, V> valueMerger) {
+    Map<K, V> result = factory.apply(map.size());
+    map.forEach(
+        (key, value) -> {
+          K newKey = keyMapping.apply(key);
+          V newValue = valueMapping.apply(value);
+          V existingValue = result.put(newKey, newValue);
+          if (existingValue != null) {
+            result.put(newKey, valueMerger.apply(existingValue, newValue));
+          }
+        });
+    return result;
+  }
+
   public static <T> void removeIdentityMappings(Map<T, T> map) {
     map.entrySet().removeIf(entry -> entry.getKey() == entry.getValue());
   }
+
+  public static String toString(Map<?, ?> map) {
+    return StringUtils.join(
+        map.entrySet(), ",", BraceType.TUBORG, entry -> entry.getKey() + ":" + entry.getValue());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
new file mode 100644
index 0000000..ad65701
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.collections;
+
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class BidirectionalManyToOneMap<K, V> {
+
+  private final Map<K, V> backing = new IdentityHashMap<>();
+  private final Map<V, Set<K>> inverse = new IdentityHashMap<>();
+
+  public V getOrDefault(K key, V value) {
+    return backing.getOrDefault(key, value);
+  }
+
+  public Set<K> getKeys(V value) {
+    return inverse.getOrDefault(value, Collections.emptySet());
+  }
+
+  public void put(K key, V value) {
+    backing.put(key, value);
+    inverse.computeIfAbsent(value, ignore -> new LinkedHashSet<>()).add(key);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ImmutableDeque.java b/src/main/java/com/android/tools/r8/utils/collections/ImmutableDeque.java
new file mode 100644
index 0000000..17c5e4a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/ImmutableDeque.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.collections;
+
+import com.android.tools.r8.errors.Unreachable;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.function.Predicate;
+
+@SuppressWarnings("NullableProblems")
+public class ImmutableDeque<T> extends ArrayDeque<T> {
+
+  private boolean isClosed = false;
+
+  private ImmutableDeque(Collection<T> items) {
+    super(items);
+  }
+
+  private void close() {
+    isClosed = true;
+  }
+
+  @Override
+  public void push(T t) {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public T pop() {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public void addFirst(T t) {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public void addLast(T t) {
+    if (isClosed) {
+      throw new Unreachable("Modification not allowed on immutable structure");
+    } else {
+      super.addLast(t);
+    }
+  }
+
+  @Override
+  public boolean removeFirstOccurrence(Object o) {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public boolean remove(Object o) {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public T removeFirst() {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public boolean removeAll(Collection<?> c) {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public boolean removeIf(Predicate<? super T> filter) {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public boolean removeLastOccurrence(Object o) {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public T removeLast() {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public T remove() {
+    throw new Unreachable("Modification not allowed on immutable structure");
+  }
+
+  @Override
+  public boolean add(T t) {
+    if (isClosed) {
+      throw new Unreachable("Modification not allowed on immutable structure");
+    } else {
+      return super.add(t);
+    }
+  }
+
+  @Override
+  public boolean addAll(Collection<? extends T> c) {
+    if (isClosed) {
+      throw new Unreachable("Modification not allowed on immutable structure");
+    } else {
+      return super.addAll(c);
+    }
+  }
+
+  @SafeVarargs
+  public static <T> Deque<T> of(T... items) {
+    ImmutableDeque<T> deque = new ImmutableDeque<>(Arrays.asList(items));
+    deque.close();
+    return deque;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ImmutableInt2ReferenceSortedMap.java b/src/main/java/com/android/tools/r8/utils/collections/ImmutableInt2ReferenceSortedMap.java
index af41bf1..a4cb27e 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ImmutableInt2ReferenceSortedMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ImmutableInt2ReferenceSortedMap.java
@@ -13,7 +13,6 @@
 import java.util.Map;
 import java.util.function.BiFunction;
 import java.util.function.Function;
-import org.jetbrains.annotations.Nullable;
 
 public class ImmutableInt2ReferenceSortedMap<V> extends Int2ReferenceSortedMaps.EmptySortedMap<V> {
 
@@ -31,6 +30,10 @@
     return new ImmutableInt2ReferenceSortedMap<>(new Int2ReferenceAVLTreeMap<>(keys, values));
   }
 
+  public static <V> ImmutableInt2ReferenceSortedMap<V> empty() {
+    return new ImmutableInt2ReferenceSortedMap<>(new Int2ReferenceAVLTreeMap<>());
+  }
+
   public static <V> Builder<V> builder() {
     return new Builder<>();
   }
@@ -50,6 +53,21 @@
   }
 
   @Override
+  public V get(int k) {
+    return sortedMap.get(k);
+  }
+
+  @Override
+  public V get(Object ok) {
+    return sortedMap.get(ok);
+  }
+
+  @Override
+  public V getOrDefault(Object key, V defaultValue) {
+    return sortedMap.getOrDefault(key, defaultValue);
+  }
+
+  @Override
   public int size() {
     return sortedMap.size();
   }
@@ -150,7 +168,6 @@
     throw new Unreachable("Should not modify an immutable structure");
   }
 
-  @Nullable
   @Override
   public V putIfAbsent(Integer key, V value) {
     throw new Unreachable("Should not modify an immutable structure");
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
index 0732bcb..2412cfd 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
@@ -4,9 +4,11 @@
 
 package com.android.tools.r8.utils.collections;
 
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
@@ -15,26 +17,34 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
 import java.util.stream.Stream;
 
 public class ProgramMethodSet implements Iterable<ProgramMethod> {
 
-  private static final ProgramMethodSet EMPTY = new ProgramMethodSet(ImmutableMap.of());
+  private static final ProgramMethodSet EMPTY = new ProgramMethodSet(ImmutableMap::of);
 
-  private Map<DexMethod, ProgramMethod> backing;
+  private final Map<DexMethod, ProgramMethod> backing;
+  private final Supplier<? extends Map<DexMethod, ProgramMethod>> backingFactory;
 
-  ProgramMethodSet(Map<DexMethod, ProgramMethod> backing) {
+  protected ProgramMethodSet(Supplier<? extends Map<DexMethod, ProgramMethod>> backingFactory) {
+    this(backingFactory, backingFactory.get());
+  }
+
+  protected ProgramMethodSet(
+      Supplier<? extends Map<DexMethod, ProgramMethod>> backingFactory,
+      Map<DexMethod, ProgramMethod> backing) {
     this.backing = backing;
+    this.backingFactory = backingFactory;
   }
 
   public static ProgramMethodSet create() {
-    return new ProgramMethodSet(new IdentityHashMap<>());
+    return new ProgramMethodSet(IdentityHashMap::new);
   }
 
   public static ProgramMethodSet create(int capacity) {
-    return new ProgramMethodSet(new IdentityHashMap<>(capacity));
+    return new ProgramMethodSet(IdentityHashMap::new, new IdentityHashMap<>(capacity));
   }
 
   public static ProgramMethodSet create(ProgramMethod element) {
@@ -44,15 +54,11 @@
   }
 
   public static ProgramMethodSet createConcurrent() {
-    return new ProgramMethodSet(new ConcurrentHashMap<>());
+    return new ProgramMethodSet(ConcurrentHashMap::new);
   }
 
   public static ProgramMethodSet createLinked() {
-    return new ProgramMethodSet(new LinkedHashMap<>());
-  }
-
-  public static ProgramMethodSet createSorted() {
-    return new ProgramMethodSet(new TreeMap<>(DexMethod::slowCompareTo));
+    return new ProgramMethodSet(LinkedHashMap::new);
   }
 
   public static ProgramMethodSet empty() {
@@ -107,6 +113,18 @@
     return remove(method.getReference());
   }
 
+  public ProgramMethodSet rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens) {
+    ProgramMethodSet rewritten = new ProgramMethodSet(backingFactory);
+    forEach(
+        method -> {
+          ProgramMethod newMethod = lens.mapProgramMethod(method, definitions);
+          if (newMethod != null) {
+            rewritten.add(newMethod);
+          }
+        });
+    return rewritten;
+  }
+
   public int size() {
     return backing.size();
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
index 4c6db49..6f6d1a5 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.utils.collections;
 
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.ForEachable;
 import com.android.tools.r8.utils.ForEachableUtils;
@@ -13,11 +15,12 @@
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.function.Supplier;
 
 public class SortedProgramMethodSet extends ProgramMethodSet {
 
-  private SortedProgramMethodSet(TreeMap<DexMethod, ProgramMethod> backing) {
-    super(backing);
+  private SortedProgramMethodSet(Supplier<TreeMap<DexMethod, ProgramMethod>> backingFactory) {
+    super(backingFactory);
   }
 
   public static SortedProgramMethodSet create() {
@@ -32,12 +35,19 @@
 
   public static SortedProgramMethodSet create(ForEachable<ProgramMethod> methods) {
     SortedProgramMethodSet result =
-        new SortedProgramMethodSet(new TreeMap<>(DexMethod::slowCompareTo));
+        new SortedProgramMethodSet(() -> new TreeMap<>(DexMethod::slowCompareTo));
     methods.forEach(result::add);
     return result;
   }
 
   @Override
+  public SortedProgramMethodSet rewrittenWithLens(
+      DexDefinitionSupplier definitions, GraphLens lens) {
+    return create(
+        consumer -> forEach(method -> consumer.accept(lens.mapProgramMethod(method, definitions))));
+  }
+
+  @Override
   public Set<DexEncodedMethod> toDefinitionSet() {
     Comparator<DexEncodedMethod> comparator =
         (x, y) -> x.getReference().slowCompareTo(y.getReference());
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 2ae20c9..cea34f9 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -310,12 +310,7 @@
             .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
             .setOutput(outputJar, OutputMode.ClassFile)
             .build();
-    ToolHelper.runR8(
-        command,
-        options -> {
-          options.skipIR = true;
-          options.testing.readInputStackMaps = true;
-        });
+    ToolHelper.runR8(command, options -> options.skipIR = true);
     ArchiveClassFileProvider expected = new ArchiveClassFileProvider(inputJar);
     ArchiveClassFileProvider actual = new ArchiveClassFileProvider(outputJar);
     assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index eddf1ee..d9ed43f 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1193,6 +1193,7 @@
   private static Map<String, Consumer<InternalOptions>> configurations =
       ImmutableMap.of(
           // Has a new-instance instruction that attempts to instantiate an interface.
+          "162-method-resolution", options -> options.testing.disableStackMapVerification = true,
           "435-new-instance", options -> options.testing.allowTypeErrors = true);
 
   private static List<String> failuresToTriage = ImmutableList.of(
@@ -1790,7 +1791,7 @@
           builder
               .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault()));
         }
-        D8.run(builder.build());
+          ToolHelper.runD8(builder, compilationOptions::accept);
         break;
       }
       case R8:
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 8b19ae3..4e8d99e 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -106,7 +106,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 103, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -146,7 +146,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 103, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index ab5027b..3935988 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -282,6 +282,7 @@
     return assertDiagnosticThatMatches(getInfos(), "info", matcher);
   }
 
+  @Override
   public TestDiagnosticMessages assertWarningThatMatches(Matcher<Diagnostic> matcher) {
     return assertDiagnosticThatMatches(getWarnings(), "warning", matcher);
   }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 98881e9..69a42a4 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -261,6 +261,11 @@
     return addKeepRules("-keepattributes " + String.join(",", attributes));
   }
 
+  public T addKeepAttributeInnerClassesAndEnclosingMethod() {
+    return addKeepAttributes(
+        ProguardKeepAttributes.INNER_CLASSES, ProguardKeepAttributes.ENCLOSING_METHOD);
+  }
+
   public T addKeepAttributeLineNumberTable() {
     return addKeepAttributes(ProguardKeepAttributes.LINE_NUMBER_TABLE);
   }
@@ -273,6 +278,10 @@
     return addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS);
   }
 
+  public T addKeepRuntimeVisibleParameterAnnotations() {
+    return addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS);
+  }
+
   public T addKeepAllAttributes() {
     return addKeepAttributes("*");
   }
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java
new file mode 100644
index 0000000..625a3dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest.java
@@ -0,0 +1,262 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf.stackmap;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.JvmTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class StackMapVerificationNoFrameForHandlerTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean includeFrameInHandler;
+  private final String EXPECTED_OUTPUT = "Hello World!";
+  private final String EXPECTED_VERIFY_ERROR =
+      "Expected stack map table for method with non-linear control flow";
+  private final String EXPECTED_JVM_ERROR =
+      "java.lang.VerifyError: Expecting a stackmap frame at branch target";
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public StackMapVerificationNoFrameForHandlerTest(
+      TestParameters parameters, boolean includeFrameInHandler) {
+    this.parameters = parameters;
+    this.includeFrameInHandler = includeFrameInHandler;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    JvmTestRunResult mainResult =
+        testForJvm()
+            .addProgramClassFileData(
+                includeFrameInHandler
+                    ? MainDump.dump()
+                    : transformer(MainDump.dump(), Reference.classFromClass(Main.class))
+                        .stripFrames("main")
+                        .transform())
+            .run(parameters.getRuntime(), Main.class);
+    if (includeFrameInHandler) {
+      mainResult.assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+    } else {
+      mainResult.assertFailureWithErrorThatMatches(containsString(EXPECTED_JVM_ERROR));
+    }
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClassFileData(
+            includeFrameInHandler
+                ? MainDump.dump()
+                : transformer(MainDump.dump(), Reference.classFromClass(Main.class))
+                    .stripFrames("main")
+                    .transform())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(this::verifyWarningsRegardingStackMap)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testHandlerR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(
+            includeFrameInHandler
+                ? MainDump.dump()
+                : transformer(MainDump.dump(), Reference.classFromClass(Main.class))
+                    .stripFrames("main")
+                    .transform())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .allowDiagnosticWarningMessages(!includeFrameInHandler)
+        .compileWithExpectedDiagnostics(this::verifyWarningsRegardingStackMap)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+
+  private void verifyWarningsRegardingStackMap(TestDiagnosticMessages diagnostics) {
+    if (includeFrameInHandler) {
+      diagnostics.assertNoMessages();
+    } else {
+      diagnostics.assertOnlyWarnings();
+      diagnostics.assertWarningsMatch(diagnosticMessage(containsString(EXPECTED_VERIFY_ERROR)));
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      try {
+        getThrowable(new Throwable());
+      } catch (Throwable e) {
+      }
+      System.out.println("Hello World!");
+    }
+
+    public static Throwable getThrowable(Throwable throwable) {
+      if (System.currentTimeMillis() > 0) {
+        return new RuntimeException(throwable);
+      } else {
+        throw new ClassCastException();
+      }
+    }
+  }
+
+  /**
+   * The dump is mostly the code obtained from the Main class above, however, some instructions are
+   * removed to have the frames being the same with linear flow:
+   *
+   * <pre>
+   * try {
+   * getThrowable(new Throwable());
+   * pop
+   * goto lbl3
+   * } catch (Throwable e) {
+   *   astore(1);
+   *   goto lbl3
+   * }
+   * lbl3
+   * System.out.println("Hello World!");
+   * </pre>
+   *
+   * becomes:
+   *
+   * <pre>
+   * try {
+   * getThrowable(new Throwable());
+   * } catch (Throwable e) {
+   *   pop;
+   * }
+   * lbl3
+   * System.out.println("Hello World!");
+   * </pre>
+   */
+  public static class MainDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_PUBLIC | ACC_SUPER,
+          "com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest$Main",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest$Main",
+          "com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest",
+          "Main",
+          ACC_PUBLIC | ACC_STATIC);
+
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        Label label1 = new Label();
+        Label label2 = new Label();
+        methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Throwable");
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitTypeInsn(NEW, "java/lang/Throwable");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Throwable", "<init>", "()V", false);
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "com/android/tools/r8/cf/stackmap/StackMapVerificationNoFrameForHandlerTest$Main",
+            "getThrowable",
+            "(Ljava/lang/Throwable;)Ljava/lang/Throwable;",
+            false);
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
+        methodVisitor.visitInsn(POP);
+        Label label3 = new Label();
+        methodVisitor.visitLabel(label3);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn("Hello World!");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(2, 2);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC,
+                "getThrowable",
+                "(Ljava/lang/Throwable;)Ljava/lang/Throwable;",
+                null,
+                null);
+        methodVisitor.visitCode();
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
+        methodVisitor.visitInsn(LCONST_0);
+        methodVisitor.visitInsn(LCMP);
+        Label label0 = new Label();
+        methodVisitor.visitJumpInsn(IFLE, label0);
+        methodVisitor.visitTypeInsn(NEW, "java/lang/RuntimeException");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL,
+            "java/lang/RuntimeException",
+            "<init>",
+            "(Ljava/lang/Throwable;)V",
+            false);
+        methodVisitor.visitInsn(ARETURN);
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        methodVisitor.visitTypeInsn(NEW, "java/lang/ClassCastException");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL, "java/lang/ClassCastException", "<init>", "()V", false);
+        methodVisitor.visitInsn(ATHROW);
+        methodVisitor.visitMaxs(4, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index e190074..164259f 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -28,7 +28,6 @@
 import java.util.Calendar;
 import java.util.List;
 import java.util.TreeSet;
-import java.util.function.Consumer;
 
 public abstract class MethodGenerationBase extends TestBase {
 
@@ -75,18 +74,17 @@
   }
 
   // Running this method will regenerate / overwrite the content of the generated class.
-  protected void generateMethodsAndWriteThemToFile(Consumer<InternalOptions> optionsConsumer)
-      throws IOException {
-    FileUtils.writeToFile(getGeneratedFile(), null, generateMethods(optionsConsumer).getBytes());
+  protected void generateMethodsAndWriteThemToFile() throws IOException {
+    FileUtils.writeToFile(getGeneratedFile(), null, generateMethods().getBytes());
   }
 
   // Running this method generate the content of the generated class but does not overwrite it.
-  protected String generateMethods(Consumer<InternalOptions> optionsConsumer) throws IOException {
+  protected String generateMethods() throws IOException {
     CfCodePrinter codePrinter = new CfCodePrinter();
 
     File tempFile = File.createTempFile("output-", ".java");
 
-    readMethodTemplatesInto(codePrinter, optionsConsumer);
+    readMethodTemplatesInto(codePrinter);
     generateRawOutput(codePrinter, tempFile.toPath());
     String result = formatRawOutput(tempFile.toPath());
 
@@ -94,10 +92,8 @@
     return result;
   }
 
-  private void readMethodTemplatesInto(
-      CfCodePrinter codePrinter, Consumer<InternalOptions> optionsConsumer) throws IOException {
+  private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException {
     InternalOptions options = new InternalOptions();
-    optionsConsumer.accept(options);
     JarClassFileReader reader =
         new JarClassFileReader(
             new JarApplicationReader(options),
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
new file mode 100644
index 0000000..316d547
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.readsInstanceField;
+import static com.android.tools.r8.utils.codeinspector.Matchers.writesInstanceField;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.horizontalclassmerging.ClassMerger;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+
+public class ConstructorMergingOverlapTest extends HorizontalClassMergingTestBase {
+
+  public ConstructorMergingOverlapTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("42", "13", "7", "print a", "print b")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                ClassSubject aClassSubject = codeInspector.clazz(A.class);
+                assertThat(aClassSubject, isPresent());
+                FieldSubject classIdFieldSubject =
+                    aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
+                assertThat(classIdFieldSubject, isPresent());
+
+                MethodSubject firstInitSubject = aClassSubject.init("int");
+                assertThat(firstInitSubject, isPresent());
+                assertThat(
+                    firstInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+
+                ClassSubject synthesizedClass = getSynthesizedArgumentClassSubject(codeInspector);
+
+                MethodSubject otherInitSubject =
+                    aClassSubject.init("int", synthesizedClass.getFinalName());
+                assertThat(otherInitSubject, isPresent());
+                assertThat(
+                    otherInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+
+                MethodSubject printSubject = aClassSubject.method("void", "print");
+                assertThat(printSubject, isPresent());
+                assertThat(
+                    printSubject, readsInstanceField(classIdFieldSubject.getFieldReference()));
+
+                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+
+                // TODO(b/165517236): Explicitly check classes have been merged.
+              } else {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      this(42);
+    }
+
+    public A(int x) {
+      System.out.println(x);
+    }
+
+    @NeverInline
+    public void print() {
+      System.out.println("print a");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B() {
+      System.out.println(7);
+    }
+
+    @NeverInline
+    public void print() {
+      System.out.println("print b");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      a = new A(13);
+      B b = new B();
+      a.print();
+      b.print();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
new file mode 100644
index 0000000..b7d3a2d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
@@ -0,0 +1,139 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.readsInstanceField;
+import static com.android.tools.r8.utils.codeinspector.Matchers.writesInstanceField;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.horizontalclassmerging.ClassMerger;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+
+public class ConstructorMergingPreoptimizedTest extends HorizontalClassMergingTestBase {
+
+  public ConstructorMergingPreoptimizedTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "changed", "13", "42", "foo", "7", "foo", "print a", "print b")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                ClassSubject changedClassSubject = codeInspector.clazz(Changed.class);
+                assertThat(changedClassSubject, isPresent());
+
+                ClassSubject aClassSubject = codeInspector.clazz(A.class);
+                assertThat(aClassSubject, isPresent());
+                FieldSubject classIdFieldSubject =
+                    aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
+                assertThat(classIdFieldSubject, isPresent());
+
+                MethodSubject firstInitSubject = aClassSubject.init("int");
+                assertThat(firstInitSubject, isPresent());
+                assertThat(
+                    firstInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+
+                MethodSubject otherInitSubject =
+                    aClassSubject.init(changedClassSubject.getFinalName(), "int");
+                assertThat(otherInitSubject, isPresent());
+                assertThat(
+                    otherInitSubject, writesInstanceField(classIdFieldSubject.getFieldReference()));
+
+                MethodSubject printSubject = aClassSubject.method("void", "print");
+                assertThat(printSubject, isPresent());
+                assertThat(
+                    printSubject, readsInstanceField(classIdFieldSubject.getFieldReference()));
+
+                assertThat(codeInspector.clazz(B.class), not(isPresent()));
+
+                // TODO(b/165517236): Explicitly check classes have been merged.
+              } else {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class Parent {
+    @NeverInline
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class Changed extends Parent {
+    public Changed() {
+      System.out.println("changed");
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A(Parent p) {
+      System.out.println(42);
+      p.foo();
+    }
+
+    public A(int x) {
+      System.out.println(x);
+    }
+
+    @NeverInline
+    public void print() {
+      System.out.println("print a");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B(Parent p) {
+      System.out.println(7);
+      p.foo();
+    }
+
+    @NeverInline
+    public void print() {
+      System.out.println("print b");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      Parent p = new Changed();
+      A a = new A(13);
+      a = new A(p);
+      B b = new B(p);
+      a.print();
+      b.print();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingWithArgumentsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingWithArgumentsTest.java
index a1b2d1a..a4a0fd9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingWithArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingWithArgumentsTest.java
@@ -10,6 +10,8 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 
 public class ConstructorMergingWithArgumentsTest extends HorizontalClassMergingTestBase {
@@ -32,8 +34,13 @@
         .inspect(
             codeInspector -> {
               if (enableHorizontalClassMerging) {
-                assertThat(codeInspector.clazz(A.class), isPresent());
+                ClassSubject aClassSubject = codeInspector.clazz(A.class);
+
+                assertThat(aClassSubject, isPresent());
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
+
+                MethodSubject initSubject = aClassSubject.init(String.class.getName(), "int");
+                assertThat(initSubject, isPresent());
                 // TODO(b/165517236): Explicitly check classes have been merged.
               } else {
                 assertThat(codeInspector.clazz(A.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
index 72e016b..d84c164 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
@@ -6,7 +6,10 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.List;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -27,4 +30,16 @@
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
+
+  protected ClassSubject getSynthesizedArgumentClassSubject(CodeInspector codeInspector) {
+    return codeInspector.allClasses().stream()
+        .filter(
+            clazz ->
+                clazz.isSynthetic()
+                    && clazz
+                        .getOriginalName()
+                        .endsWith(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX))
+        .findFirst()
+        .get();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
new file mode 100644
index 0000000..4a50200
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.A;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.B;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.Main;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class PreventMergeMainDexListTest extends HorizontalClassMergingTestBase {
+  public PreventMergeMainDexListTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDexRuntimes()
+            .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+            .build(),
+        BooleanUtils.values());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepClassAndMembersRules(Main.class)
+        .addMainDexListClasses(A.class, Main.class)
+        .addOptionsModification(
+            options -> {
+              options.enableHorizontalClassMerging = enableHorizontalClassMerging;
+              options.minimalMainDex = true;
+            })
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .apply(this::checkCompileResult)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("main dex");
+  }
+
+  private void checkCompileResult(R8TestCompileResult compileResult) throws Exception {
+    Path out = temp.newFolder().toPath();
+    compileResult.app.writeToDirectory(out, OutputMode.DexIndexed);
+    Path classes = out.resolve("classes.dex");
+    Path classes2 = out.resolve("classes2.dex");
+    inspectMainDex(new CodeInspector(classes, compileResult.getProguardMap()));
+    inspectSecondaryDex(new CodeInspector(classes2, compileResult.getProguardMap()));
+  }
+
+  private void inspectMainDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(B.class), not(isPresent()));
+  }
+
+  private void inspectSecondaryDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class), not(isPresent()));
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+    }
+
+    public static void otherDex() {
+      B b = new B();
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("main dex");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B() {
+      System.out.println("not main dex");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java
new file mode 100644
index 0000000..9e81bc6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.PreventMergeMainDexListTest.Main;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class PreventMergeMainDexTracingTest extends HorizontalClassMergingTestBase {
+  public PreventMergeMainDexTracingTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDexRuntimes()
+            .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+            .build(),
+        BooleanUtils.values());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(Other.class)
+        .addMainDexClassRules(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.enableHorizontalClassMerging = enableHorizontalClassMerging;
+              options.minimalMainDex = true;
+            })
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .apply(this::checkCompileResult)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("main dex");
+  }
+
+  private void checkCompileResult(R8TestCompileResult compileResult) throws Exception {
+    Path out = temp.newFolder().toPath();
+    compileResult.app.writeToDirectory(out, OutputMode.DexIndexed);
+    Path classes = out.resolve("classes.dex");
+    Path classes2 = out.resolve("classes2.dex");
+    inspectMainDex(new CodeInspector(classes, compileResult.getProguardMap()));
+    inspectSecondaryDex(new CodeInspector(classes2, compileResult.getProguardMap()));
+  }
+
+  private void inspectMainDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(B.class), not(isPresent()));
+  }
+
+  private void inspectSecondaryDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(A.class), not(isPresent()));
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  public static class Other {
+    public static void otherDex() {
+      B b = new B();
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("main dex");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B() {
+      System.out.println("not main dex");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
new file mode 100644
index 0000000..e5c77a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+
+public class SynchronizedClassesTest extends HorizontalClassMergingTestBase {
+  public SynchronizedClassesTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.enableHorizontalClassMerging = enableHorizontalClassMerging;
+            })
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("foo", "b", "bar", "1", "true")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(codeInspector.clazz(B.class), isPresent());
+              if (enableHorizontalClassMerging) {
+                // C has been merged into A.
+                assertThat(codeInspector.clazz(C.class), not(isPresent()));
+                assertThat(codeInspector.clazz(A.class).init("long"), isPresent());
+
+                // D has been merged into B.
+                assertThat(codeInspector.clazz(D.class), not(isPresent()));
+                ClassSubject bClassSubject = codeInspector.clazz(B.class);
+                assertThat(bClassSubject.init("boolean"), isPresent());
+              } else {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    static synchronized void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B(String foo) {
+      System.out.println(foo);
+    }
+
+    @NeverInline
+    void bar() {
+      synchronized (B.class) {
+        System.out.println("bar");
+      }
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+    public C(long v) {
+      System.out.println(v);
+    }
+  }
+
+  @NeverClassInline
+  public static class D {
+    public D(boolean v) {
+      System.out.println(v);
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      A.foo();
+      B b = new B("b");
+      b.bar();
+      C c = new C(1);
+      D d = new D(true);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/code/PassThroughTest.java b/src/test/java/com/android/tools/r8/code/PassThroughTest.java
index 673f3a2..cb93c83 100644
--- a/src/test/java/com/android/tools/r8/code/PassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/code/PassThroughTest.java
@@ -91,7 +91,6 @@
             internalOptions -> {
               internalOptions.testing.cfByteCodePassThrough =
                   method -> !method.name.toString().equals("<init>");
-              internalOptions.testing.readInputStackMaps = true;
             })
         .compile()
         .writeToZip(outputJar)
diff --git a/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
index 20c7e0e..6a6e8d7 100644
--- a/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
@@ -69,7 +69,7 @@
   private Path toCompile;
   private List<Path> classpath;
 
-  public  BasicTestDependenciesDesugaringTest(String name, String toCompile, String classpath) {
+  public BasicTestDependenciesDesugaringTest(String name, String toCompile, String classpath) {
     this.name = name;
     this.toCompile = Paths.get(toCompile);
     this.classpath = Arrays.asList(classpath.split(CLASSPATH_SEPARATOR)).stream()
@@ -87,7 +87,10 @@
             .addProgramFiles(toCompile)
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
             .setMinApiLevel(AndroidApiLevel.K.getLevel()),
-        options -> options.interfaceMethodDesugaring = OffOrAuto.Auto);
+        options -> {
+          options.interfaceMethodDesugaring = OffOrAuto.Auto;
+          options.testing.disableStackMapVerification = name.equals("espresso-core-3.0.0.jar");
+        });
   }
 
   @Test
@@ -101,6 +104,9 @@
             .addProgramFiles(toCompile)
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
             .setMinApiLevel(AndroidApiLevel.K.getLevel()),
-        options -> options.interfaceMethodDesugaring = OffOrAuto.Off);
+        options -> {
+          options.interfaceMethodDesugaring = OffOrAuto.Off;
+          options.testing.disableStackMapVerification = name.equals("espresso-core-3.0.0.jar");
+        });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
new file mode 100644
index 0000000..99b1316
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -0,0 +1,250 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.L8Command;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class BufferedReaderTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withAllRuntimes()
+            .withAllApiLevelsAlsoForCf()
+            .withApiLevel(AndroidApiLevel.N)
+            .build());
+  }
+
+  public BufferedReaderTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  private String expectedOutput() {
+    return StringUtils.lines(
+        "Hello",
+        "Larry",
+        "Page",
+        parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
+            ? "Caught java.io.UncheckedIOException"
+            : "Caught j$.io.UncheckedIOException");
+  }
+
+  DesugaredLibraryConfiguration configurationWithBufferedReader(
+      InternalOptions options, boolean libraryCompilation, TestParameters parameters) {
+    // Parse the current configuration and amend the configuration for BufferedReader.lines. The
+    // configuration is the same for both program and library.
+    return new DesugaredLibraryConfigurationParser(
+            options.dexItemFactory(),
+            options.reporter,
+            libraryCompilation,
+            parameters.getApiLevel().getLevel())
+        .parse(
+            StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING),
+            builder -> {
+              if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+                builder.putRewritePrefix(
+                    "java.io.DesugarBufferedReader", "j$.io.DesugarBufferedReader");
+                builder.putRewritePrefix(
+                    "java.io.UncheckedIOException", "j$.io.UncheckedIOException");
+                builder.putRetargetCoreLibMember(
+                    "java.io.BufferedReader#lines", "java.io.DesugarBufferedReader");
+              }
+            });
+  }
+
+  private void configurationForProgramCompilation(InternalOptions options) {
+    options.desugaredLibraryConfiguration =
+        configurationWithBufferedReader(options, false, parameters);
+  }
+
+  private void configurationForLibraryCompilation(InternalOptions options) {
+    options.desugaredLibraryConfiguration =
+        configurationWithBufferedReader(options, true, parameters);
+  }
+
+  @Test
+  public void testBufferedReaderD8Cf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addOptionsModification(this::configurationForProgramCompilation)
+            .addInnerClasses(BufferedReaderTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            // .inspect(this::checkRewrittenInvokes)
+            .writeToZip();
+
+    if (parameters.getRuntime().isDex()) {
+      // Collection keep rules is only implemented in the DEX writer.
+      String desugaredLibraryKeepRules = keepRuleConsumer.get();
+      if (desugaredLibraryKeepRules != null) {
+        assertEquals(0, desugaredLibraryKeepRules.length());
+        desugaredLibraryKeepRules = "-keep class * { *; }";
+      }
+
+      // Convert to DEX without desugaring and run.
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              (apiLevel, keepRules, shrink) ->
+                  buildDesugaredLibrary(
+                      apiLevel,
+                      keepRules,
+                      shrink,
+                      ImmutableList.of(),
+                      this::configurationForLibraryCompilation),
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(expectedOutput());
+    } else {
+      // Build the desugared library in class file format.
+      Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
+      L8Command.Builder l8Builder =
+          L8Command.builder()
+              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+              .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+              .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+              .setMode(CompilationMode.DEBUG)
+              .addDesugaredLibraryConfiguration(
+                  StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+              .setMinApiLevel(parameters.getApiLevel().getLevel())
+              .setOutput(desugaredLib, OutputMode.ClassFile);
+      ToolHelper.runL8(l8Builder.build(), this::configurationForLibraryCompilation);
+
+      // Run on the JVM with desuagred library on classpath.
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(desugaredLib)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(expectedOutput());
+    }
+  }
+
+  @Test
+  public void testBufferedReaderD8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addOptionsModification(
+            options ->
+                options.desugaredLibraryConfiguration =
+                    configurationWithBufferedReader(options, false, parameters))
+        .addInnerClasses(BufferedReaderTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            (apiLevel, keepRules, shrink) ->
+                buildDesugaredLibrary(
+                    apiLevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    this::configurationForLibraryCompilation),
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(expectedOutput());
+  }
+
+  @Test
+  public void testBufferedReaderR8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addOptionsModification(
+            options ->
+                options.desugaredLibraryConfiguration =
+                    configurationWithBufferedReader(options, false, parameters))
+        .addInnerClasses(BufferedReaderTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .enableInliningAnnotations()
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            (apiLevel, keepRules, shrink) ->
+                buildDesugaredLibrary(
+                    apiLevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    this::configurationForLibraryCompilation),
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(expectedOutput());
+  }
+
+  static class TestClass {
+
+    @NeverInline
+    public static void testBufferedReaderLines() throws Exception {
+      try (BufferedReader reader = new BufferedReader(new StringReader("Hello\nLarry\nPage"))) {
+        reader.lines().forEach(System.out::println);
+      }
+    }
+
+    @NeverInline
+    public static void testBufferedReaderLines_uncheckedIoException() throws Exception {
+      BufferedReader reader = new BufferedReader(new StringReader(""));
+      reader.close();
+      try {
+        reader.lines().count();
+        System.out.println("UncheckedIOException expected");
+      } catch (UncheckedIOException expected) {
+        System.out.println("Caught " + expected.getClass().getName());
+      } catch (Throwable t) {
+        System.out.println("Caught unexpected" + t.getClass().getName());
+      }
+    }
+
+    public static void main(String[] args) throws Exception {
+      testBufferedReaderLines();
+      testBufferedReaderLines_uncheckedIoException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index b68997a..e520016 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -29,6 +30,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class DesugaredLibraryTestBase extends TestBase {
 
@@ -69,7 +71,7 @@
   }
 
   protected Path buildDesugaredLibrary(AndroidApiLevel apiLevel, String keepRules, boolean shrink) {
-    return buildDesugaredLibrary(apiLevel, keepRules, shrink, ImmutableList.of());
+    return buildDesugaredLibrary(apiLevel, keepRules, shrink, ImmutableList.of(), options -> {});
   }
 
   protected Path buildDesugaredLibrary(
@@ -77,6 +79,16 @@
       String keepRules,
       boolean shrink,
       List<Path> additionalProgramFiles) {
+    return buildDesugaredLibrary(
+        apiLevel, keepRules, shrink, additionalProgramFiles, options -> {});
+  }
+
+  protected Path buildDesugaredLibrary(
+      AndroidApiLevel apiLevel,
+      String keepRules,
+      boolean shrink,
+      List<Path> additionalProgramFiles,
+      Consumer<InternalOptions> optionsModifier) {
     // We wrap exceptions in a RuntimeException to call this from a lambda.
     try {
       // If we compile extended library here, it means we use TestNG.
@@ -108,6 +120,7 @@
               options.testing.disableL8AnnotationRemoval = true;
               options.testing.forceLibBackportsInL8CfToCf = true;
             }
+            optionsModifier.accept(options);
           });
       if (!extraFiles) {
         assertTrue(
@@ -188,6 +201,7 @@
       stringBuilder.append(string);
     }
 
+    @Override
     public void finished(DiagnosticsHandler handler) {
       assert stringBuilder != null;
       assert result == null;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index fa6e974..6db1591 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -8,19 +8,24 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.L8Command;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.PrintUses;
 import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingSupplier;
 import com.android.tools.r8.utils.codeinspector.CheckCastInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -29,11 +34,14 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.android.tools.r8.utils.codeinspector.TryCatchSubject;
 import com.android.tools.r8.utils.codeinspector.TypeSubject;
+import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import kotlin.text.Charsets;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,6 +53,7 @@
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
+  private final boolean printUsesKeepRules;
   private static final String expectedOutput =
       StringUtils.lines(
           "Caught java.time.format.DateTimeParseException",
@@ -54,10 +63,11 @@
           "GMT",
           "Hello, world");
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "{2}, shrinkDesugaredLibrary: {0}, printUsesKeepRules {1}")
   public static List<Object[]> data() {
     return buildParameters(
         BooleanUtils.values(),
+        BooleanUtils.values(),
         getTestParameters()
             .withAllRuntimes()
             .withAllApiLevelsAlsoForCf()
@@ -65,8 +75,10 @@
             .build());
   }
 
-  public JavaTimeTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+  public JavaTimeTest(
+      boolean shrinkDesugaredLibrary, boolean printUsesKeepRules, TestParameters parameters) {
     this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.printUsesKeepRules = printUsesKeepRules;
     this.parameters = parameters;
   }
 
@@ -121,8 +133,66 @@
     assertEquals(expectedCatchGuards, foundCatchGuards);
   }
 
+  // Build the desugared library in class file format.
+  private Path buildDesugaredLibraryClassFile() throws Exception {
+    Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
+    L8Command.Builder l8Builder =
+        L8Command.builder()
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+            .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+            .setMode(CompilationMode.DEBUG)
+            .addDesugaredLibraryConfiguration(
+                StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+            .setMinApiLevel(parameters.getApiLevel().getLevel())
+            .setOutput(desugaredLib, OutputMode.ClassFile);
+    ToolHelper.runL8(l8Builder.build());
+    return desugaredLib;
+  }
+
+  Supplier<Path> desugaredLibraryClassFile =
+      Suppliers.memoize(
+          () -> {
+            try {
+              return buildDesugaredLibraryClassFile();
+            } catch (Exception e) {
+              fail("Unexpected");
+              return null;
+            }
+          });
+
+  private String collectKeepRulesWithPrintUses(
+      Path desugaredProgramClassFile, Path desugaredLibraryClassFile) throws Exception {
+    Path printUsesKeepRules = temp.newFile().toPath();
+    PrintUses.main(
+        "--keeprules",
+        ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+        desugaredLibraryClassFile.toString(),
+        desugaredProgramClassFile.toString(),
+        printUsesKeepRules.toString());
+    return FileUtils.readTextFile(printUsesKeepRules, Charsets.UTF_8);
+  }
+
+  private String desugaredLibraryKeepRules(
+      KeepRuleConsumer keepRuleConsumer, ThrowingSupplier<Path, Exception> programSupplier)
+      throws Exception {
+    String desugaredLibraryKeepRules = null;
+    if (shrinkDesugaredLibrary) {
+      desugaredLibraryKeepRules = keepRuleConsumer.get();
+      if (desugaredLibraryKeepRules != null) {
+        if (printUsesKeepRules) {
+          desugaredLibraryKeepRules =
+              collectKeepRulesWithPrintUses(programSupplier.get(), desugaredLibraryClassFile.get());
+        }
+      }
+    }
+    return desugaredLibraryKeepRules;
+  }
+
   @Test
   public void testTimeD8Cf() throws Exception {
+    Assume.assumeTrue(shrinkDesugaredLibrary || !printUsesKeepRules);
+
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     // Use D8 to desugar with Java classfile output.
     Path jar =
@@ -134,14 +204,17 @@
             .inspect(this::checkRewrittenInvokes)
             .writeToZip();
 
-    if (parameters.getRuntime().isDex()) {
+    String desugaredLibraryKeepRules;
+    if (shrinkDesugaredLibrary && !printUsesKeepRules && keepRuleConsumer.get() != null) {
       // Collection keep rules is only implemented in the DEX writer.
-      String desugaredLibraryKeepRules = keepRuleConsumer.get();
-      if (desugaredLibraryKeepRules != null) {
-        assertEquals(0, desugaredLibraryKeepRules.length());
-        desugaredLibraryKeepRules = "-keep class * { *; }";
-      }
+      assertEquals(0, keepRuleConsumer.get().length());
+      desugaredLibraryKeepRules = "-keep class * { *; }";
+    } else {
+      desugaredLibraryKeepRules = desugaredLibraryKeepRules(keepRuleConsumer, () -> jar);
+    }
 
+    // Determine desugared library keep rules.
+    if (parameters.getRuntime().isDex()) {
       // Convert to DEX without desugaring and run.
       testForD8()
           .addProgramFiles(jar)
@@ -156,25 +229,11 @@
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutput(expectedOutput);
     } else {
-      // Build the desugared library in class file format.
-      Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
-      L8Command.Builder l8Builder =
-          L8Command.builder()
-              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-              .addProgramFiles(ToolHelper.getDesugarJDKLibs())
-              .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
-              .setMode(CompilationMode.DEBUG)
-              .addDesugaredLibraryConfiguration(
-                  StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
-              .setMinApiLevel(parameters.getApiLevel().getLevel())
-              .setOutput(desugaredLib, OutputMode.ClassFile);
-      ToolHelper.runL8(l8Builder.build());
-
-      // Run on the JVM with desuagred library on classpath.
+      // Run on the JVM with desugared library on classpath.
       TestRunResult<?> result =
           testForJvm()
               .addProgramFiles(jar)
-              .addRunClasspathFiles(desugaredLib)
+              .addRunClasspathFiles(desugaredLibraryClassFile.get())
               .run(parameters.getRuntime(), TestClass.class);
       if (parameters.getApiLevel().isGreaterThan(AndroidApiLevel.N_MR1)) {
         // java.time is present from O, so the desugared library classes are not loaded.
@@ -190,17 +249,21 @@
   @Test
   public void testTimeD8() throws Exception {
     Assume.assumeTrue(parameters.getRuntime().isDex());
+    Assume.assumeTrue(shrinkDesugaredLibrary || !printUsesKeepRules);
+
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForD8()
-        .addInnerClasses(JavaTimeTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .compile()
-        .inspect(this::checkRewrittenInvokes)
+    TestCompileResult<?, ?> result =
+        testForD8()
+            .addInnerClasses(JavaTimeTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .inspect(this::checkRewrittenInvokes);
+    result
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
             parameters.getApiLevel(),
-            keepRuleConsumer.get(),
+            desugaredLibraryKeepRules(keepRuleConsumer, result::writeToZip),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(expectedOutput);
@@ -209,19 +272,23 @@
   @Test
   public void testTimeR8() throws Exception {
     Assume.assumeTrue(parameters.getRuntime().isDex());
+    Assume.assumeTrue(shrinkDesugaredLibrary || !printUsesKeepRules);
+
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForR8(parameters.getBackend())
-        .addInnerClasses(JavaTimeTest.class)
-        .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .enableInliningAnnotations()
-        .compile()
-        .inspect(this::checkRewrittenInvokes)
+    TestCompileResult<?, ?> result =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(JavaTimeTest.class)
+            .addKeepMainRule(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .enableInliningAnnotations()
+            .compile()
+            .inspect(this::checkRewrittenInvokes);
+    result
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
             parameters.getApiLevel(),
-            keepRuleConsumer.get(),
+            desugaredLibraryKeepRules(keepRuleConsumer, result::writeToZip),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(expectedOutput);
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
index 71d5ffb..c72cf47 100644
--- a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
@@ -150,14 +151,7 @@
 
   private DexCode jumboStringProcess(
       DexItemFactory factory, DexString string, Instruction[] instructions) {
-    DexCode code = new DexCode(
-        1,
-        0,
-        0,
-        instructions,
-        new Try[0],
-        null,
-        null);
+    DexCode code = new DexCode(1, 0, 0, instructions, new Try[0], new TryHandler[0], null);
     MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false);
     DexEncodedMethod method =
         new DexEncodedMethod(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
index 4abe533..54a405c 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -51,9 +50,6 @@
     return METHOD_TEMPLATE_CLASSES;
   }
 
-  private static void setReadInputStackMap(InternalOptions options) {
-    options.testing.readInputStackMaps = true;
-  }
 
   @Test
   public void testEnumUtilityMethodsGenerated() throws Exception {
@@ -61,12 +57,10 @@
     sorted.sort(Comparator.comparing(Class::getTypeName));
     assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
     assertEquals(
-        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8),
-        generateMethods(GenerateEnumUnboxingMethods::setReadInputStackMap));
+        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
   }
 
   public static void main(String[] args) throws Exception {
-    new GenerateEnumUnboxingMethods(null)
-        .generateMethodsAndWriteThemToFile(GenerateEnumUnboxingMethods::setReadInputStackMap);
+    new GenerateEnumUnboxingMethods(null).generateMethodsAndWriteThemToFile();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java
index 7c02878..5092831 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
+import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
 public class YouTubeProguardJarVerificationTest extends YouTubeCompilationBase {
@@ -14,11 +16,23 @@
 
   @Test
   public void buildDebugFromProguardJar() throws Exception {
-    runR8AndCheckVerification(CompilationMode.DEBUG, PG_JAR);
+    runAndCheckVerification(
+        CompilerUnderTest.R8,
+        CompilationMode.DEBUG,
+        base + APK,
+        null,
+        options -> options.testing.disableStackMapVerification = true,
+        ImmutableList.of(base + PG_JAR));
   }
 
   @Test
   public void buildReleaseFromProguardJar() throws Exception {
-    runR8AndCheckVerification(CompilationMode.RELEASE, PG_JAR);
+    runAndCheckVerification(
+        CompilerUnderTest.R8,
+        CompilationMode.RELEASE,
+        base + APK,
+        null,
+        options -> options.testing.disableStackMapVerification = true,
+        ImmutableList.of(base + PG_JAR));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index cd417d1..05c11c4 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -69,22 +68,16 @@
     return METHOD_TEMPLATE_CLASSES;
   }
 
-  private static void setReadInputStackMap(InternalOptions options) {
-    options.testing.readInputStackMaps = true;
-  }
-
   @Test
   public void testBackportsGenerated() throws Exception {
     ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
     sorted.sort(Comparator.comparing(Class::getTypeName));
     assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
     assertEquals(
-        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8),
-        generateMethods(GenerateBackportMethods::setReadInputStackMap));
+        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
   }
 
   public static void main(String[] args) throws Exception {
-    new GenerateBackportMethods(null)
-        .generateMethodsAndWriteThemToFile(GenerateBackportMethods::setReadInputStackMap);
+    new GenerateBackportMethods(null).generateMethodsAndWriteThemToFile();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B148366506.java b/src/test/java/com/android/tools/r8/ir/optimize/B148366506.java
index e89e6cd..1fa9b16 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/B148366506.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B148366506.java
@@ -49,7 +49,7 @@
     MethodVisitor methodVisitor;
 
     classWriter.visit(
-        V1_7,
+        V1_6,
         ACC_PUBLIC | ACC_SUPER,
         "d/b/c/e/e/a/b/a",
         null,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
index 3940548..4ca57c1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
@@ -52,7 +52,6 @@
         .addOptionsModification(
             options -> options.enableUninstantiatedTypeOptimizationForInterfaces = true)
         .compile()
-        .disassemble()
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatThrows(NullPointerException.class);
   }
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 74d62e0..23c4628 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -8,12 +8,14 @@
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -453,7 +455,16 @@
     file.readJasmin(new StringReader(builder.toString()), builder.name, false);
     ByteArrayOutputStream out = new ByteArrayOutputStream();
     file.write(out);
-    return out.toByteArray();
+    // Jasmin incorrectly sets super on interfaces: https://sourceforge.net/p/jasmin/bugs/5/
+    return TestBase.transformer(
+            out.toByteArray(), Reference.classFromBinaryName(file.getClassName()))
+        .setAccessFlags(
+            flags -> {
+              if (flags.isInterface()) {
+                flags.unsetSuper();
+              }
+            })
+        .transform();
   }
 
   public ImmutableList.Builder<byte[]> buildClasses(ImmutableList.Builder<byte[]> builder)
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
index 3b35a09..73b42ed 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.kotlin.lambda.b159688129;
 
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
@@ -69,7 +70,7 @@
         .assertSuccessWithOutputLines("3")
         .inspect(
             codeInspector -> {
-              final List<FoundClassSubject> lambdaGroups =
+              List<FoundClassSubject> lambdaGroups =
                   codeInspector.allClasses().stream()
                       .filter(c -> c.getFinalName().contains("LambdaGroup"))
                       .collect(Collectors.toList());
@@ -79,6 +80,7 @@
     ProcessResult processResult =
         ToolHelper.runDex2OatRaw(path, oatFile, parameters.getRuntime().asDex().getVm());
     assertEquals(0, processResult.exitCode);
-    assertThat(processResult.stderr, containsString("Method exceeds compiler instruction limit"));
+    assertThat(
+        processResult.stderr, not(containsString("Method exceeds compiler instruction limit")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
new file mode 100644
index 0000000..3926dea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.lambda.b159688129;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LambdaSplitByCodeCorrectnessTest extends AbstractR8KotlinTestBase {
+
+  private final TestParameters parameters;
+  private final KotlinTargetVersion targetVersion;
+  private final boolean splitGroup;
+
+  @Parameters(name = "{0}, targetVersion: {1}, splitGroup: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+        KotlinTargetVersion.values(),
+        BooleanUtils.values());
+  }
+
+  public LambdaSplitByCodeCorrectnessTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion, boolean splitGroup) {
+    super(targetVersion);
+    this.parameters = parameters;
+    this.targetVersion = targetVersion;
+    this.splitGroup = splitGroup;
+  }
+
+  @Test
+  public void testSplitLambdaGroups() throws Exception {
+    String PKG_NAME = LambdaSplitByCodeCorrectnessTest.class.getPackage().getName();
+    String folder = DescriptorUtils.getBinaryNameFromJavaType(PKG_NAME);
+    CfRuntime cfRuntime =
+        parameters.isCfRuntime() ? parameters.getRuntime().asCf() : TestRuntime.getCheckedInJdk9();
+    Path ktClasses =
+        kotlinc(cfRuntime, KOTLINC, targetVersion)
+            .addSourceFiles(getKotlinFileInTest(folder, "Simple"))
+            .compile();
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addProgramFiles(ktClasses)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(PKG_NAME + ".SimpleKt")
+        .applyIf(
+            splitGroup,
+            b ->
+                b.addOptionsModification(
+                    internalOptions ->
+                        // Setting verificationSizeLimitInBytesOverride = 1 will force a a chain
+                        // having
+                        // only a single implementation method in each.
+                        internalOptions.testing.verificationSizeLimitInBytesOverride =
+                            splitGroup ? 1 : -1))
+        .noMinification()
+        .allowDiagnosticWarningMessages()
+        .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .inspect(
+            codeInspector -> {
+              List<FoundClassSubject> lambdaGroups =
+                  codeInspector.allClasses().stream()
+                      .filter(c -> c.getFinalName().contains("LambdaGroup"))
+                      .collect(Collectors.toList());
+              assertEquals(1, lambdaGroups.size());
+              FoundClassSubject lambdaGroup = lambdaGroups.get(0);
+              List<FoundMethodSubject> invokeChain =
+                  lambdaGroup.allMethods(method -> method.getFinalName().contains("invoke$"));
+              assertEquals(splitGroup ? 5 : 0, invokeChain.size());
+            })
+        .run(parameters.getRuntime(), PKG_NAME + ".SimpleKt")
+        .assertSuccessWithOutputLines("Hello1", "Hello2", "Hello3", "Hello4", "Hello5", "Hello6");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Main.kt b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Main.kt
index 0bbca3c..eed8d91 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Main.kt
@@ -4,13 +4,10 @@
 
 package com.android.tools.r8.kotlin.lambda.b159688129
 
-import com.android.tools.r8.NeverInline
-
 fun main() {
   run ({ arg -> println(arg)}, 3)
 }
 
-@NeverInline
 fun run(param: Function1<Int, Unit>, arg : Int) {
   param.invoke(arg)
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/MainKtDump.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/MainKtDump.java
index 7321fcb..66c64b2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/MainKtDump.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/MainKtDump.java
@@ -112,8 +112,6 @@
               "(Lkotlin/jvm/functions/Function1<-Ljava/lang/Integer;Lkotlin/Unit;>;I)V",
               null);
       {
-        annotationVisitor0 =
-            methodVisitor.visitAnnotation("Lcom/android/tools/r8/NeverInline;", true);
         annotationVisitor0.visitEnd();
       }
       methodVisitor.visitAnnotableParameterCount(2, false);
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Simple.kt b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Simple.kt
new file mode 100644
index 0000000..3fe6e9e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/Simple.kt
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.lambda.b159688129
+
+fun main() {
+  runSimple { println("Hello1")}
+  runSimple { println("Hello2")}
+  runSimple { println("Hello3")}
+  runSimple { println("Hello4")}
+  runSimple { println("Hello5")}
+  runSimple { println("Hello6")}
+}
+
+fun runSimple(cb: () -> Unit) {
+  cb()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/regress/b111960171/B111960171.java b/src/test/java/com/android/tools/r8/regress/b111960171/B111960171.java
index 21d088f..d05520f 100644
--- a/src/test/java/com/android/tools/r8/regress/b111960171/B111960171.java
+++ b/src/test/java/com/android/tools/r8/regress/b111960171/B111960171.java
@@ -65,13 +65,13 @@
   public void disableDex2OatInliningWithTryCatch()
       throws IOException, CompilationFailedException, ExecutionException {
     MethodSubject method = compileTestClassAndGetMethod(AndroidApiLevel.M.getLevel());
-    assertTrue(method.getMethod().getCode().asDexCode().handlers != null);
+    assertTrue(method.getMethod().getCode().asDexCode().handlers.length > 0);
   }
 
   @Test
   public void dontDisableDex2OatInliningWithTryCatch()
       throws IOException, CompilationFailedException, ExecutionException {
     MethodSubject method = compileTestClassAndGetMethod(AndroidApiLevel.N.getLevel());
-    assertTrue(method.getMethod().getCode().asDexCode().handlers == null);
+    assertTrue(method.getMethod().getCode().asDexCode().handlers.length == 0);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java b/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java
index a62622e..397f703 100644
--- a/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java
+++ b/src/test/java/com/android/tools/r8/regress/b113347830/B113347830.java
@@ -3,21 +3,35 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.regress.b113347830;
 
-import com.android.tools.r8.D8;
-import com.android.tools.r8.D8Command;
 import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.origin.Origin;
 import jasmin.ClassFile;
 import java.io.ByteArrayOutputStream;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-public class B113347830 {
+@RunWith(Parameterized.class)
+public class B113347830 extends TestBase {
 
   public static final Class CLASS = B113347830.class;
   public static final String NAME = CLASS.getSimpleName();
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public B113347830(TestParameters parameters) {
+    this.parameters = parameters;
+  }
 
   @Test
   public void test() throws Exception {
@@ -32,11 +46,11 @@
     jasminFile.write(out);
     byte[] bytes = out.toByteArray();
 
-    D8.run(
-        D8Command.builder()
-            .addClassProgramData(bytes, Origin.unknown())
-            .setDisableDesugaring(true)
-            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-            .build());
+    testForD8(Backend.DEX)
+        .addProgramClassFileData(bytes)
+        .setDisableDesugaring(true)
+        .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+        .addOptionsModification(options -> options.testing.disableStackMapVerification = true)
+        .compile();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465Dump.java b/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465Dump.java
index 7a5aaea..51af1ed 100644
--- a/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465Dump.java
+++ b/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465Dump.java
@@ -23,7 +23,7 @@
     MethodVisitor mv;
 
     cw.visit(
-        V1_7, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, CLASS_INTERNAL, null, "java/lang/Object", null);
+        V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, CLASS_INTERNAL, null, "java/lang/Object", null);
 
     {
       fv = cw.visitField(0, "b", "I", null, null);
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
index 0afec27..a2ca851 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
@@ -65,6 +65,7 @@
             .addProgramClasses(CLASSES)
             .addProgramClassFileData(CLASS_BYTES)
             .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(options -> options.testing.readInputStackMaps = false)
             .run(parameters.getRuntime(), MAIN);
     checkResult(result);
   }
@@ -88,6 +89,7 @@
             .treeShaking(treeShake)
             .noMinification()
             .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(options -> options.testing.readInputStackMaps = false)
             .addKeepMainRule(MAIN)
             .run(parameters.getRuntime(), MAIN);
     checkResult(result);
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java
index 8e08452..c7060f0 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageAfterCollisionWithPackagePrivateSignatureTest.java
@@ -4,44 +4,21 @@
 
 package com.android.tools.r8.repackage;
 
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.repackage.testclasses.repackagetest.TestClass;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class RepackageAfterCollisionWithPackagePrivateSignatureTest extends TestBase {
-
-  private static final String REPACKAGE_DIR = "foo";
-
-  private final String flattenPackageHierarchyOrRepackageClasses;
-  private final TestParameters parameters;
-
-  @Parameters(name = "{1}, kind: {0}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
-        getTestParameters().withAllRuntimesAndApiLevels().build());
-  }
+public class RepackageAfterCollisionWithPackagePrivateSignatureTest extends RepackageTestBase {
 
   public RepackageAfterCollisionWithPackagePrivateSignatureTest(
       String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
-    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
-    this.parameters = parameters;
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
   }
 
   @Test
@@ -49,10 +26,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(RepackageAfterCollisionWithPackagePrivateSignatureTest.class)
         .addKeepClassAndMembersRules(TestClass.class)
-        .addKeepRules(
-            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"")
         .addClassObfuscationDictionary("a")
-        .addOptionsModification(options -> options.testing.enableExperimentalRepackaging = true)
+        .apply(this::configureRepackaging)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -62,13 +37,7 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    ClassSubject repackageClassSubject = inspector.clazz(RepackageCandidate.class);
-    assertThat(repackageClassSubject, isPresent());
-    assertEquals(
-        flattenPackageHierarchyOrRepackageClasses.equals(FLATTEN_PACKAGE_HIERARCHY)
-            ? REPACKAGE_DIR + ".a"
-            : REPACKAGE_DIR,
-        repackageClassSubject.getDexProgramClass().getType().getPackageName());
+    assertThat(RepackageCandidate.class, isRepackaged(inspector));
   }
 
   public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
index 1d9718b..2f6574b 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
@@ -4,13 +4,12 @@
 
 package com.android.tools.r8.repackage;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.repackage.testclasses.repackagetest.AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect;
@@ -34,7 +33,6 @@
 import com.android.tools.r8.repackage.testclasses.repackagetest.ReachableClassWithKeptMethodAllowRenaming;
 import com.android.tools.r8.repackage.testclasses.repackagetest.TestClass;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -46,11 +44,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class RepackageTest extends TestBase {
-
-  private static final String FLATTEN_PACKAGE_HIERARCHY = "flattenpackagehierarchy";
-  private static final String REPACKAGE_CLASSES = "repackageclasses";
-  private static final String REPACKAGE_DIR = "foo";
+public class RepackageTest extends RepackageTestBase {
 
   private static final List<String> EXPECTED =
       ImmutableList.of(
@@ -71,9 +65,6 @@
           "ReachableClass.packagePrivateMethod()");
 
   private final boolean allowAccessModification;
-  private final boolean enableExperimentalRepackaging;
-  private final String flattenPackageHierarchyOrRepackageClasses;
-  private final TestParameters parameters;
 
   @Parameters(name = "{3}, allow access modification: {0}, experimental: {1}, kind: {2}")
   public static List<Object[]> data() {
@@ -89,17 +80,15 @@
       boolean enableExperimentalRepackaging,
       String flattenPackageHierarchyOrRepackageClasses,
       TestParameters parameters) {
+    super(enableExperimentalRepackaging, flattenPackageHierarchyOrRepackageClasses, parameters);
     this.allowAccessModification = allowAccessModification;
-    this.enableExperimentalRepackaging = enableExperimentalRepackaging;
-    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
-    this.parameters = parameters;
   }
 
   @Test
   public void testJvm() throws Exception {
     assumeFalse(allowAccessModification);
-    assumeFalse(enableExperimentalRepackaging);
-    assumeTrue(flattenPackageHierarchyOrRepackageClasses.equals(FLATTEN_PACKAGE_HIERARCHY));
+    assumeFalse(isExperimentalRepackaging());
+    assumeTrue(isFlattenPackageHierarchy());
     assumeTrue(parameters.isCfRuntime());
     testForJvm()
         .addTestClasspath()
@@ -113,7 +102,6 @@
         .addProgramFiles(ToolHelper.getClassFilesForTestPackage(TestClass.class.getPackage()))
         .addKeepMainRule(TestClass.class)
         .addKeepRules(
-            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"",
             "-keep class " + KeptClass.class.getTypeName(),
             "-keep,allowobfuscation class " + KeptClassAllowRenaming.class.getTypeName(),
             "-keepclassmembers class " + ReachableClassWithKeptMethod.class.getTypeName() + " {",
@@ -125,9 +113,7 @@
             "  <methods>;",
             "}")
         .allowAccessModification(allowAccessModification)
-        .addOptionsModification(
-            options ->
-                options.testing.enableExperimentalRepackaging = enableExperimentalRepackaging)
+        .apply(this::configureRepackaging)
         .enableInliningAnnotations()
         .enableNoStaticClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -139,23 +125,8 @@
 
   private void inspect(CodeInspector inspector) {
     forEachClass(
-        (clazz, eligibleForRepackaging) -> {
-          ClassSubject subject = inspector.clazz(clazz);
-          assertThat(subject, isPresent());
-          if (eligibleForRepackaging) {
-            assertEquals(
-                clazz.getTypeName(),
-                flattenPackageHierarchyOrRepackageClasses.equals(FLATTEN_PACKAGE_HIERARCHY)
-                    ? REPACKAGE_DIR + ".a"
-                    : REPACKAGE_DIR,
-                subject.getDexProgramClass().getType().getPackageName());
-          } else {
-            assertEquals(
-                clazz.getTypeName(),
-                RepackageTest.class.getPackage().getName() + ".testclasses.repackagetest",
-                subject.getDexProgramClass().getType().getPackageName());
-          }
-        });
+        (clazz, eligibleForRepackaging) ->
+            assertThat(clazz, isRepackagedIf(inspector, eligibleForRepackaging)));
   }
 
   /**
@@ -167,7 +138,7 @@
     //  the consumer, since these classes should be repackaged independent of
     //  -allowaccessmodification.
     Consumer<Class<?>> markShouldAlwaysBeEligible =
-        clazz -> consumer.accept(clazz, allowAccessModification || enableExperimentalRepackaging);
+        clazz -> consumer.accept(clazz, allowAccessModification || isExperimentalRepackaging());
     Consumer<Class<?>> markEligibleWithAllowAccessModification =
         clazz -> consumer.accept(clazz, allowAccessModification);
 
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java b/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
new file mode 100644
index 0000000..8eb3d77
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
@@ -0,0 +1,156 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.runners.Parameterized.Parameters;
+
+public abstract class RepackageTestBase extends TestBase {
+
+  private final boolean enableExperimentalRepackaging;
+  private final String flattenPackageHierarchyOrRepackageClasses;
+  protected final TestParameters parameters;
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public RepackageTestBase(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    this(true, flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  public RepackageTestBase(
+      boolean enableExperimentalRepackaging,
+      String flattenPackageHierarchyOrRepackageClasses,
+      TestParameters parameters) {
+    this.enableExperimentalRepackaging = enableExperimentalRepackaging;
+    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
+    this.parameters = parameters;
+  }
+
+  protected String getRepackagePackage() {
+    return "foo";
+  }
+
+  protected Matcher<Class<?>> isRepackaged(CodeInspector inspector) {
+    return isRepackagedAsExpected(inspector, null, true);
+  }
+
+  protected Matcher<Class<?>> isRepackagedIf(
+      CodeInspector inspector, boolean eligibleForRepackaging) {
+    return isRepackagedAsExpected(inspector, null, eligibleForRepackaging);
+  }
+
+  /**
+   * Checks that the class of interest is repackaged as expected.
+   *
+   * <p>If building with -repackageclasses, it is checked that the given class of interest is
+   * repackaged into "foo" (unless getRepackagePackage() is overridden). In this case, {@param
+   * packageName} is unused.
+   *
+   * <p>If building with -flattenpackagehierarchy, it is checked that the given class is repackaged
+   * into "foo.<packageName>".
+   */
+  protected Matcher<Class<?>> isRepackagedAsExpected(CodeInspector inspector, String packageName) {
+    return isRepackagedAsExpected(inspector, packageName, true);
+  }
+
+  private Matcher<Class<?>> isRepackagedAsExpected(
+      CodeInspector inspector, String packageName, boolean eligibleForRepackaging) {
+    return new TypeSafeMatcher<Class<?>>() {
+      @Override
+      public boolean matchesSafely(Class<?> clazz) {
+        ClassSubject classSubject = inspector.clazz(clazz);
+        if (!classSubject.isPresent()) {
+          return false;
+        }
+        return getActualPackage(classSubject).equals(getExpectedPackage(clazz));
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        if (eligibleForRepackaging) {
+          description.appendText(
+              "class to be repackaged to '" + getExpectedPackageForEligibleClass() + "'");
+        } else {
+          description.appendText("class to be ineligible for repackaging");
+        }
+      }
+
+      @Override
+      public void describeMismatchSafely(Class<?> clazz, Description description) {
+        ClassSubject classSubject = inspector.clazz(clazz);
+        if (classSubject.isPresent()) {
+          description
+              .appendText("class ")
+              .appendValue(clazz.getTypeName())
+              .appendText(" was not (actual: '" + getActualPackage(classSubject) + "')");
+        } else {
+          description
+              .appendText("class ")
+              .appendValue(clazz.getTypeName())
+              .appendText(" was absent");
+        }
+      }
+
+      private String getActualPackage(ClassSubject classSubject) {
+        return classSubject.getDexProgramClass().getType().getPackageName();
+      }
+
+      private String getExpectedPackage(Class<?> clazz) {
+        return eligibleForRepackaging
+            ? getExpectedPackageForEligibleClass()
+            : clazz.getPackage().getName();
+      }
+
+      private String getExpectedPackageForEligibleClass() {
+        List<String> expectedPackageNames = new ArrayList<>();
+        expectedPackageNames.add(getRepackagePackage());
+        if (isFlattenPackageHierarchy()) {
+          expectedPackageNames.add(packageName != null ? packageName : "a");
+        }
+        return StringUtils.join(expectedPackageNames, ".");
+      }
+    };
+  }
+
+  protected void configureRepackaging(R8FullTestBuilder testBuilder) {
+    testBuilder
+        .addKeepRules(
+            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + getRepackagePackage() + "\"")
+        .addOptionsModification(
+            options -> {
+              assertFalse(options.testing.enableExperimentalRepackaging);
+              options.testing.enableExperimentalRepackaging = enableExperimentalRepackaging;
+            });
+  }
+
+  protected boolean isExperimentalRepackaging() {
+    return enableExperimentalRepackaging;
+  }
+
+  protected boolean isFlattenPackageHierarchy() {
+    return flattenPackageHierarchyOrRepackageClasses.equals(FLATTEN_PACKAGE_HIERARCHY);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java
new file mode 100644
index 0000000..42f2492
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithCollisionsTest extends RepackageTestBase {
+
+  public RepackageWithCollisionsTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Override
+  public String getRepackagePackage() {
+    return com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination.Foo
+        .class
+        .getPackage()
+        .getName();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addProgramClasses(getTestClasses())
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-keep class " + getRepackagePackage() + ".** { *; }")
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .apply(this::configureRepackaging)
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(
+            "first.Foo",
+            "first.Foo$Bar",
+            "first.first.Foo",
+            "first.first.Foo$Bar",
+            "second.Foo",
+            "second.Foo$Bar",
+            "destination.Foo",
+            "destination.Foo$Bar",
+            "destination.first.Foo",
+            "destination.first.Foo$Bar");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    Iterator<Class<?>> testClassesIterator = getTestClasses().iterator();
+
+    Class<?> firstFoo = testClassesIterator.next();
+    assertThat(firstFoo, isRepackagedAsExpected(inspector, "a"));
+
+    Class<?> firstFooBar = testClassesIterator.next();
+    assertThat(firstFooBar, isRepackagedAsExpected(inspector, "a"));
+
+    Class<?> firstFirstFoo = testClassesIterator.next();
+    assertThat(firstFirstFoo, isRepackagedAsExpected(inspector, "b"));
+
+    Class<?> firstFirstFooBar = testClassesIterator.next();
+    assertThat(firstFirstFooBar, isRepackagedAsExpected(inspector, "b"));
+
+    Class<?> secondFoo = testClassesIterator.next();
+    assertThat(secondFoo, isRepackagedAsExpected(inspector, "c"));
+
+    Class<?> secondBar = testClassesIterator.next();
+    assertThat(secondBar, isRepackagedAsExpected(inspector, "c"));
+
+    Class<?> destinationFoo = testClassesIterator.next();
+    assertThat(inspector.clazz(destinationFoo), isPresentAndNotRenamed());
+
+    Class<?> destinationFooBar = testClassesIterator.next();
+    assertThat(inspector.clazz(destinationFooBar), isPresentAndNotRenamed());
+
+    Class<?> destinationFirstFoo = testClassesIterator.next();
+    assertThat(inspector.clazz(destinationFirstFoo), isPresentAndNotRenamed());
+
+    Class<?> destinationFirstBar = testClassesIterator.next();
+    assertThat(inspector.clazz(destinationFirstBar), isPresentAndNotRenamed());
+  }
+
+  private static List<Class<?>> getTestClasses() {
+    return ImmutableList.of(
+        com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.Foo.class,
+        com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.Foo.Bar.class,
+        com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.first.Foo
+            .class,
+        com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.first.Foo.Bar
+            .class,
+        com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.second.Foo.class,
+        com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.second.Foo.Bar.class,
+        com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination.Foo
+            .class,
+        com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination.Foo.Bar
+            .class,
+        com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination.first.Foo
+            .class,
+        com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination.first.Foo
+            .Bar.class);
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      new com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.Foo();
+      new com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.Foo.Bar();
+      new com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.first.Foo();
+      new com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.first.Foo
+          .Bar();
+      new com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.second.Foo();
+      new com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.second.Foo.Bar();
+      new com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination.Foo();
+      new com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination.Foo
+          .Bar();
+      new com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination.first
+          .Foo();
+      new com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination.first
+          .Foo.Bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java
index 40e7721..1183c1f 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithFeatureSplitTest.java
@@ -9,10 +9,8 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
@@ -25,12 +23,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class RepackageWithFeatureSplitTest extends TestBase {
-
-  private static final String REPACKAGE_DIR = "foo";
-
-  private final String flattenPackageHierarchyOrRepackageClasses;
-  private final TestParameters parameters;
+public class RepackageWithFeatureSplitTest extends RepackageTestBase {
 
   @Parameters(name = "{1}, kind: {0}")
   public static List<Object[]> data() {
@@ -41,8 +34,7 @@
 
   public RepackageWithFeatureSplitTest(
       String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
-    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
-    this.parameters = parameters;
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
   }
 
   @Test
@@ -52,13 +44,7 @@
         .addFeatureSplit(FeatureMain.class, FeatureClass.class)
         .addFeatureSplitRuntime()
         .addKeepFeatureMainRule(FeatureMain.class)
-        .addKeepRules(
-            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"")
-        .addOptionsModification(
-            options -> {
-              assertFalse(options.testing.enableExperimentalRepackaging);
-              options.testing.enableExperimentalRepackaging = true;
-            })
+        .apply(this::configureRepackaging)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
index 501340a..9bc8115 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
@@ -4,43 +4,23 @@
 
 package com.android.tools.r8.repackage;
 
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class RepackageWithInitClassTest extends TestBase {
-
-  private static final String REPACKAGE_PACKAGE = "foo";
-
-  private final String flattenPackageHierarchyOrRepackageClasses;
-  private final TestParameters parameters;
-
-  @Parameters(name = "{1}, kind: {0}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
-        getTestParameters().withAllRuntimesAndApiLevels().build());
-  }
+public class RepackageWithInitClassTest extends RepackageTestBase {
 
   public RepackageWithInitClassTest(
       String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
-    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
-    this.parameters = parameters;
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
   }
 
   @Test
@@ -49,13 +29,7 @@
         .addInnerClasses(getClass())
         .addClassObfuscationDictionary("a")
         .addKeepMainRule(TestClass.class)
-        .addKeepRules(
-            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_PACKAGE + "\"")
-        .addOptionsModification(
-            options -> {
-              assert !options.testing.enableExperimentalRepackaging;
-              options.testing.enableExperimentalRepackaging = true;
-            })
+        .apply(this::configureRepackaging)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
@@ -73,11 +47,7 @@
     assertThat(repackagedClassSubject.uniqueFieldWithName("GREETING"), not(isPresent()));
 
     // Verify that the class was repackaged.
-    assertEquals(
-        flattenPackageHierarchyOrRepackageClasses.equals(FLATTEN_PACKAGE_HIERARCHY)
-            ? REPACKAGE_PACKAGE + ".a"
-            : REPACKAGE_PACKAGE,
-        repackagedClassSubject.getDexProgramClass().getType().getPackageName());
+    assertThat(StaticMemberValuePropagation.class, isRepackaged(inspector));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
index 00dd079..4fc28f5 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
@@ -9,11 +9,9 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8TestCompileResult;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -25,12 +23,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class RepackageWithMainDexListTest extends TestBase {
-
-  private static final String REPACKAGE_DIR = "foo";
-
-  private final String flattenPackageHierarchyOrRepackageClasses;
-  private final TestParameters parameters;
+public class RepackageWithMainDexListTest extends RepackageTestBase {
 
   @Parameters(name = "{1}, kind: {0}")
   public static List<Object[]> data() {
@@ -44,8 +37,7 @@
 
   public RepackageWithMainDexListTest(
       String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
-    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
-    this.parameters = parameters;
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
   }
 
   @Test
@@ -56,15 +48,9 @@
         .addKeepClassRulesWithAllowObfuscation(TestClass.class, OtherTestClass.class)
         .addKeepRules(
             "-keepclassmembers class " + TestClass.class.getTypeName() + " { <methods>; }")
-        .addKeepRules(
-            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"")
         // Add a class that will be repackaged to the main dex list.
         .addMainDexListClasses(TestClass.class)
-        .addOptionsModification(
-            options -> {
-              assertFalse(options.testing.enableExperimentalRepackaging);
-              options.testing.enableExperimentalRepackaging = true;
-            })
+        .apply(this::configureRepackaging)
         // Debug mode to enable minimal main dex.
         .debug()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithNonReboundFieldReferenceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithNonReboundFieldReferenceTest.java
new file mode 100644
index 0000000..2372f8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithNonReboundFieldReferenceTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundFieldReferenceTestClasses;
+import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundFieldReferenceTestClasses.B;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackageWithNonReboundFieldReferenceTest extends RepackageTestBase {
+
+  private final boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding;
+
+  @Parameters(name = "{2}, use access info collections: {0}, kind: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public RepackageWithNonReboundFieldReferenceTest(
+      boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding,
+      String flattenPackageHierarchyOrRepackageClasses,
+      TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+    this.alwaysUseExistingAccessInfoCollectionsInMemberRebinding =
+        alwaysUseExistingAccessInfoCollectionsInMemberRebinding;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass(), RepackagingWithNonReboundFieldReferenceTestClasses.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              assertTrue(options.testing.alwaysUseExistingAccessInfoCollectionsInMemberRebinding);
+              options.testing.alwaysUseExistingAccessInfoCollectionsInMemberRebinding =
+                  alwaysUseExistingAccessInfoCollectionsInMemberRebinding;
+            })
+        .apply(this::configureRepackaging)
+        .enableMemberValuePropagationAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(B.GREETING);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithNonReboundMethodReferenceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithNonReboundMethodReferenceTest.java
new file mode 100644
index 0000000..cbb352a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithNonReboundMethodReferenceTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundMethodReferenceTestClasses;
+import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundMethodReferenceTestClasses.B;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackageWithNonReboundMethodReferenceTest extends RepackageTestBase {
+
+  private final boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding;
+
+  @Parameters(name = "{2}, use access info collections: {0}, kind: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public RepackageWithNonReboundMethodReferenceTest(
+      boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding,
+      String flattenPackageHierarchyOrRepackageClasses,
+      TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+    this.alwaysUseExistingAccessInfoCollectionsInMemberRebinding =
+        alwaysUseExistingAccessInfoCollectionsInMemberRebinding;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass(), RepackagingWithNonReboundMethodReferenceTestClasses.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              assertTrue(options.testing.alwaysUseExistingAccessInfoCollectionsInMemberRebinding);
+              options.testing.alwaysUseExistingAccessInfoCollectionsInMemberRebinding =
+                  alwaysUseExistingAccessInfoCollectionsInMemberRebinding;
+            })
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new B().greet();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
new file mode 100644
index 0000000..4672079
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithPackagePrivateClassAnnotationTest extends RepackageTestBase {
+
+  public RepackageWithPackagePrivateClassAnnotationTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(NonPublicKeptAnnotation.class)
+        .addKeepRuntimeVisibleAnnotations()
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(IneligibleForRepackaging.class);
+    assertThat(classSubject, isPresent());
+
+    // Verify that the class was not repackaged.
+    assertEquals(
+        IneligibleForRepackaging.class.getPackage().getName(),
+        classSubject.getDexProgramClass().getType().getPackageName());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      IneligibleForRepackaging.greet();
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  @interface NonPublicKeptAnnotation {}
+
+  @NonPublicKeptAnnotation
+  public static class IneligibleForRepackaging {
+
+    @NeverInline
+    public static void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
new file mode 100644
index 0000000..46a83ff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithPackagePrivateFieldAnnotationTest extends RepackageTestBase {
+
+  public RepackageWithPackagePrivateFieldAnnotationTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(NonPublicKeptAnnotation.class)
+        .addKeepRuntimeVisibleAnnotations()
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(IneligibleForRepackaging.class);
+    assertThat(classSubject, isPresent());
+
+    // Verify that the class was not repackaged.
+    assertEquals(
+        IneligibleForRepackaging.class.getPackage().getName(),
+        classSubject.getDexProgramClass().getType().getPackageName());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      IneligibleForRepackaging.greet();
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.FIELD})
+  @interface NonPublicKeptAnnotation {}
+
+  public static class IneligibleForRepackaging {
+
+    @NeverPropagateValue @NonPublicKeptAnnotation private static String GREETING = "Hello world!";
+
+    @NeverInline
+    public static void greet() {
+      System.out.println(GREETING);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldTypeTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldTypeTest.java
new file mode 100644
index 0000000..9dd90f3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldTypeTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithPackagePrivateFieldTypeTest extends RepackageTestBase {
+
+  public RepackageWithPackagePrivateFieldTypeTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(NonPublicKeptClass.class)
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(IneligibleForRepackaging.class);
+    assertThat(classSubject, isPresent());
+
+    // Verify that the class was not repackaged.
+    assertEquals(
+        IneligibleForRepackaging.class.getPackage().getName(),
+        classSubject.getDexProgramClass().getType().getPackageName());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      IneligibleForRepackaging.greet();
+    }
+  }
+
+  static class NonPublicKeptClass {}
+
+  public static class PublicSubClass extends NonPublicKeptClass {}
+
+  public static class IneligibleForRepackaging {
+
+    @NeverPropagateValue
+    private static NonPublicKeptClass FIELD =
+        System.currentTimeMillis() > 0 ? new PublicSubClass() : null;
+
+    @NeverInline
+    public static void greet() {
+      if (FIELD != null) {
+        System.out.println("Hello world!");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
new file mode 100644
index 0000000..e4c7fd1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.repackage.RepackageWithPackagePrivateInnerClassTest.IneligibleForRepackaging.NonPublicKeptClass;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithPackagePrivateInnerClassTest extends RepackageTestBase {
+
+  public RepackageWithPackagePrivateInnerClassTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(NonPublicKeptClass.class)
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(IneligibleForRepackaging.class);
+    assertThat(classSubject, isPresent());
+
+    // Verify that the class was not repackaged.
+    assertEquals(
+        IneligibleForRepackaging.class.getPackage().getName(),
+        classSubject.getDexProgramClass().getType().getPackageName());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      IneligibleForRepackaging.greet();
+    }
+  }
+
+  public static class IneligibleForRepackaging {
+
+    @NeverInline
+    public static void greet() {
+      System.out.println("Hello world!");
+    }
+
+    static class NonPublicKeptClass {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInterfaceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInterfaceTest.java
new file mode 100644
index 0000000..7fa82cd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInterfaceTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithPackagePrivateInterfaceTest extends RepackageTestBase {
+
+  public RepackageWithPackagePrivateInterfaceTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(NonPublicKeptInterface.class)
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(IneligibleForRepackaging.class);
+    assertThat(classSubject, isPresent());
+
+    // Verify that the class was not repackaged.
+    assertEquals(
+        IneligibleForRepackaging.class.getPackage().getName(),
+        classSubject.getDexProgramClass().getType().getPackageName());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      IneligibleForRepackaging.greet();
+    }
+  }
+
+  interface NonPublicKeptInterface {}
+
+  public static class IneligibleForRepackaging implements NonPublicKeptInterface {
+
+    @NeverInline
+    public static void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
new file mode 100644
index 0000000..068cc89
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithPackagePrivateMethodAnnotationTest extends RepackageTestBase {
+
+  public RepackageWithPackagePrivateMethodAnnotationTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(NonPublicKeptAnnotation.class)
+        .addKeepRuntimeVisibleAnnotations()
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(IneligibleForRepackaging.class);
+    assertThat(classSubject, isPresent());
+
+    // Verify that the class was not repackaged.
+    assertEquals(
+        IneligibleForRepackaging.class.getPackage().getName(),
+        classSubject.getDexProgramClass().getType().getPackageName());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      IneligibleForRepackaging.greet();
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD})
+  @interface NonPublicKeptAnnotation {}
+
+  public static class IneligibleForRepackaging {
+
+    @NonPublicKeptAnnotation
+    @NeverInline
+    public static void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
new file mode 100644
index 0000000..c7cd698
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithPackagePrivateMethodParameterAnnotationTest extends RepackageTestBase {
+
+  public RepackageWithPackagePrivateMethodParameterAnnotationTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(NonPublicKeptAnnotation.class)
+        .addKeepRuntimeVisibleParameterAnnotations()
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(IneligibleForRepackaging.class);
+    assertThat(classSubject, isPresent());
+
+    // Verify that the class was not repackaged.
+    assertEquals(
+        IneligibleForRepackaging.class.getPackage().getName(),
+        classSubject.getDexProgramClass().getType().getPackageName());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      IneligibleForRepackaging.greet("Hello world!");
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.PARAMETER})
+  @interface NonPublicKeptAnnotation {}
+
+  public static class IneligibleForRepackaging {
+
+    @NeverInline
+    public static void greet(@NonPublicKeptAnnotation String greeting) {
+      System.out.println(greeting);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateSuperClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateSuperClassTest.java
new file mode 100644
index 0000000..6029869
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateSuperClassTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageWithPackagePrivateSuperClassTest extends RepackageTestBase {
+
+  public RepackageWithPackagePrivateSuperClassTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(NonPublicKeptClass.class)
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(IneligibleForRepackaging.class);
+    assertThat(classSubject, isPresent());
+
+    // Verify that the class was not repackaged.
+    assertEquals(
+        IneligibleForRepackaging.class.getPackage().getName(),
+        classSubject.getDexProgramClass().getType().getPackageName());
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      IneligibleForRepackaging.greet();
+    }
+  }
+
+  static class NonPublicKeptClass {}
+
+  public static class IneligibleForRepackaging extends NonPublicKeptClass {
+
+    @NeverInline
+    public static void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundFieldReferenceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundFieldReferenceTest.java
deleted file mode 100644
index c8a9d82..0000000
--- a/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundFieldReferenceTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.repackage;
-
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.repackage.RepackageWithMainDexListTest.TestClass;
-import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundFieldReferenceTestClasses;
-import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundFieldReferenceTestClasses.B;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class RepackagingWithNonReboundFieldReferenceTest extends TestBase {
-
-  private static final String REPACKAGE_DIR = "foo";
-
-  private final String flattenPackageHierarchyOrRepackageClasses;
-  private final TestParameters parameters;
-
-  @Parameters(name = "{1}, kind: {0}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
-        getTestParameters().withAllRuntimesAndApiLevels().build());
-  }
-
-  public RepackagingWithNonReboundFieldReferenceTest(
-      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
-    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void test() throws Exception {
-    try {
-      testForR8(parameters.getBackend())
-          .addInnerClasses(getClass(), RepackagingWithNonReboundFieldReferenceTestClasses.class)
-          .addKeepMainRule(TestClass.class)
-          .addKeepRules(
-              "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"")
-          .addOptionsModification(
-              options -> {
-                assertFalse(options.testing.enableExperimentalRepackaging);
-                options.testing.enableExperimentalRepackaging = true;
-              })
-          .enableMemberValuePropagationAnnotations()
-          .enableNoVerticalClassMergingAnnotations()
-          .setMinApi(parameters.getApiLevel())
-          .compile();
-
-      // TODO(b/168282032): Support lens rewriting of non-rebound references in the writer.
-      fail();
-    } catch (CompilationFailedException exception) {
-      // Ignore.
-    }
-  }
-
-  static class TestClass {
-
-    public static void main(String[] args) {
-      System.out.println(B.GREETING);
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundMethodReferenceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundMethodReferenceTest.java
deleted file mode 100644
index 48891e7..0000000
--- a/src/test/java/com/android/tools/r8/repackage/RepackagingWithNonReboundMethodReferenceTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.repackage;
-
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
-import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundMethodReferenceTestClasses;
-import com.android.tools.r8.repackage.testclasses.RepackagingWithNonReboundMethodReferenceTestClasses.B;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class RepackagingWithNonReboundMethodReferenceTest extends TestBase {
-
-  private static final String REPACKAGE_DIR = "foo";
-
-  private final String flattenPackageHierarchyOrRepackageClasses;
-  private final TestParameters parameters;
-
-  @Parameters(name = "{1}, kind: {0}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
-        getTestParameters().withAllRuntimesAndApiLevels().build());
-  }
-
-  public RepackagingWithNonReboundMethodReferenceTest(
-      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
-    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void test() throws Exception {
-    try {
-      testForR8(parameters.getBackend())
-          .addInnerClasses(getClass(), RepackagingWithNonReboundMethodReferenceTestClasses.class)
-          .addKeepMainRule(TestClass.class)
-          .addKeepRules(
-              "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"")
-          .addOptionsModification(
-              options -> {
-                assertFalse(options.testing.enableExperimentalRepackaging);
-                options.testing.enableExperimentalRepackaging = true;
-              })
-          .enableInliningAnnotations()
-          .enableNeverClassInliningAnnotations()
-          .enableNoVerticalClassMergingAnnotations()
-          .setMinApi(parameters.getApiLevel())
-          .compile();
-
-      // TODO(b/168282032): Support lens rewriting of non-rebound references in the writer.
-      fail();
-    } catch (CompilationFailedException exception) {
-      // Ignore.
-    }
-  }
-
-  static class TestClass {
-
-    public static void main(String[] args) {
-      new B().greet();
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/RepackagingWithNonReboundMethodReferenceTestClasses.java b/src/test/java/com/android/tools/r8/repackage/testclasses/RepackagingWithNonReboundMethodReferenceTestClasses.java
index fb4775f..3979c2b 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/RepackagingWithNonReboundMethodReferenceTestClasses.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/RepackagingWithNonReboundMethodReferenceTestClasses.java
@@ -20,5 +20,6 @@
     }
   }
 
+  @NeverClassInline
   public static class B extends A {}
 }
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/destination/Foo.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/destination/Foo.java
new file mode 100644
index 0000000..3def86a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/destination/Foo.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination;
+
+import com.android.tools.r8.NeverClassInline;
+
+@NeverClassInline
+public class Foo {
+
+  public Foo() {
+    System.out.println("destination.Foo");
+  }
+
+  @NeverClassInline
+  public static class Bar {
+
+    public Bar() {
+      System.out.println("destination.Foo$Bar");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/destination/first/Foo.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/destination/first/Foo.java
new file mode 100644
index 0000000..7cde833
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/destination/first/Foo.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.destination.first;
+
+import com.android.tools.r8.NeverClassInline;
+
+@NeverClassInline
+public class Foo {
+
+  public Foo() {
+    System.out.println("destination.first.Foo");
+  }
+
+  @NeverClassInline
+  public static class Bar {
+
+    public Bar() {
+      System.out.println("destination.first.Foo$Bar");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/first/Foo.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/first/Foo.java
new file mode 100644
index 0000000..9f8beaf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/first/Foo.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first;
+
+import com.android.tools.r8.NeverClassInline;
+
+@NeverClassInline
+public class Foo {
+
+  public Foo() {
+    System.out.println("first.Foo");
+  }
+
+  @NeverClassInline
+  public static class Bar {
+
+    public Bar() {
+      System.out.println("first.Foo$Bar");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/first/first/Foo.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/first/first/Foo.java
new file mode 100644
index 0000000..586ec4e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/first/first/Foo.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.first;
+
+import com.android.tools.r8.NeverClassInline;
+
+@NeverClassInline
+public class Foo {
+
+  public Foo() {
+    System.out.println("first.first.Foo");
+  }
+
+  @NeverClassInline
+  public static class Bar {
+
+    public Bar() {
+      System.out.println("first.first.Foo$Bar");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/second/Foo.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/second/Foo.java
new file mode 100644
index 0000000..b950606
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagewithcollisionstest/second/Foo.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.second;
+
+import com.android.tools.r8.NeverClassInline;
+
+@NeverClassInline
+public class Foo {
+
+  public Foo() {
+    System.out.println("second.Foo");
+  }
+
+  @NeverClassInline
+  public static class Bar {
+
+    public Bar() {
+      System.out.println("second.Foo$Bar");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index acf4aff..8a0f5e5 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTraceWithInfo;
 import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
+import com.android.tools.r8.retrace.stacktraces.PGStackTrace;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Charsets;
 import java.io.ByteArrayOutputStream;
@@ -117,6 +118,16 @@
   }
 
   @Test
+  public void testPGStackTrace() throws Exception {
+    PGStackTrace pgStackTrace = new PGStackTrace();
+    runTest(
+        pgStackTrace.mapping(),
+        StringUtils.joinLines(pgStackTrace.obfuscatedStackTrace()),
+        false,
+        StringUtils.joinLines(pgStackTrace.retracedStackTrace()) + StringUtils.LINE_SEPARATOR);
+  }
+
+  @Test
   public void testEmpty() throws IOException {
     runTest("", "", false, "");
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 9af9e28..8bf3b29 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineSourceFileContextStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
 import com.android.tools.r8.retrace.stacktraces.NamedModuleStackTrace;
@@ -173,6 +174,11 @@
     runRetraceTest(new UnknownSourceStackTrace());
   }
 
+  @Test
+  public void testInlineSourceFileContext() {
+    runRetraceTest(new InlineSourceFileContextStackTrace());
+  }
+
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     RetraceCommand retraceCommand =
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
new file mode 100644
index 0000000..3c22097
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineSourceFileContextStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
+            + ".main(KotlinJavaSourceFileTestObject.java:1)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.joinLines(
+        "com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary ->"
+            + " com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary:",
+        "# {\"id\":\"sourceFile\",\"fileName\":\"KotlinJavaSourceFileTestLibrary.kt\"}",
+        "    void <init>() -> <init>",
+        "com.google.appreduce.remapper.KotlinJavaSourceFileTestObject ->"
+            + " com.google.appreduce.remapper.KotlinJavaSourceFileTestObject:",
+        "    void <init>() -> <init>",
+        "    1:1:void com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
+            + ".throwsException():22:22 -> main",
+        "    1:1:void com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
+            + ".callsThrowsException():19 -> main",
+        "    1:1:void main(java.lang.String[]):32 -> main",
+        "    2:7:void printStackTraceUpToMain(java.lang.Exception):19:24 -> main",
+        "    2:7:void main(java.lang.String[]):34 -> main");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
+            + ".throwsException(KotlinJavaSourceFileTestLibrary.kt:22)",
+        "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
+            + ".callsThrowsException(KotlinJavaSourceFileTestLibrary.kt:19)",
+        "  at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
+            + ".main(KotlinJavaSourceFileTestObject.java:32)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java
new file mode 100644
index 0000000..2732b34
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class PGStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime: java.lang.NullPointerException: Attempt"
+            + " to invoke virtual method 'boolean"
+            + " com.google.android.foo(com.google.android.foo.Data$Key)' on a null object"
+            + " reference",
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime:        at"
+            + " com.google.apps.sectionheader.SectionHeaderListController.onToolbarStateChanged(PG:586)",
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime:        at"
+            + " com.google.apps.Controller.onToolbarStateChanged(PG:1087)");
+  }
+
+  @Override
+  public String mapping() {
+    return "";
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime: java.lang.NullPointerException: Attempt"
+            + " to invoke virtual method 'boolean"
+            + " com.google.android.foo(com.google.android.foo.Data$Key)' on a null object"
+            + " reference",
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime:        at"
+            + " com.google.apps.sectionheader.SectionHeaderListController.onToolbarStateChanged(SectionHeaderListController.java:586)",
+        "09-16 15:43:01.249 23316 23316 E AndroidRuntime:        at"
+            + " com.google.apps.Controller.onToolbarStateChanged(Controller.java:1087)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
index a0049f4..0be3ece 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
@@ -481,14 +481,12 @@
     runR8Test(
         builder -> {
           builder.addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions);
-          builder.addOptionsModification(options -> options.testing.readInputStackMaps = true);
         },
         inspector -> checkAssertionCodeEnabled(inspector, true),
         allAssertionsExpectedLines());
     runR8Test(
         builder -> {
           builder.addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions);
-          builder.addOptionsModification(options -> options.testing.readInputStackMaps = true);
         },
         inspector -> checkAssertionCodeEnabled(inspector, true),
         allAssertionsExpectedLines(),
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index d989fbf..fd676a3 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -210,8 +210,7 @@
                     + " *** *(...); }")
             .compile()
             .graphInspector();
-    // TODO(b/159418523): It appears that the insertion in never-inline causes additional retention.
-    assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector, false, true);
+    assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector, false, false);
 
     GraphInspector ifThenKeepClassesWithMembersInspector =
         testForR8(Backend.CF)
@@ -228,9 +227,8 @@
                     + " *** *(...); }")
             .compile()
             .graphInspector();
-    // TODO(b/159418523): It appears that the insertion in never-inline causes additional retention.
     assertRetainedClassesEqual(
-        referenceInspector, ifThenKeepClassesWithMembersInspector, false, true);
+        referenceInspector, ifThenKeepClassesWithMembersInspector, false, false);
 
     GraphInspector ifHasMemberThenKeepClassInspector =
         testForR8(Backend.CF)
@@ -249,9 +247,8 @@
                     + " *** <2>(...); }")
             .compile()
             .graphInspector();
-    // TODO(b/159418523): It appears that the insertion in never-inline causes additional retention.
-    //  Also, here neither is a subset of the other. That also appears wrong.
-    assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true, true);
+    // TODO(b/159418523): Should the reference be equal to the result with the conditional rule?
+    assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true, false);
   }
 
   private void assertRetainedClassesEqual(
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
index de762ac..cd6e4a3 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
@@ -54,8 +54,7 @@
         .assertStdoutThatMatches(containsString("referenced in keep rule"))
         // TODO(b/124655065): We should always know the reason for keeping.
         // It is OK if this starts failing while the kept-graph API is incomplete, in which case
-        // replace
-        // the 'not(containsString(' by just 'containsString('.
+        // replace the 'not(containsString(' by just 'containsString('.
         .assertStdoutThatMatches(not(containsString("kept for unknown reasons")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index c84415e..84621b6 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -627,7 +627,7 @@
     void visitTryCatchBlock(Label start, Label end, Label handler, String type);
   }
 
-  private MethodVisitor redirectVistiTryCatchBlock(
+  private MethodVisitor redirectVisitTryCatchBlock(
       MethodVisitor visitor, VisitTryCatchBlockCallback callback) {
     return new MethodVisitor(ASM7, visitor) {
       @Override
@@ -649,7 +649,7 @@
                   end,
                   handler,
                   type,
-                  redirectVistiTryCatchBlock(this, super::visitTryCatchBlock));
+                  redirectVisitTryCatchBlock(this, super::visitTryCatchBlock));
             } else {
               super.visitTryCatchBlock(start, end, handler, type);
             }
@@ -685,4 +685,18 @@
           }
         });
   }
+
+  public ClassFileTransformer stripFrames(String methodName) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+
+          @Override
+          public void visitFrame(
+              int type, int numLocal, Object[] local, int numStack, Object[] stack) {
+            if (!getContext().method.getMethodName().equals(methodName)) {
+              super.visitFrame(type, numLocal, local, numStack, stack);
+            }
+          }
+        });
+  }
 }